Coverage for utilities/tests/test_array.py: 100%

1"""Define the unit tests for the :mod:`colour.utilities.array` module."""

2

3 from __future__ import annotations

4

5 import typing

6 import unittest

7 from copy import deepcopy

8 from dataclasses import dataclass, field, fields

9 from functools import partial

10

11 import numpy as np

12 import pytest

13

14 from colour.constants import (

15 DTYPE_COMPLEX_DEFAULT,

16 DTYPE_FLOAT_DEFAULT,

17 DTYPE_INT_DEFAULT,

18 TOLERANCE_ABSOLUTE_TESTS,

19)

20

21 if typing.TYPE_CHECKING:

22 from colour.hints import (

23 Annotated,

24 Any,

25 ArrayLike,

26 Domain1,

27 Domain10,

28 Domain100,

29 Domain100_100_360,

30 Domain360,

31 DType,

32 NDArray,

33 NDArrayFloat,

34 Range1,

35 Range10,

36 Range100,

37 Range100_100_360,

38 Range360,

39 Type,

40 )

41 else:

42 # Import Annotated at runtime for test helper function signatures

43 # get_domain_range_scale_metadata() needs to access Annotated.__metadata__

44 from colour.hints import ( # noqa: TC001

45 Annotated,

46 Any,

47 ArrayLike,

48 Domain1,

49 Domain10,

50 Domain100,

51 Domain360,

52 Domain100_100_360,

53 NDArrayFloat,

54 Range1,

55 Range10,

56 Range100,

57 Range360,

58 Range100_100_360,

59 )

60

61 from colour.utilities import (

62 MixinDataclassArithmetic,

63 MixinDataclassArray,

64 MixinDataclassFields,

65 MixinDataclassIterable,

66 as_array,

67 as_complex_array,

68 as_float,

69 as_float_array,

70 as_float_scalar,

71 as_int,

72 as_int_array,

73 as_int_scalar,

74 centroid,

75 closest,

76 closest_indexes,

77 domain_range_scale,

78 fill_nan,

79 format_array_as_row,

80 from_range_1,

81 from_range_10,

82 from_range_100,

83 from_range_degrees,

84 from_range_int,

85 full,

86 get_domain_range_scale,

87 get_domain_range_scale_metadata,

88 has_only_nan,

89 in_array,

90 index_along_last_axis,

91 interval,

92 is_ndarray_copy_enabled,

93 is_networkx_installed,

94 is_scipy_installed,

95 is_uniform,

96 ndarray_copy,

97 ndarray_copy_enable,

98 ndarray_write,

99 ones,

100 orient,

101 row_as_diagonal,

102 set_default_float_dtype,

103 set_default_int_dtype,

104 set_domain_range_scale,

105 set_ndarray_copy_enable,

106 to_domain_1,

107 to_domain_10,

108 to_domain_100,

109 to_domain_degrees,

110 to_domain_int,

111 tsplit,

112 tstack,

113 zeros,

114)

115

116 __author__ = "Colour Developers"

117 __copyright__ = "Copyright 2013 Colour Developers"

118 __license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"

119 __maintainer__ = "Colour Developers"

120 __email__ = "colour-developers@colour-science.org"

121 __status__ = "Production"

122

123 __all__ = [

124 "TestMixinDataclassFields",

125 "TestMixinDataclassIterable",

126 "TestMixinDataclassArray",

127 "TestMixinDataclassArithmetic",

128 "TestAsArray",

129 "TestAsInt",

130 "TestAsFloat",

131 "TestAsIntArray",

132 "TestAsFloatArray",

133 "TestAsComplexArray",

134 "TestAsIntScalar",

135 "TestAsFloatScalar",

136 "TestSetDefaultIntegerDtype",

137 "TestSetDefaultFloatDtype",

138 "TestGetDomainRangeScale",

139 "TestSetDomainRangeScale",

140 "TestDomainRangeScale",

141 "TestGetDomainRangeScaleMetadata",

142 "TestToDomain1",

143 "TestToDomain10",

144 "TestToDomain100",

145 "TestToDomainDegrees",

146 "TestToDomainInt",

147 "TestFromRange1",

148 "TestFromRange10",

149 "TestFromRange100",

150 "TestFromRangeDegrees",

151 "TestFromRangeInt",

152 "TestIsNdarrayCopyEnabled",

153 "TestSetNdarrayCopyEnabled",

154 "TestNdarrayCopyEnable",

155 "TestNdarrayCopy",

156 "TestClosestIndexes",

157 "TestClosest",

158 "TestInterval",

159 "TestIsUniform",

160 "TestInArray",

161 "TestTstack",

162 "TestTsplit",

163 "TestRowAsDiagonal",

164 "TestOrient",

165 "TestCentroid",

166 "TestFillNan",

167 "TestHasNanOnly",

168 "TestNdarrayWrite",

169 "TestZeros",

170 "TestOnes",

171 "TestFull",

172 "TestIndexAlongLastAxis",

173]

174

175

176 class TestMixinDataclassFields(unittest.TestCase):

177 """

178 Define :class:`colour.utilities.array.MixinDataclassFields` class unit

179 tests methods.

180 """

181

182 def setUp(self) -> None:

183 """Initialise the common tests attributes."""

184

185 @dataclass

186 class Data(MixinDataclassFields):

187 a: str

188 b: str

189 c: str

190

191 self._data: Data = Data(a="Foo", b="Bar", c="Baz")

192

193 def test_required_attributes(self) -> None:

194 """Test the presence of required attributes."""

195

196 required_attributes = ("fields",)

197

198 for method in required_attributes:

199 assert method in dir(MixinDataclassFields)

200

201 def test_fields(self) -> None:

202 """

203 Test :meth:`colour.utilities.array.MixinDataclassIterable._fields`

204 method.

205 """

206

207 assert self._data.fields == fields(self._data)

208

209

210 class TestMixinDataclassIterable(unittest.TestCase):

211 """

212 Define :class:`colour.utilities.array.MixinDataclassIterable` class unit

213 tests methods.

214 """

215

216 def setUp(self) -> None:

217 """Initialise the common tests attributes."""

218

219 @dataclass

220 class Data(MixinDataclassIterable):

221 a: str

222 b: str

223 c: str

224

225 self._data: Data = Data(a="Foo", b="Bar", c="Baz")

226

227 def test_required_attributes(self) -> None:

228 """Test the presence of required attributes."""

229

230 required_attributes = (

231 "keys",

232 "values",

233 "items",

234 )

235

236 for method in required_attributes:

237 assert method in dir(MixinDataclassIterable)

238

239 def test_required_methods(self) -> None:

240 """Test the presence of required methods."""

241

242 required_methods = ("__iter__",)

243

244 for method in required_methods:

245 assert method in dir(MixinDataclassIterable)

246

247 def test__iter__(self) -> None:

248 """

249 Test :meth:`colour.utilities.array.MixinDataclassIterable.__iter__`

250 method.

251 """

252

253 assert {key: value for key, value in self._data} == (

254 {"a": "Foo", "b": "Bar", "c": "Baz"}

255 )

256

257 def test_keys(self) -> None:

258 """

259 Test :meth:`colour.utilities.array.MixinDataclassIterable.keys`

260 method.

261 """

262

263 assert tuple(self._data.keys) == ("a", "b", "c")

264

265 def test_values(self) -> None:

266 """

267 Test :meth:`colour.utilities.array.MixinDataclassIterable.values`

268 method.

269 """

270

271 assert tuple(self._data.values) == ("Foo", "Bar", "Baz")

272

273 def test_items(self) -> None:

274 """

275 Test :meth:`colour.utilities.array.MixinDataclassIterable.items`

276 method.

277 """

278

279 assert tuple(self._data.items) == (("a", "Foo"), ("b", "Bar"), ("c", "Baz"))

280

281

282 class TestMixinDataclassArray(unittest.TestCase):

283 """

284 Define :class:`colour.utilities.array.MixinDataclassArray` class unit

285 tests methods.

286 """

287

288 def setUp(self) -> None:

289 """Initialise the common tests attributes."""

290

291 @dataclass

292 class Data(MixinDataclassArray):

293 a: float | list | tuple | np.ndarray | None = field(

294 default_factory=lambda: None

295 )

296

297 b: float | list | tuple | np.ndarray | None = field(

298 default_factory=lambda: None

299 )

300

301 c: float | list | tuple | np.ndarray | None = field(

302 default_factory=lambda: None

303 )

304

305 self._data: Data = Data(

306 b=np.array([0.1, 0.2, 0.3]), c=np.array([0.4, 0.5, 0.6])

307 )

308 self._array: NDArray = np.array(

309 [

310 [np.nan, 0.1, 0.4],

311 [np.nan, 0.2, 0.5],

312 [np.nan, 0.3, 0.6],

313 ]

314 )

315

316 def test_required_methods(self) -> None:

317 """Test the presence of required methods."""

318

319 required_methods = ("__array__",)

320

321 for method in required_methods:

322 assert method in dir(MixinDataclassArray)

323

324 def test__array__(self) -> None:

325 """

326 Test :meth:`colour.utilities.array.MixinDataclassArray.__array__`

327 method.

328 """

329

330 np.testing.assert_array_equal(self._data, self._array)

331

332 assert np.array(self._data, dtype=DTYPE_INT_DEFAULT).dtype == DTYPE_INT_DEFAULT

333

334

335 class TestMixinDataclassArithmetic(unittest.TestCase):

336 """

337 Define :class:`colour.utilities.array.MixinDataclassArithmetic` class unit

338 tests methods.

339 """

340

341 def setUp(self) -> None:

342 """Initialise the common tests attributes."""

343

344 @dataclass

345 class Data(MixinDataclassArithmetic):

346 a: float | list | tuple | np.ndarray | None = field(

347 default_factory=lambda: None

348 )

349

350 b: float | list | tuple | np.ndarray | None = field(

351 default_factory=lambda: None

352 )

353

354 c: float | list | tuple | np.ndarray | None = field(

355 default_factory=lambda: None

356 )

357

358 self._factory: Type[Data] = Data

359 self._data: Data = Data(

360 b=np.array([0.1, 0.2, 0.3]), c=np.array([0.4, 0.5, 0.6])

361 )

362 self._array: NDArray = np.array(

363 [

364 [np.nan, 0.1, 0.4],

365 [np.nan, 0.2, 0.5],

366 [np.nan, 0.3, 0.6],

367 ]

368 )

369

370 def test_required_methods(self) -> None:

371 """Test the presence of required methods."""

372

373 required_methods = (

374 "__iadd__",

375 "__add__",

376 "__isub__",

377 "__sub__",

378 "__imul__",

379 "__mul__",

380 "__idiv__",

381 "__div__",

382 "__ipow__",

383 "__pow__",

384 "arithmetical_operation",

385 )

386

387 for method in required_methods:

388 assert method in dir(MixinDataclassArithmetic)

389

390 def test_arithmetical_operation(self) -> None:

391 """

392 Test :meth:`colour.utilities.array.MixinDataclassArithmetic.\

393 arithmetical_operation` method.

394 """

395

396 np.testing.assert_allclose(

397 self._data.arithmetical_operation(10, "+", False),

398 self._array + 10,

399 atol=TOLERANCE_ABSOLUTE_TESTS,

400 )

401

402 np.testing.assert_allclose(

403 self._data.arithmetical_operation(10, "-", False),

404 self._array - 10,

405 atol=TOLERANCE_ABSOLUTE_TESTS,

406 )

407

408 np.testing.assert_allclose(

409 self._data.arithmetical_operation(10, "*", False),

410 self._array * 10,

411 atol=TOLERANCE_ABSOLUTE_TESTS,

412 )

413

414 np.testing.assert_allclose(

415 self._data.arithmetical_operation(10, "/", False),

416 self._array / 10,

417 atol=TOLERANCE_ABSOLUTE_TESTS,

418 )

419

420 np.testing.assert_allclose(

421 self._data.arithmetical_operation(10, "**", False),

422 self._array**10,

423 atol=TOLERANCE_ABSOLUTE_TESTS,

424 )

425

426 np.testing.assert_allclose(

427 self._data + 10,

428 self._array + 10,

429 atol=TOLERANCE_ABSOLUTE_TESTS,

430 )

431

432 np.testing.assert_allclose(

433 self._data - 10,

434 self._array - 10,

435 atol=TOLERANCE_ABSOLUTE_TESTS,

436 )

437

438 np.testing.assert_allclose(

439 self._data * 10,

440 self._array * 10,

441 atol=TOLERANCE_ABSOLUTE_TESTS,

442 )

443

444 np.testing.assert_allclose(

445 self._data / 10,

446 self._array / 10,

447 atol=TOLERANCE_ABSOLUTE_TESTS,

448 )

449

450 np.testing.assert_allclose(

451 self._data**10,

452 self._array**10,

453 atol=TOLERANCE_ABSOLUTE_TESTS,

454 )

455

456 data = deepcopy(self._data)

457

458 np.testing.assert_allclose(

459 data.arithmetical_operation(10, "+", True),

460 self._array + 10,

461 atol=TOLERANCE_ABSOLUTE_TESTS,

462 )

463

464 np.testing.assert_allclose(

465 data.arithmetical_operation(10, "-", True),

466 self._array,

467 atol=TOLERANCE_ABSOLUTE_TESTS,

468 )

469

470 np.testing.assert_allclose(

471 data.arithmetical_operation(10, "*", True),

472 self._array * 10,

473 atol=TOLERANCE_ABSOLUTE_TESTS,

474 )

475

476 np.testing.assert_allclose(

477 data.arithmetical_operation(10, "/", True),

478 self._array,

479 atol=TOLERANCE_ABSOLUTE_TESTS,

480 )

481

482 np.testing.assert_allclose(

483 data.arithmetical_operation(10, "**", True),

484 self._array**10,

485 atol=TOLERANCE_ABSOLUTE_TESTS,

486 )

487

488 data = deepcopy(self._data)

489

490 np.testing.assert_allclose(

491 data.arithmetical_operation(self._array, "+", False),

492 data + self._array,

493 atol=TOLERANCE_ABSOLUTE_TESTS,

494 )

495

496 np.testing.assert_allclose(

497 data.arithmetical_operation(data, "+", False),

498 data + data,

499 atol=TOLERANCE_ABSOLUTE_TESTS,

500 )

501

502 data = self._factory(1, 2, 3)

503

504 data += 1

505 assert data.a == 2

506

507 data -= 1

508 assert data.a == 1

509

510 data *= 2

511 assert data.a == 2

512

513 data /= 2

514 assert data.a == 1

515

516 data **= 0.5

517 assert data.a == 1

518

519

520 class TestAsArray(unittest.TestCase):

521 """

522 Define :func:`colour.utilities.array.as_array` definition unit tests

523 methods.

524 """

525

526 def test_as_array(self) -> None:

527 """Test :func:`colour.utilities.array.as_array` definition."""

528

529 np.testing.assert_equal(as_array([1, 2, 3]), np.array([1, 2, 3]))

530

531 assert as_array([1, 2, 3], DTYPE_FLOAT_DEFAULT).dtype == DTYPE_FLOAT_DEFAULT

532

533 assert as_array([1, 2, 3], DTYPE_INT_DEFAULT).dtype == DTYPE_INT_DEFAULT

534

535 np.testing.assert_equal(

536 as_array(dict(zip("abc", [1, 2, 3], strict=True)).values()),

537 np.array([1, 2, 3]),

538 )

539

540

541 class TestAsInt(unittest.TestCase):

542 """

543 Define :func:`colour.utilities.array.as_int` definition unit tests

544 methods.

545 """

546

547 def test_as_int(self) -> None:

548 """Test :func:`colour.utilities.array.as_int` definition."""

549

550 assert as_int(1) == 1

551

552 assert as_int(np.array([1])).ndim == 1

553

554 assert as_int(np.array([[1]])).ndim == 2

555

556 np.testing.assert_array_equal(

557 as_int(np.array([1.0, 2.0, 3.0])), np.array([1, 2, 3])

558 )

559

560 assert as_int(np.array([1.0, 2.0, 3.0])).dtype == DTYPE_INT_DEFAULT

561

562 assert isinstance(as_int(1), DTYPE_INT_DEFAULT)

563

564

565 class TestAsFloat(unittest.TestCase):

566 """

567 Define :func:`colour.utilities.array.as_float` definition unit tests

568 methods.

569 """

570

571 def test_as_float(self) -> None:

572 """Test :func:`colour.utilities.array.as_float` definition."""

573

574 assert as_float(1) == 1.0

575

576 assert as_float(np.array([1])).ndim == 1

577

578 assert as_float(np.array([[1]])).ndim == 2

579

580 np.testing.assert_allclose(

581 as_float(np.array([1, 2, 3])),

582 np.array([1.0, 2.0, 3.0]),

583 atol=TOLERANCE_ABSOLUTE_TESTS,

584 )

585

586 assert as_float(np.array([1, 2, 3])).dtype == DTYPE_FLOAT_DEFAULT

587

588 assert isinstance(as_float(1), DTYPE_FLOAT_DEFAULT)

589

590

591 class TestAsIntArray(unittest.TestCase):

592 """

593 Define :func:`colour.utilities.array.as_int_array` definition unit tests

594 methods.

595 """

596

597 def test_as_int_array(self) -> None:

598 """Test :func:`colour.utilities.array.as_int_array` definition."""

599

600 np.testing.assert_equal(as_int_array([1.0, 2.0, 3.0]), np.array([1, 2, 3]))

601

602 assert as_int_array([1, 2, 3]).dtype == DTYPE_INT_DEFAULT

603

604

605 class TestAsFloatArray(unittest.TestCase):

606 """

607 Define :func:`colour.utilities.array.as_float_array` definition unit tests

608 methods.

609 """

610

611 def test_as_float_array(self) -> None:

612 """Test :func:`colour.utilities.array.as_float_array` definition."""

613

614 np.testing.assert_equal(as_float_array([1, 2, 3]), np.array([1, 2, 3]))

615

616 assert as_float_array([1, 2, 3]).dtype == DTYPE_FLOAT_DEFAULT

617

618

619 class TestAsComplexArray(unittest.TestCase):

620 """

621 Define :func:`colour.utilities.array.as_complex_array` definition unit tests

622 methods.

623 """

624

625 def test_as_complex_array(self) -> None:

626 """Test :func:`colour.utilities.array.as_complex_array` definition."""

627

628 np.testing.assert_equal(

629 as_complex_array([1, 2, 3]), np.array([1 + 0j, 2 + 0j, 3 + 0j])

630 )

631

632 np.testing.assert_equal(

633 as_complex_array([1 + 2j, 3 + 4j]), np.array([1 + 2j, 3 + 4j])

634 )

635

636 assert as_complex_array([1, 2, 3]).dtype == DTYPE_COMPLEX_DEFAULT

637

638 assert as_complex_array([1, 2, 3], np.complex64).dtype == np.complex64

639

640

641 class TestAsIntScalar(unittest.TestCase):

642 """

643 Define :func:`colour.utilities.array.as_int_scalar` definition unit tests

644 methods.

645 """

646

647 def test_as_int_scalar(self) -> None:

648 """Test :func:`colour.utilities.array.as_int_scalar` definition."""

649

650 assert as_int_scalar(1.0) == 1

651

652 assert as_int_scalar(1.0).dtype == DTYPE_INT_DEFAULT # pyright: ignore

653

654

655 class TestAsFloatScalar(unittest.TestCase):

656 """

657 Define :func:`colour.utilities.array.as_float_scalar` definition unit

658 tests methods.

659 """

660

661 def test_as_float_scalar(self) -> None:

662 """Test :func:`colour.utilities.array.as_float_scalar` definition."""

663

664 assert as_float_scalar(1) == 1.0

665

666 assert as_float_scalar(1).dtype == DTYPE_FLOAT_DEFAULT # pyright: ignore

667

668

669 class TestSetDefaultIntegerDtype(unittest.TestCase):

670 """

671 Define :func:`colour.utilities.array.set_default_int_dtype` definition unit

672 tests methods.

673 """

674

675 def test_set_default_int_dtype(self) -> None:

676 """

677 Test :func:`colour.utilities.array.set_default_int_dtype` definition.

678 """

679

680 assert as_int_array(np.ones(3)).dtype == np.int64

681

682 set_default_int_dtype(np.int32)

683

684 assert as_int_array(np.ones(3)).dtype == np.int32

685

686 set_default_int_dtype(np.int64)

687

688 assert as_int_array(np.ones(3)).dtype == np.int64

689

690 def tearDown(self) -> None:

691 """After tests actions."""

692

693 set_default_int_dtype(np.int64)

694

695

696 class TestSetDefaultFloatDtype(unittest.TestCase):

697 """

698 Define :func:`colour.utilities.array.set_default_float_dtype` definition unit

699 tests methods.

700 """

701

702 def test_set_default_float_dtype(self) -> None:

703 """

704 Test :func:`colour.utilities.array.set_default_float_dtype`

705 definition.

706 """

707

708 try:

709 assert as_float_array(np.ones(3)).dtype == np.float64

710

711 set_default_float_dtype(np.float16)

712

713 assert as_float_array(np.ones(3)).dtype == np.float16

714

715 set_default_float_dtype(np.float64)

716

717 assert as_float_array(np.ones(3)).dtype == np.float64

718 finally:

719 set_default_float_dtype(np.float64)

720

721 def test_set_default_float_dtype_enforcement(self) -> None:

722 """

723 Test whether :func:`colour.utilities.array.set_default_float_dtype`

724 effect is applied through most of *Colour* public API.

725 """

726

727 if not is_scipy_installed(): # pragma: no cover

728 return

729

730 if not is_networkx_installed(): # pragma: no cover

731 return

732

733 from colour.appearance import ( # noqa: PLC0415

734 CAM_Specification_CAM16,

735 CAM_Specification_CIECAM02,

736 CAM_Specification_CIECAM16,

737 CAM_Specification_Hellwig2022,

738 CAM_Specification_Kim2009,

739 CAM_Specification_sCAM,

740 CAM_Specification_ZCAM,

741 )

742 from colour.graph.conversion import ( # noqa: PLC0415

743 CONVERSION_SPECIFICATIONS_DATA,

744 convert,

745 )

746

747 try:

748 dtype = np.float32

749 set_default_float_dtype(dtype)

750

751 for source, target, _callable in CONVERSION_SPECIFICATIONS_DATA:

752 if target in ("Hexadecimal", "Munsell Colour"):

753 continue

754

755 # Spectral distributions are instantiated with float64 data and

756 # spectral up-sampling optimization fails.

757 if (

758 "Spectral Distribution" in (source, target) # noqa: PLR1714

759 or target == "Complementary Wavelength"

760 or target == "Dominant Wavelength"

761 ):

762 continue

763

764 a = np.array([(0.25, 0.5, 0.25), (0.25, 0.5, 0.25)])

765

766 if source == "CAM16":

767 a = CAM_Specification_CAM16(J=0.25, M=0.5, h=0.25)

768

769 if source == "CIECAM02":

770 a = CAM_Specification_CIECAM02(J=0.25, M=0.5, h=0.25)

771

772 if source == "CIECAM16":

773 a = CAM_Specification_CIECAM16(J=0.25, M=0.5, h=0.25)

774

775 if source == "Hellwig 2022":

776 a = CAM_Specification_Hellwig2022(J=0.25, M=0.5, h=0.25)

777

778 if source == "Kim 2009":

779 a = CAM_Specification_Kim2009(J=0.25, M=0.5, h=0.25)

780

781 if source == "sCAM":

782 a = CAM_Specification_sCAM(J=0.25, M=0.5, h=0.25)

783

784 if source == "ZCAM":

785 a = CAM_Specification_ZCAM(J=0.25, M=0.5, h=0.25)

786

787 if source == "CMYK":

788 a = np.array([(0.25, 0.5, 0.25, 0.5), (0.25, 0.5, 0.25, 0.5)])

789

790 if source == "Hexadecimal":

791 a = np.array(["#FFFFFF", "#FFFFFF"])

792

793 if source == "CSS Color 3":

794 a = "aliceblue"

795

796 if source == "Munsell Colour":

797 a = ["4.2YR 8.1/5.3", "4.2YR 8.1/5.3"]

798

799 if source == "Wavelength":

800 a = 555

801

802 if (

803 source.startswith("CCT") # noqa: PIE810

804 or source.endswith(" xy")

805 or source.endswith(" uv")

806 ):

807 a = np.array([(0.25, 0.5), (0.25, 0.5)])

808

809 def dtype_getter(x: NDArray) -> DType:

810 """Dtype getter callable."""

811

812 for specification in (

813 "ATD95",

814 "CIECAM02",

815 "CAM16",

816 "Hellwig 2022",

817 "Hunt",

818 "Kim 2009",

819 "LLAB",

820 "Nayatani95",

821 "RLAB",

822 "sCAM",

823 "ZCAM",

824 ):

825 if target.endswith(specification): # noqa: B023

826 return getattr(x, fields(x)[0].name).dtype # pyright: ignore

827

828 return x.dtype # pyright: ignore

829

830 assert dtype_getter(convert(a, source, target)) == dtype

831 finally:

832 set_default_float_dtype(np.float64)

833

834

835 class TestGetDomainRangeScale(unittest.TestCase):

836 """

837 Define :func:`colour.utilities.common.get_domain_range_scale` definition

838 unit tests methods.

839 """

840

841 def test_get_domain_range_scale(self) -> None:

842 """

843 Test :func:`colour.utilities.common.get_domain_range_scale`

844 definition.

845 """

846

847 with domain_range_scale("Reference"):

848 assert get_domain_range_scale() == "reference"

849

850 with domain_range_scale("1"):

851 assert get_domain_range_scale() == "1"

852

853 with domain_range_scale("100"):

854 assert get_domain_range_scale() == "100"

855

856

857 class TestSetDomainRangeScale(unittest.TestCase):

858 """

859 Define :func:`colour.utilities.common.set_domain_range_scale` definition

860 unit tests methods.

861 """

862

863 def test_set_domain_range_scale(self) -> None:

864 """

865 Test :func:`colour.utilities.common.set_domain_range_scale`

866 definition.

867 """

868

869 with domain_range_scale("Reference"):

870 set_domain_range_scale("1")

871 assert get_domain_range_scale() == "1"

872

873 with domain_range_scale("Reference"):

874 set_domain_range_scale("100")

875 assert get_domain_range_scale() == "100"

876

877 with domain_range_scale("1"):

878 set_domain_range_scale("Reference")

879 assert get_domain_range_scale() == "reference"

880

881 with pytest.raises(ValueError):

882 set_domain_range_scale("Invalid")

883

884

885 class TestDomainRangeScale(unittest.TestCase):

886 """

887 Define :func:`colour.utilities.common.domain_range_scale` definition

888 unit tests methods.

889 """

890

891 def test_domain_range_scale(self) -> None:

892 """

893 Test :func:`colour.utilities.common.domain_range_scale`

894 definition.

895 """

896

897 assert get_domain_range_scale() == "reference"

898

899 with domain_range_scale("Reference"):

900 assert get_domain_range_scale() == "reference"

901

902 assert get_domain_range_scale() == "reference"

903

904 with domain_range_scale("1"):

905 assert get_domain_range_scale() == "1"

906

907 assert get_domain_range_scale() == "reference"

908

909 with domain_range_scale("100"):

910 assert get_domain_range_scale() == "100"

911

912 assert get_domain_range_scale() == "reference"

913

914 def fn_a(a: ArrayLike) -> NDArrayFloat:

915 """Change the domain-range scale for unit testing."""

916

917 b = to_domain_10(a)

918

919 b *= 2

920

921 return from_range_100(b)

922

923 with domain_range_scale("Reference"):

924 with domain_range_scale("1"):

925 with domain_range_scale("100"):

926 with domain_range_scale("Ignore"):

927 assert get_domain_range_scale() == "ignore"

928 assert fn_a(4) == 8

929

930 assert get_domain_range_scale() == "100"

931 assert fn_a(40) == 8

932

933 assert get_domain_range_scale() == "1"

934 assert fn_a(0.4) == 0.08

935

936 assert get_domain_range_scale() == "reference"

937 assert fn_a(4) == 8

938

939 assert get_domain_range_scale() == "reference"

940

941 @domain_range_scale("1")

942 def fn_b(a: ArrayLike) -> NDArrayFloat:

943 """Change the domain-range scale for unit testing."""

944

945 b = to_domain_10(a)

946

947 b *= 2

948

949 return from_range_100(b)

950

951 assert fn_b(10) == 2.0

952

953

954 class TestGetDomainRangeScaleMetadata(unittest.TestCase):

955 """

956 Define :func:`colour.utilities.array.get_domain_range_scale_metadata`

957 definition unit tests methods.

958 """

959

960 def test_get_domain_range_scale_metadata(self) -> None:

961 """

962 Test :func:`colour.utilities.array.get_domain_range_scale_metadata`

963 definition.

964 """

965

966 # Pattern 1: Uniform parameter scaling

967 def function_a(

968 XYZ: Annotated[ArrayLike, 1],

969 illuminant: ArrayLike = None, # type: ignore

970 ) -> Annotated[NDArrayFloat, 100]: # type: ignore

971 """Test uniform parameter scaling."""

972

973 metadata = get_domain_range_scale_metadata(function_a)

974 assert metadata["domain"] == {"XYZ": 1}

975 assert metadata["range"] == 100

976

977 # Pattern 2: Per-parameter scaling (only some params scaled)

978 def function_b(

979 uv: ArrayLike,

980 illuminant: ArrayLike = None, # type: ignore

981 L: Annotated[ArrayLike, 100] = 100,

982 ) -> Annotated[NDArrayFloat, 100]: # type: ignore

983 """Test per-parameter scaling."""

984

985 metadata = get_domain_range_scale_metadata(function_b)

986 assert metadata["domain"] == {"L": 100}

987 assert metadata["range"] == 100

988

989 # Pattern 3: Per-component tuple scaling (CAM models)

990 def function_c(

991 XYZ: Annotated[ArrayLike, 100],

992 ) -> Annotated[tuple, (100, 100, 360, 100, 100, 100, 400)]: # type: ignore

993 """Test tuple return scaling."""

994

995 metadata = get_domain_range_scale_metadata(function_c)

996 assert metadata["domain"] == {"XYZ": 100}

997 assert metadata["range"] == (100, 100, 360, 100, 100, 100, 400)

998

999 # Multiple domain parameters

1000 def function_d(

1001 XYZ: Annotated[ArrayLike, 100],

1002 XYZ_w: Annotated[ArrayLike, 100],

1003 illuminant: ArrayLike = None, # type: ignore

1004 ) -> Annotated[NDArrayFloat, 100]: # type: ignore

1005 """Test multiple domain parameters."""

1006

1007 metadata = get_domain_range_scale_metadata(function_d)

1008 assert metadata["domain"] == {"XYZ": 100, "XYZ_w": 100}

1009 assert metadata["range"] == 100

1010

1011 # No annotations (backward compatibility)

1012 def function_e(XYZ: Any, illuminant: Any = None) -> None:

1013 """Test backward compatibility."""

1014

1015 metadata = get_domain_range_scale_metadata(function_e)

1016 assert metadata["domain"] == {}

1017 assert metadata["range"] is None

1018

1019 # Only domain scaling, no range

1020 def function_f(

1021 XYZ: Annotated[ArrayLike, 1],

1022 ) -> NDArrayFloat: # type: ignore

1023 """Test domain-only scaling."""

1024

1025 metadata = get_domain_range_scale_metadata(function_f)

1026 assert metadata["domain"] == {"XYZ": 1}

1027 assert metadata["range"] is None

1028

1029 # Only range scaling, no domain

1030 def function_g(

1031 XYZ: ArrayLike,

1032 ) -> Annotated[NDArrayFloat, 100]: # type: ignore

1033 """Test range-only scaling."""

1034

1035 metadata = get_domain_range_scale_metadata(function_g)

1036 assert metadata["domain"] == {}

1037 assert metadata["range"] == 100

1038

1039 # Type aliases: Domain1/Range1

1040 def function_h(XYZ: Domain1, XYZ_w: Domain1 = 1) -> Range1: # type: ignore

1041 """Test Domain1/Range1 type aliases."""

1042

1043 metadata = get_domain_range_scale_metadata(function_h)

1044 assert metadata["domain"] == {"XYZ": 1, "XYZ_w": 1}

1045

1046 # Union with Annotated types

1047 def function_i(

1048 value: Annotated[int, 100] | Annotated[float, 200],

1049 ) -> NDArrayFloat: # type: ignore

1050 """Test Union with Annotated members."""

1051

1052 metadata = get_domain_range_scale_metadata(function_i)

1053 assert metadata["domain"] == {"value": 100}

1054 assert metadata["range"] is None

1055

1056 # Type aliases: Domain100/Range100

1057 def function_j(Y: Domain100, Y_n: Domain100 = 100) -> Range100: # type: ignore

1058 """Test Domain100/Range100 type aliases."""

1059

1060 metadata = get_domain_range_scale_metadata(function_j)

1061 assert metadata["domain"] == {"Y": 100, "Y_n": 100}

1062 assert metadata["range"] == 100

1063

1064 # Type aliases: Domain10/Range10

1065 def function_k(L: Domain10) -> Range10: # type: ignore

1066 """Test Domain10/Range10 type aliases."""

1067

1068 metadata = get_domain_range_scale_metadata(function_k)

1069 assert metadata["domain"] == {"L": 10}

1070 assert metadata["range"] == 10

1071

1072 # Type aliases: Domain360/Range360

1073 def function_l(hue: Domain360) -> Range360: # type: ignore

1074 """Test Domain360/Range360 type aliases."""

1075

1076 metadata = get_domain_range_scale_metadata(function_l)

1077 assert metadata["domain"] == {"hue": 360}

1078 assert metadata["range"] == 360

1079

1080 # Type aliases: Domain100_100_360/Range100_100_360

1081 def function_m(Lab: Domain100_100_360) -> Range100_100_360: # type: ignore

1082 """Test Domain100_100_360/Range100_100_360 type aliases."""

1083

1084 metadata = get_domain_range_scale_metadata(function_m)

1085 assert metadata["domain"] == {"Lab": (100, 100, 360)}

1086 assert metadata["range"] == (100, 100, 360)

1087

1088 # Mixed: type aliases and explicit Annotated

1089 def function_n(

1090 XYZ: Domain1, L: Domain100, custom: Annotated[ArrayLike, 50]

1091 ) -> Range100: # type: ignore

1092 """Test mixed type aliases and Annotated."""

1093

1094 metadata = get_domain_range_scale_metadata(function_n)

1095 assert metadata["domain"] == {"XYZ": 1, "L": 100, "custom": 50}

1096 assert metadata["range"] == 100

1097

1098 # functools.partial with type aliases

1099 def function_o(

1100 XYZ: Domain1,

1101 colourspace: str,

1102 illuminant: ArrayLike | None = None,

1103 ) -> Range1: # type: ignore

1104 """Test function for partial wrapping."""

1105

1106 partial_func = partial(function_o, colourspace="sRGB")

1107 metadata = get_domain_range_scale_metadata(partial_func)

1108 assert metadata["domain"] == {"XYZ": 1}

1109 assert metadata["range"] == 1

1110

1111 # functools.partial with explicit Annotated

1112 def function_p(

1113 Lab: Annotated[ArrayLike, 100],

1114 illuminant: ArrayLike | None = None,

1115 method: str = "CIE 1976",

1116 ) -> Annotated[NDArrayFloat, 100]: # type: ignore

1117 """Test function for partial wrapping with Annotated."""

1118

1119 partial_func2 = partial(function_p, method="CIE 2000")

1120 metadata = get_domain_range_scale_metadata(partial_func2)

1121 assert metadata["domain"] == {"Lab": 100}

1122 assert metadata["range"] == 100

1123

1124 # Test string annotation with unevaluable scale (triggers exception handler)

1125 # This simulates what happens with `from __future__ import annotations`

1126 # when the annotation contains an undefined variable

1127 def function_q(x: Any) -> Any:

1128 """Test function with mock string annotation."""

1129

1130 # Manually set __annotations__ to simulate string annotation with undefined var

1131 function_q.__annotations__ = {

1132 "x": "Annotated[float, undefined_variable]",

1133 "return": "Annotated[float, another_undefined]",

1134 }

1135

1136 metadata = get_domain_range_scale_metadata(function_q)

1137 # The eval will fail, so it falls back to the string itself

1138 assert metadata["domain"] == {"x": "undefined_variable"}

1139 assert metadata["range"] == "another_undefined"

1140

1141

1142 class TestToDomain1(unittest.TestCase):

1143 """

1144 Define :func:`colour.utilities.common.to_domain_1` definition unit

1145 tests methods.

1146 """

1147

1148 def test_to_domain_1(self) -> None:

1149 """Test :func:`colour.utilities.common.to_domain_1` definition."""

1150

1151 with domain_range_scale("Reference"):

1152 assert to_domain_1(1) == 1

1153

1154 with domain_range_scale("1"):

1155 assert to_domain_1(1) == 1

1156

1157 with domain_range_scale("100"):

1158 assert to_domain_1(1) == 0.01

1159

1160 with domain_range_scale("100"):

1161 assert to_domain_1(1, np.pi) == 1 / np.pi

1162

1163 with domain_range_scale("100"):

1164 assert to_domain_1(1, dtype=np.float16).dtype == np.float16

1165

1166

1167 class TestToDomain10(unittest.TestCase):

1168 """

1169 Define :func:`colour.utilities.common.to_domain_10` definition unit

1170 tests methods.

1171 """

1172

1173 def test_to_domain_10(self) -> None:

1174 """Test :func:`colour.utilities.common.to_domain_10` definition."""

1175

1176 with domain_range_scale("Reference"):

1177 assert to_domain_10(1) == 1

1178

1179 with domain_range_scale("1"):

1180 assert to_domain_10(1) == 10

1181

1182 with domain_range_scale("100"):

1183 assert to_domain_10(1) == 0.1

1184

1185 with domain_range_scale("100"):

1186 assert to_domain_10(1, np.pi) == 1 / np.pi

1187

1188 with domain_range_scale("100"):

1189 assert to_domain_10(1, dtype=np.float16).dtype == np.float16

1190

1191

1192 class TestToDomain100(unittest.TestCase):

1193 """

1194 Define :func:`colour.utilities.common.to_domain_100` definition unit

1195 tests methods.

1196 """

1197

1198 def test_to_domain_100(self) -> None:

1199 """Test :func:`colour.utilities.common.to_domain_100` definition."""

1200

1201 with domain_range_scale("Reference"):

1202 assert to_domain_100(1) == 1

1203

1204 with domain_range_scale("1"):

1205 assert to_domain_100(1) == 100

1206

1207 with domain_range_scale("100"):

1208 assert to_domain_100(1) == 1

1209

1210 with domain_range_scale("1"):

1211 assert to_domain_100(1, np.pi) == np.pi

1212

1213 with domain_range_scale("100"):

1214 assert to_domain_100(1, dtype=np.float16).dtype == np.float16

1215

1216

1217 class TestToDomainDegrees(unittest.TestCase):

1218 """

1219 Define :func:`colour.utilities.common.to_domain_degrees` definition unit

1220 tests methods.

1221 """

1222

1223 def test_to_domain_degrees(self) -> None:

1224 """Test :func:`colour.utilities.common.to_domain_degrees` definition."""

1225

1226 with domain_range_scale("Reference"):

1227 assert to_domain_degrees(1) == 1

1228

1229 with domain_range_scale("1"):

1230 assert to_domain_degrees(1) == 360

1231

1232 with domain_range_scale("100"):

1233 assert to_domain_degrees(1) == 3.6

1234

1235 with domain_range_scale("100"):

1236 assert to_domain_degrees(1, np.pi) == np.pi / 100

1237

1238 with domain_range_scale("100"):

1239 assert to_domain_degrees(1, dtype=np.float16).dtype == np.float16

1240

1241

1242 class TestToDomainInt(unittest.TestCase):

1243 """

1244 Define :func:`colour.utilities.common.to_domain_int` definition unit

1245 tests methods.

1246 """

1247

1248 def test_to_domain_int(self) -> None:

1249 """Test :func:`colour.utilities.common.to_domain_int` definition."""

1250

1251 with domain_range_scale("Reference"):

1252 assert to_domain_int(1) == 1

1253

1254 with domain_range_scale("1"):

1255 assert to_domain_int(1) == 255

1256

1257 with domain_range_scale("100"):

1258 assert to_domain_int(1) == 2.55

1259

1260 with domain_range_scale("100"):

1261 assert to_domain_int(1, 10) == 10.23

1262

1263 with domain_range_scale("100"):

1264 assert to_domain_int(1, dtype=np.float16).dtype == np.float16

1265

1266

1267 class TestFromRange1(unittest.TestCase):

1268 """

1269 Define :func:`colour.utilities.common.from_range_1` definition unit

1270 tests methods.

1271 """

1272

1273 def test_from_range_1(self) -> None:

1274 """Test :func:`colour.utilities.common.from_range_1` definition."""

1275

1276 with domain_range_scale("Reference"):

1277 assert from_range_1(1) == 1

1278

1279 with domain_range_scale("1"):

1280 assert from_range_1(1) == 1

1281

1282 with domain_range_scale("100"):

1283 assert from_range_1(1) == 100

1284

1285 with domain_range_scale("100"):

1286 assert from_range_1(1, np.pi) == 1 * np.pi

1287

1288

1289 class TestFromRange10(unittest.TestCase):

1290 """

1291 Define :func:`colour.utilities.common.from_range_10` definition unit

1292 tests methods.

1293 """

1294

1295 def test_from_range_10(self) -> None:

1296 """Test :func:`colour.utilities.common.from_range_10` definition."""

1297

1298 with domain_range_scale("Reference"):

1299 assert from_range_10(1) == 1

1300

1301 with domain_range_scale("1"):

1302 assert from_range_10(1) == 0.1

1303

1304 with domain_range_scale("100"):

1305 assert from_range_10(1) == 10

1306

1307 with domain_range_scale("100"):

1308 assert from_range_10(1, np.pi) == 1 * np.pi

1309

1310

1311 class TestFromRange100(unittest.TestCase):

1312 """

1313 Define :func:`colour.utilities.common.from_range_100` definition unit

1314 tests methods.

1315 """

1316

1317 def test_from_range_100(self) -> None:

1318 """Test :func:`colour.utilities.common.from_range_100` definition."""

1319

1320 with domain_range_scale("Reference"):

1321 assert from_range_100(1) == 1

1322

1323 with domain_range_scale("1"):

1324 assert from_range_100(1) == 0.01

1325

1326 with domain_range_scale("100"):

1327 assert from_range_100(1) == 1

1328

1329 with domain_range_scale("1"):

1330 assert from_range_100(1, np.pi) == 1 / np.pi

1331

1332

1333 class TestFromRangeDegrees(unittest.TestCase):

1334 """

1335 Define :func:`colour.utilities.common.from_range_degrees` definition unit

1336 tests methods.

1337 """

1338

1339 def test_from_range_degrees(self) -> None:

1340 """Test :func:`colour.utilities.common.from_range_degrees` definition."""

1341

1342 with domain_range_scale("Reference"):

1343 assert from_range_degrees(1) == 1

1344

1345 with domain_range_scale("1"):

1346 assert from_range_degrees(1) == 1 / 360

1347

1348 with domain_range_scale("100"):

1349 assert from_range_degrees(1) == 1 / 3.6

1350

1351 with domain_range_scale("100"):

1352 assert from_range_degrees(1, np.pi) == 1 / (np.pi / 100)

1353

1354

1355 class TestFromRangeInt(unittest.TestCase):

1356 """

1357 Define :func:`colour.utilities.common.from_range_int` definition unit

1358 tests methods.

1359 """

1360

1361 def test_from_range_int(self) -> None:

1362 """Test :func:`colour.utilities.common.from_range_int` definition."""

1363

1364 with domain_range_scale("Reference"):

1365 assert from_range_int(1) == 1

1366

1367 with domain_range_scale("1"):

1368 assert from_range_int(1) == 1 / 255

1369

1370 with domain_range_scale("100"):

1371 assert from_range_int(1) == 1 / 2.55

1372

1373 with domain_range_scale("100"):

1374 assert from_range_int(1, 10) == 1 / (1023 / 100)

1375

1376 with domain_range_scale("100"):

1377 assert from_range_int(1, dtype=np.float16).dtype == np.float16

1378

1379

1380 class TestIsNdarrayCopyEnabled(unittest.TestCase):

1381 """

1382 Define :func:`colour.utilities.array.is_ndarray_copy_enabled` definition

1383 unit tests methods.

1384 """

1385

1386 def test_is_ndarray_copy_enabled(self) -> None:

1387 """

1388 Test :func:`colour.utilities.array.is_ndarray_copy_enabled` definition.

1389 """

1390

1391 with ndarray_copy_enable(True):

1392 assert is_ndarray_copy_enabled()

1393

1394 with ndarray_copy_enable(False):

1395 assert not is_ndarray_copy_enabled()

1396

1397

1398 class TestSetNdarrayCopyEnabled(unittest.TestCase):

1399 """

1400 Define :func:`colour.utilities.array.set_ndarray_copy_enable` definition

1401 unit tests methods.

1402 """

1403

1404 def test_set_ndarray_copy_enable(self) -> None:

1405 """

1406 Test :func:`colour.utilities.array.set_ndarray_copy_enable` definition.

1407 """

1408

1409 with ndarray_copy_enable(is_ndarray_copy_enabled()):

1410 set_ndarray_copy_enable(True)

1411 assert is_ndarray_copy_enabled()

1412

1413 with ndarray_copy_enable(is_ndarray_copy_enabled()):

1414 set_ndarray_copy_enable(False)

1415 assert not is_ndarray_copy_enabled()

1416

1417

1418 class TestNdarrayCopyEnable(unittest.TestCase):

1419 """

1420 Define :func:`colour.utilities.array.ndarray_copy_enable` definition unit

1421 tests methods.

1422 """

1423

1424 def test_ndarray_copy_enable(self) -> None:

1425 """

1426 Test :func:`colour.utilities.array.ndarray_copy_enable` definition.

1427 """

1428

1429 with ndarray_copy_enable(True):

1430 assert is_ndarray_copy_enabled()

1431

1432 with ndarray_copy_enable(False):

1433 assert not is_ndarray_copy_enabled()

1434

1435 @ndarray_copy_enable(True)

1436 def fn_a() -> None:

1437 """:func:`ndarray_copy_enable` unit tests :func:`fn_a` definition."""

1438

1439 assert is_ndarray_copy_enabled()

1440

1441 fn_a()

1442

1443 @ndarray_copy_enable(False)

1444 def fn_b() -> None:

1445 """:func:`ndarray_copy_enable` unit tests :func:`fn_b` definition."""

1446

1447 assert not is_ndarray_copy_enabled()

1448

1449 fn_b()

1450

1451

1452 class TestNdarrayCopy(unittest.TestCase):

1453 """

1454 Define :func:`colour.utilities.array.ndarray_copy` definition unit

1455 tests methods.

1456 """

1457

1458 def test_ndarray_copy(self) -> None:

1459 """Test :func:`colour.utilities.array.ndarray_copy` definition."""

1460

1461 a = np.linspace(0, 1, 10)

1462 with ndarray_copy_enable(True):

1463 assert id(ndarray_copy(a)) != id(a)

1464

1465 with ndarray_copy_enable(False):

1466 assert id(ndarray_copy(a)) == id(a)

1467

1468

1469 class TestClosestIndexes(unittest.TestCase):

1470 """

1471 Define :func:`colour.utilities.array.closest_indexes` definition unit

1472 tests methods.

1473 """

1474

1475 def test_closest_indexes(self) -> None:

1476 """Test :func:`colour.utilities.array.closest_indexes` definition."""

1477

1478 a = np.array(

1479 [

1480 24.31357115,

1481 63.62396289,

1482 55.71528816,

1483 62.70988028,

1484 46.84480573,

1485 25.40026416,

1486 ]

1487 )

1488

1489 assert closest_indexes(a, 63.05) == 3

1490

1491 assert closest_indexes(a, 51.15) == 4

1492

1493 assert closest_indexes(a, 24.90) == 5

1494

1495 np.testing.assert_array_equal(

1496 closest_indexes(a, np.array([63.05, 51.15, 24.90])),

1497 np.array([3, 4, 5]),

1498 )

1499

1500

1501 class TestClosest(unittest.TestCase):

1502 """

1503 Define :func:`colour.utilities.array.closest` definition unit tests

1504 methods.

1505 """

1506

1507 def test_closest(self) -> None:

1508 """Test :func:`colour.utilities.array.closest` definition."""

1509

1510 a = np.array(

1511 [

1512 24.31357115,

1513 63.62396289,

1514 55.71528816,

1515 62.70988028,

1516 46.84480573,

1517 25.40026416,

1518 ]

1519 )

1520

1521 assert closest(a, 63.05) == 62.70988028

1522

1523 assert closest(a, 51.15) == 46.84480573

1524

1525 assert closest(a, 24.90) == 25.40026416

1526

1527 np.testing.assert_allclose(

1528 closest(a, np.array([63.05, 51.15, 24.90])),

1529 np.array([62.70988028, 46.84480573, 25.40026416]),

1530 atol=TOLERANCE_ABSOLUTE_TESTS,

1531 )

1532

1533

1534 class TestInterval(unittest.TestCase):

1535 """

1536 Define :func:`colour.utilities.array.interval` definition unit tests

1537 methods.

1538 """

1539

1540 def test_interval(self) -> None:

1541 """Test :func:`colour.utilities.array.interval` definition."""

1542

1543 np.testing.assert_array_equal(interval(range(0, 10, 2)), np.array([2]))

1544

1545 np.testing.assert_array_equal(

1546 interval(range(0, 10, 2), False), np.array([2, 2, 2, 2])

1547 )

1548

1549 np.testing.assert_allclose(

1550 interval([1, 2, 3, 4, 6, 6.5]),

1551 np.array([0.5, 1.0, 2.0]),

1552 atol=TOLERANCE_ABSOLUTE_TESTS,

1553 )

1554

1555 np.testing.assert_allclose(

1556 interval([1, 2, 3, 4, 6, 6.5], False),

1557 np.array([1.0, 1.0, 1.0, 2.0, 0.5]),

1558 atol=TOLERANCE_ABSOLUTE_TESTS,

1559 )

1560

1561

1562 class TestIsUniform(unittest.TestCase):

1563 """

1564 Define :func:`colour.utilities.array.is_uniform` definition unit tests

1565 methods.

1566 """

1567

1568 def test_is_uniform(self) -> None:

1569 """Test :func:`colour.utilities.array.is_uniform` definition."""

1570

1571 assert is_uniform(range(0, 10, 2))

1572

1573 assert not is_uniform([1, 2, 3, 4, 6])

1574

1575

1576 class TestInArray(unittest.TestCase):

1577 """

1578 Define :func:`colour.utilities.array.in_array` definition unit tests

1579 methods.

1580 """

1581

1582 def test_in_array(self) -> None:

1583 """Test :func:`colour.utilities.array.in_array` definition."""

1584

1585 assert np.array_equal(

1586 in_array(np.array([0.50, 0.60]), np.linspace(0, 10, 101)),

1587 np.array([True, True]),

1588 )

1589

1590 assert not np.array_equal(

1591 in_array(np.array([0.50, 0.61]), np.linspace(0, 10, 101)),

1592 np.array([True, True]),

1593 )

1594

1595 assert np.array_equal(

1596 in_array(np.array([[0.50], [0.60]]), np.linspace(0, 10, 101)),

1597 np.array([[True], [True]]),

1598 )

1599

1600 def test_n_dimensional_in_array(self) -> None:

1601 """

1602 Test :func:`colour.utilities.array.in_array` definition n-dimensional

1603 support.

1604 """

1605

1606 np.testing.assert_array_equal(

1607 in_array(np.array([0.50, 0.60]), np.linspace(0, 10, 101)).shape,

1608 np.array([2]),

1609 )

1610

1611 np.testing.assert_array_equal(

1612 in_array(np.array([[0.50, 0.60]]), np.linspace(0, 10, 101)).shape,

1613 np.array([1, 2]),

1614 )

1615

1616 np.testing.assert_array_equal(

1617 in_array(np.array([[0.50], [0.60]]), np.linspace(0, 10, 101)).shape,

1618 np.array([2, 1]),

1619 )

1620

1621

1622 class TestTstack(unittest.TestCase):

1623 """

1624 Define :func:`colour.utilities.array.tstack` definition unit tests

1625 methods.

1626 """

1627

1628 def test_tstack(self) -> None:

1629 """Test :func:`colour.utilities.array.tstack` definition."""

1630

1631 a = 0

1632 np.testing.assert_array_equal(tstack([a, a, a]), np.array([0, 0, 0]))

1633

1634 a = np.arange(0, 6)

1635 np.testing.assert_array_equal(

1636 tstack([a, a, a]),

1637 np.array(

1638 [

1639 [0, 0, 0],

1640 [1, 1, 1],

1641 [2, 2, 2],

1642 [3, 3, 3],

1643 [4, 4, 4],

1644 [5, 5, 5],

1645 ]

1646 ),

1647 )

1648

1649 a = np.reshape(a, (1, 6))

1650 np.testing.assert_array_equal(

1651 tstack([a, a, a]),

1652 np.array(

1653 [

1654 [

1655 [0, 0, 0],

1656 [1, 1, 1],

1657 [2, 2, 2],

1658 [3, 3, 3],

1659 [4, 4, 4],

1660 [5, 5, 5],

1661 ]

1662 ]

1663 ),

1664 )

1665

1666 a = np.reshape(a, (1, 2, 3))

1667 np.testing.assert_array_equal(

1668 tstack([a, a, a]),

1669 np.array(

1670 [

1671 [

1672 [[0, 0, 0], [1, 1, 1], [2, 2, 2]],

1673 [[3, 3, 3], [4, 4, 4], [5, 5, 5]],

1674 ]

1675 ]

1676 ),

1677 )

1678

1679 # Ensuring that contiguity is maintained.

1680 a = np.array([0, 1, 2], dtype=DTYPE_FLOAT_DEFAULT)

1681 b = tstack([a, a, a])

1682 assert b.flags.contiguous

1683

1684 # Ensuring that independence is maintained.

1685 a *= 2

1686 np.testing.assert_array_equal(

1687 b,

1688 np.array(

1689 [

1690 [0, 0, 0],

1691 [1, 1, 1],

1692 [2, 2, 2],

1693 ],

1694 ),

1695 )

1696

1697 a = np.array([0, 1, 2], dtype=DTYPE_FLOAT_DEFAULT)

1698 b = tstack([a, a, a])

1699

1700 b[1] *= 2

1701 np.testing.assert_array_equal(

1702 a,

1703 np.array([0, 1, 2]),

1704 )

1705

1706

1707 class TestTsplit(unittest.TestCase):

1708 """

1709 Define :func:`colour.utilities.array.tsplit` definition unit tests

1710 methods.

1711 """

1712

1713 def test_tsplit(self) -> None:

1714 """Test :func:`colour.utilities.array.tsplit` definition."""

1715

1716 a = np.array([0, 0, 0])

1717 np.testing.assert_array_equal(tsplit(a), np.array([0, 0, 0]))

1718 a = np.array(

1719 [

1720 [0, 0, 0],

1721 [1, 1, 1],

1722 [2, 2, 2],

1723 [3, 3, 3],

1724 [4, 4, 4],

1725 [5, 5, 5],

1726 ]

1727 )

1728 np.testing.assert_array_equal(

1729 tsplit(a),

1730 np.array(

1731 [

1732 [0, 1, 2, 3, 4, 5],

1733 [0, 1, 2, 3, 4, 5],

1734 [0, 1, 2, 3, 4, 5],

1735 ]

1736 ),

1737 )

1738

1739 a = np.array(

1740 [

1741 [

1742 [0, 0, 0],

1743 [1, 1, 1],

1744 [2, 2, 2],

1745 [3, 3, 3],

1746 [4, 4, 4],

1747 [5, 5, 5],

1748 ],

1749 ]

1750 )

1751 np.testing.assert_array_equal(

1752 tsplit(a),

1753 np.array(

1754 [

1755 [[0, 1, 2, 3, 4, 5]],

1756 [[0, 1, 2, 3, 4, 5]],

1757 [[0, 1, 2, 3, 4, 5]],

1758 ]

1759 ),

1760 )

1761

1762 a = np.array(

1763 [

1764 [

1765 [[0, 0, 0], [1, 1, 1], [2, 2, 2]],

1766 [[3, 3, 3], [4, 4, 4], [5, 5, 5]],

1767 ]

1768 ]

1769 )

1770 np.testing.assert_array_equal(

1771 tsplit(a),

1772 np.array(

1773 [

1774 [[[0, 1, 2], [3, 4, 5]]],

1775 [[[0, 1, 2], [3, 4, 5]]],

1776 [[[0, 1, 2], [3, 4, 5]]],

1777 ]

1778 ),

1779 )

1780

1781 # Ensuring that contiguity is maintained.

1782 a = np.array(

1783 [

1784 [0, 0, 0],

1785 [1, 1, 1],

1786 [2, 2, 2],

1787 ],

1788 dtype=DTYPE_FLOAT_DEFAULT,

1789 )

1790 b = tsplit(a)

1791 assert b.flags.contiguous

1792

1793 # Ensuring that independence is maintained.

1794 a *= 2

1795 np.testing.assert_array_equal(

1796 b,

1797 np.array(

1798 [

1799 [0, 1, 2],

1800 [0, 1, 2],

1801 [0, 1, 2],

1802 ]

1803 ),

1804 )

1805

1806 a = np.array(

1807 [

1808 [0, 0, 0],

1809 [1, 1, 1],

1810 [2, 2, 2],

1811 ],

1812 dtype=DTYPE_FLOAT_DEFAULT,

1813 )

1814 b = tsplit(a)

1815

1816 b[1] *= 2

1817 np.testing.assert_array_equal(

1818 a,

1819 np.array(

1820 [

1821 [0, 0, 0],

1822 [1, 1, 1],

1823 [2, 2, 2],

1824 ]

1825 ),

1826 )

1827

1828

1829 class TestRowAsDiagonal(unittest.TestCase):

1830 """

1831 Define :func:`colour.utilities.array.row_as_diagonal` definition unit

1832 tests methods.

1833 """

1834

1835 def test_row_as_diagonal(self) -> None:

1836 """Test :func:`colour.utilities.array.row_as_diagonal` definition."""

1837

1838 np.testing.assert_allclose(

1839 row_as_diagonal(

1840 np.array(

1841 [

1842 [0.25891593, 0.07299478, 0.36586996],

1843 [0.30851087, 0.37131459, 0.16274825],

1844 [0.71061831, 0.67718718, 0.09562581],

1845 [0.71588836, 0.76772047, 0.15476079],

1846 [0.92985142, 0.22263399, 0.88027331],

1847 ]

1848 )

1849 ),

1850 np.array(

1851 [

1852 [

1853 [0.25891593, 0.00000000, 0.00000000],

1854 [0.00000000, 0.07299478, 0.00000000],

1855 [0.00000000, 0.00000000, 0.36586996],

1856 ],

1857 [

1858 [0.30851087, 0.00000000, 0.00000000],

1859 [0.00000000, 0.37131459, 0.00000000],

1860 [0.00000000, 0.00000000, 0.16274825],

1861 ],

1862 [

1863 [0.71061831, 0.00000000, 0.00000000],

1864 [0.00000000, 0.67718718, 0.00000000],

1865 [0.00000000, 0.00000000, 0.09562581],

1866 ],

1867 [

1868 [0.71588836, 0.00000000, 0.00000000],

1869 [0.00000000, 0.76772047, 0.00000000],

1870 [0.00000000, 0.00000000, 0.15476079],

1871 ],

1872 [

1873 [0.92985142, 0.00000000, 0.00000000],

1874 [0.00000000, 0.22263399, 0.00000000],

1875 [0.00000000, 0.00000000, 0.88027331],

1876 ],

1877 ]

1878 ),

1879 atol=TOLERANCE_ABSOLUTE_TESTS,

1880 )

1881

1882

1883 class TestOrient(unittest.TestCase):

1884 """

1885 Define :func:`colour.utilities.array.orient` definition unit tests

1886 methods.

1887 """

1888

1889 def test_orient(self) -> None:

1890 """Test :func:`colour.utilities.array.orient` definition."""

1891

1892 a = np.tile(np.arange(5), (5, 1))

1893

1894 np.testing.assert_array_equal(

1895 orient(a, "Flip"),

1896 np.array(

1897 [

1898 [4, 3, 2, 1, 0],

1899 [4, 3, 2, 1, 0],

1900 [4, 3, 2, 1, 0],

1901 [4, 3, 2, 1, 0],

1902 [4, 3, 2, 1, 0],

1903 ]

1904 ),

1905 )

1906

1907 np.testing.assert_array_equal(

1908 orient(a, "Flop"),

1909 np.array(

1910 [

1911 [0, 1, 2, 3, 4],

1912 [0, 1, 2, 3, 4],

1913 [0, 1, 2, 3, 4],

1914 [0, 1, 2, 3, 4],

1915 [0, 1, 2, 3, 4],

1916 ]

1917 ),

1918 )

1919

1920 np.testing.assert_array_equal(

1921 orient(a, "90 CW"),

1922 np.array(

1923 [

1924 [0, 0, 0, 0, 0],

1925 [1, 1, 1, 1, 1],

1926 [2, 2, 2, 2, 2],

1927 [3, 3, 3, 3, 3],

1928 [4, 4, 4, 4, 4],

1929 ]

1930 ),

1931 )

1932

1933 np.testing.assert_array_equal(

1934 orient(a, "90 CCW"),

1935 np.array(

1936 [

1937 [4, 4, 4, 4, 4],

1938 [3, 3, 3, 3, 3],

1939 [2, 2, 2, 2, 2],

1940 [1, 1, 1, 1, 1],

1941 [0, 0, 0, 0, 0],

1942 ]

1943 ),

1944 )

1945

1946 np.testing.assert_array_equal(

1947 orient(a, "180"),

1948 np.array(

1949 [

1950 [4, 3, 2, 1, 0],

1951 [4, 3, 2, 1, 0],

1952 [4, 3, 2, 1, 0],

1953 [4, 3, 2, 1, 0],

1954 [4, 3, 2, 1, 0],

1955 ]

1956 ),

1957 )

1958

1959 np.testing.assert_array_equal(orient(a), a)

1960

1961

1962 class TestCentroid(unittest.TestCase):

1963 """

1964 Define :func:`colour.utilities.array.centroid` definition unit tests

1965 methods.

1966 """

1967

1968 def test_centroid(self) -> None:

1969 """Test :func:`colour.utilities.array.centroid` definition."""

1970

1971 a = np.arange(5)

1972 np.testing.assert_array_equal(centroid(a), np.array([3]))

1973

1974 a = np.tile(a, (5, 1))

1975 np.testing.assert_array_equal(centroid(a), np.array([2, 3]))

1976

1977 a = np.tile(np.linspace(0, 1, 10), (10, 1))

1978 np.testing.assert_array_equal(centroid(a), np.array([4, 6]))

1979

1980 a = tstack([a, a, a])

1981 np.testing.assert_array_equal(centroid(a), np.array([4, 6, 1]))

1982

1983

1984 class TestFillNan(unittest.TestCase):

1985 """

1986 Define :func:`colour.utilities.array.fill_nan` definition unit tests

1987 methods.

1988 """

1989

1990 def test_fill_nan(self) -> None:

1991 """Test :func:`colour.utilities.array.fill_nan` definition."""

1992

1993 a = np.array([0.1, 0.2, np.nan, 0.4, 0.5])

1994 np.testing.assert_allclose(

1995 fill_nan(a),

1996 np.array([0.1, 0.2, 0.3, 0.4, 0.5]),

1997 atol=TOLERANCE_ABSOLUTE_TESTS,

1998 )

1999

2000 np.testing.assert_allclose(

2001 fill_nan(a, method="Constant", default=8.0),

2002 np.array([0.1, 0.2, 8.0, 0.4, 0.5]),

2003 atol=TOLERANCE_ABSOLUTE_TESTS,

2004 )

2005

2006

2007 class TestHasNanOnly(unittest.TestCase):

2008 """

2009 Define :func:`colour.utilities.array.has_only_nan` definition unit tests

2010 methods.

2011 """

2012

2013 def test_has_only_nan(self) -> None:

2014 """Test :func:`colour.utilities.array.has_only_nan` definition."""

2015

2016 assert has_only_nan(None) # pyright: ignore

2017

2018 assert has_only_nan([None, None]) # pyright: ignore

2019

2020 assert not has_only_nan([True, None]) # pyright: ignore

2021

2022 assert not has_only_nan([0.1, np.nan, 0.3])

2023

2024

2025 class TestNdarrayWrite(unittest.TestCase):

2026 """

2027 Define :func:`colour.utilities.array.ndarray_write` definition unit tests

2028 methods.

2029 """

2030

2031 def test_ndarray_write(self) -> None:

2032 """Test :func:`colour.utilities.array.ndarray_write` definition."""

2033

2034 a = np.linspace(0, 1, 10)

2035 a.setflags(write=False)

2036

2037 with pytest.raises(ValueError):

2038 a += 1

2039

2040 with ndarray_write(a):

2041 a += 1

2042

2043

2044 class TestZeros(unittest.TestCase):

2045 """

2046 Define :func:`colour.utilities.array.zeros` definition unit tests

2047 methods.

2048 """

2049

2050 def test_zeros(self) -> None:

2051 """Test :func:`colour.utilities.array.zeros` definition."""

2052

2053 np.testing.assert_equal(zeros(3), np.zeros(3))

2054

2055

2056 class TestOnes(unittest.TestCase):

2057 """

2058 Define :func:`colour.utilities.array.ones` definition unit tests

2059 methods.

2060 """

2061

2062 def test_ones(self) -> None:

2063 """Test :func:`colour.utilities.array.ones` definition."""

2064

2065 np.testing.assert_equal(ones(3), np.ones(3))

2066

2067

2068 class TestFull(unittest.TestCase):

2069 """

2070 Define :func:`colour.utilities.array.full` definition unit tests

2071 methods.

2072 """

2073

2074 def test_full(self) -> None:

2075 """Test :func:`colour.utilities.array.full` definition."""

2076

2077 np.testing.assert_equal(full(3, 0.5), np.full(3, 0.5))

2078

2079

2080 class TestIndexAlongLastAxis(unittest.TestCase):

2081 """

2082 Define :func:`colour.utilities.array.index_along_last_axis` definition

2083 unit tests methods.

2084 """

2085

2086 def test_index_along_last_axis(self) -> None:

2087 """Test :func:`colour.utilities.array.index_along_last_axis` definition."""

2088 a = np.array(

2089 [

2090 [

2091 [

2092 [0.51090627, 0.86191718, 0.8687926],

2093 [0.82738158, 0.80587656, 0.28285687],

2094 ],

2095 [

2096 [0.84085977, 0.03851814, 0.06057988],

2097 [0.94659267, 0.79308353, 0.30870888],

2098 ],

2099 ],

2100 [

2101 [

2102 [0.50758436, 0.24066455, 0.20199051],

2103 [0.4507304, 0.84189245, 0.81160878],

2104 ],

2105 [

2106 [0.75421871, 0.88187494, 0.01612045],

2107 [0.38777511, 0.58905552, 0.32970469],

2108 ],

2109 ],

2110 [

2111 [

2112 [0.99285824, 0.738076, 0.0716432],

2113 [0.35847844, 0.0367514, 0.18586322],

2114 ],

2115 [

2116 [0.72674561, 0.0822759, 0.9771182],

2117 [0.90644279, 0.09689787, 0.93483977],

2118 ],

2119 ],

2120 ]

2121 )

2122

2123 indexes = np.array([[[0, 1], [0, 1]], [[2, 1], [2, 1]], [[2, 1], [2, 0]]])

2124

2125 np.testing.assert_equal(

2126 index_along_last_axis(a, indexes),

2127 np.array(

2128 [

2129 [[0.51090627, 0.80587656], [0.84085977, 0.79308353]],

2130 [[0.20199051, 0.84189245], [0.01612045, 0.58905552]],

2131 [[0.0716432, 0.0367514], [0.9771182, 0.90644279]],

2132 ]

2133 ),

2134 )

2135

2136 def test_compare_with_argmin_argmax(self) -> None:

2137 """

2138 Test :func:`colour.utilities.array.index_along_last_axis` definition

2139 by comparison with :func:`argmin` and :func:`argmax`.

2140 """

2141

2142 a = np.random.random((2, 3, 4, 5, 6, 7))

2143

2144 np.testing.assert_equal(

2145 index_along_last_axis(a, np.argmin(a, axis=-1)), np.min(a, axis=-1)

2146 )

2147

2148 np.testing.assert_equal(

2149 index_along_last_axis(a, np.argmax(a, axis=-1)), np.max(a, axis=-1)

2150 )

2151

2152 def test_exceptions(self) -> None:

2153 """

2154 Test :func:`colour.utilities.array.index_along_last_axis` definition

2155 handling of invalid inputs.

2156 """

2157

2158 a = as_float_array([[11, 12], [21, 22]])

2159

2160 # Bad shape

2161 with pytest.raises(ValueError):

2162 indexes = np.array([0])

2163 index_along_last_axis(a, indexes)

2164

2165 # Indexes out of range

2166 with pytest.raises(IndexError):

2167 indexes = np.array([123, 456])

2168 index_along_last_axis(a, indexes)

2169

2170 # Non-int indexes

2171 with pytest.raises(IndexError):

2172 indexes = np.array([0.0, 0.0])

2173 index_along_last_axis(a, indexes)

2174

2175

2176 class TestFormatArrayAsRow(unittest.TestCase):

2177 """

2178 Define :func:`colour.utilities.array.format_array_as_row` definition unit

2179 tests methods.

2180 """

2181

2182 def test_format_array_as_row(self) -> None:

2183 """Test :func:`colour.utilities.array.format_array_as_row` definition."""

2184

2185 assert format_array_as_row([1.25, 2.5, 3.75]) == "1.2500000 2.5000000 3.7500000"

2186

2187 assert format_array_as_row([1.25, 2.5, 3.75], 3) == "1.250 2.500 3.750"

2188

2189 assert format_array_as_row([1.25, 2.5, 3.75], 3, ", ") == "1.250, 2.500, 3.750"