Skip to content

Commit 8d207e4

Browse files
authored
Merge pull request #99 from ampas/feature/calculate_camera_npm_and_primaries
Feature/calculate camera npm and primaries
2 parents 0bd3462 + 169f1bc commit 8d207e4

File tree

4 files changed

+137
-0
lines changed

4 files changed

+137
-0
lines changed

aces/idt/core/common.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"calculate_clipped_exposures",
9292
"create_samples_macbeth_image",
9393
"interpolate_nan_values",
94+
"calculate_camera_npm_and_primaries_wp",
9495
]
9596

9697
LOGGER = logging.getLogger(__name__)
@@ -968,3 +969,58 @@ def interpolate_nan_values(array: np.array) -> np.array:
968969
final_array = np.where(np.isnan(array), interpolated_df.to_numpy(), array)
969970

970971
return final_array
972+
973+
974+
def calculate_camera_npm_and_primaries_wp(
975+
input_matrix: np.array,
976+
target_white_point: str = "D65",
977+
cat: str = "Bradford",
978+
observer: str = "CIE 1931 2 Degree Standard Observer",
979+
) -> Tuple[np.array, np.array, np.array]:
980+
"""
981+
Calculate the camera's npm matrix (RGB to XYZ), it also returns the cameras
982+
primaries, and whitepoint.
983+
984+
Parameters
985+
----------
986+
input_matrix: np.array
987+
The camera's calculated or known RGB to ACES2065-1 matrix
988+
989+
target_white_point: str
990+
The target whitepoint to calculate the camera's npm matrix for
991+
cat: str
992+
The chromatic adaptation transform to use
993+
994+
observer: str
995+
The observer to use for the chromatic adaptation
996+
997+
Returns
998+
-------
999+
Tuple[np.array, np.array, np.array]
1000+
The camera's npm matrix, the camera's primaries, and the camera's whitepoint
1001+
1002+
"""
1003+
1004+
camera_rgb_to_xyz = RGB_COLOURSPACE_ACES2065_1.matrix_RGB_to_XYZ @ np.array(
1005+
input_matrix
1006+
)
1007+
1008+
# Get the xy chromaticity coordinates of the source and target whitepoints
1009+
source_whitepoint_xy = colour.CCS_ILLUMINANTS[observer]["D60"]
1010+
target_whitepoint_xy = colour.CCS_ILLUMINANTS[observer][target_white_point]
1011+
1012+
# Convert chromaticity coordinates to XYZ tristimulus values
1013+
source_whitepoint = colour.xy_to_XYZ(source_whitepoint_xy)
1014+
target_whitepoint = colour.xy_to_XYZ(target_whitepoint_xy)
1015+
1016+
# Compute the chromatic adaptation matrix using the given CAT
1017+
cat_matrix = colour.adaptation.matrix_chromatic_adaptation_VonKries(
1018+
source_whitepoint, target_whitepoint, transform=cat
1019+
)
1020+
1021+
# Apply the chromatic adaptation matrix to the camera's RGB to XYZ matrix
1022+
computed_camera_npm = cat_matrix @ camera_rgb_to_xyz
1023+
1024+
# Compute the camera's whitepoint and primaries
1025+
primaries, whitepoint = colour.primaries_whitepoint(computed_camera_npm)
1026+
return computed_camera_npm, primaries, whitepoint

aces/idt/generators/base_generator.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ def __init__(self, project_settings):
117117
self._M = None
118118
self._RGB_w = None
119119
self._k = None
120+
self._npm = None
121+
self._primaries = None
122+
self._whitepoint = None
120123

121124
self._image_grey_card_sampling = None
122125
self._image_colour_checker_segmentation = None
@@ -283,6 +286,45 @@ def k(self) -> NDArrayFloat | None:
283286

284287
return self._k
285288

289+
@property
290+
def npm(self) -> NDArrayFloat | None:
291+
"""
292+
Getter property for the camera's RGB to XYZ matrix.
293+
294+
Returns
295+
-------
296+
:class:`NDArray` or None
297+
Camera's RGB to XYZ matrix.
298+
"""
299+
300+
return self._npm
301+
302+
@property
303+
def primaries(self) -> NDArrayFloat | None:
304+
"""
305+
Getter property for the camera's primaries.
306+
307+
Returns
308+
-------
309+
:class:`NDArray` or None
310+
Camera's primaries.
311+
"""
312+
313+
return self._primaries
314+
315+
@property
316+
def whitepoint(self) -> NDArrayFloat | None:
317+
"""
318+
Getter property for the camera's whitepoint.
319+
320+
Returns
321+
-------
322+
:class:`NDArray` or None
323+
Camera's whitepoint.
324+
"""
325+
326+
return self._whitepoint
327+
286328
@property
287329
def samples_analysis(self) -> NDArrayFloat | None:
288330
"""

aces/idt/generators/prosumer_camera.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,17 @@ def optimise(self) -> Tuple[NDArrayFloat]:
496496

497497
self._M = finaliser_function(self._M)
498498

499+
# Calculate and store the camera npm, the primaries and the whitepoint
500+
(
501+
self._npm,
502+
self._primaries,
503+
self._whitepoint,
504+
) = common.calculate_camera_npm_and_primaries_wp(
505+
self._M,
506+
target_white_point=self.project_settings.illuminant,
507+
cat=self.project_settings.cat,
508+
)
509+
499510
return self._M, self._RGB_w, self._k
500511

501512
def png_measured_camera_samples(self) -> str | None:

tests/test_common.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,34 @@ def test_generate_reference_colour_checker(self):
4545
result = common.generate_reference_colour_checker()
4646
self.assertTrue(np.allclose(expected, result, atol=1e-20))
4747

48+
def test_calculate_camera_npm_and_primaries_wp(self):
49+
"""Test generating a camera npm from an RGB to AP0 matrix"""
50+
input_matrix = [
51+
[0.785043, 0.083844, 0.131118],
52+
[0.023172, 1.087892, -0.111055],
53+
[-0.073769, -0.314639, 1.388537],
54+
]
55+
56+
npm, primaries, wp = common.calculate_camera_npm_and_primaries_wp(input_matrix)
57+
58+
expected_npm = [
59+
[0.7353579, 0.06867992, 0.14646275],
60+
[0.28673187, 0.84296573, -0.1297009],
61+
[-0.07965591, -0.34720223, 1.5155319],
62+
]
63+
64+
expected_primaries = [
65+
[0.7802753326723714, 0.3042461485259778],
66+
[0.1216772523298739, 1.4934459215643787],
67+
[0.09558399073520502, -0.08464493023427101],
68+
]
69+
70+
expected_wp = [0.31274994, 0.32903601]
71+
72+
self.assertTrue(np.allclose(expected_npm, npm, atol=1e-6))
73+
self.assertTrue(np.allclose(expected_primaries, primaries, atol=1e-6))
74+
self.assertTrue(np.allclose(expected_wp, wp, atol=1e-6))
75+
4876

4977
class TestExposureClippingMask(TestIDTBase):
5078
"""

0 commit comments

Comments
 (0)