Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions aces/idt/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ def format_array(a: NDArrayFloat) -> str:

return "\n\t\t".join(formatted_lines)

LOGGER.info(f"flatten_clf: {flatten_clf}")
LOGGER.info(f"include_white_balance: {include_white_balance}")
LOGGER.info(f"include_exposure_factor: {include_exposure_factor}")

if not flatten_clf:
if include_white_balance:
et_RGB_w = ET.SubElement(
Expand Down Expand Up @@ -507,6 +511,8 @@ def format_array(a: NDArrayFloat) -> str:

clf_matrix = matrix

LOGGER.debug(f"not_flatten_clf_matrix: {clf_matrix}")

else:
if not include_exposure_factor:
k_factor = 1.0
Expand All @@ -516,6 +522,8 @@ def format_array(a: NDArrayFloat) -> str:
if include_white_balance:
clf_matrix = np.matmul(np.diag(multipliers), clf_matrix)

LOGGER.debug(f"flatten_clf_matrix: {clf_matrix}")

et_M = ET.SubElement(root, "Matrix", inBitDepth="32f", outBitDepth="32f")
et_description = ET.SubElement(et_M, "Description")
et_description.text = "*Input Device Transform* (IDT) matrix *B*."
Expand Down Expand Up @@ -956,6 +964,7 @@ def calculate_camera_npm_and_primaries_wp(
target_white_point: str = "D65",
chromatic_adaptation_transform: LiteralChromaticAdaptationTransform
| str = "Bradford",
custom_illuminant_sd: SpectralDistribution | None = None,
) -> Tuple[np.array, np.array, np.array]:
"""
Calculate the camera's normalised primary (NPM) matrix, i.e., RGB to
Expand All @@ -969,6 +978,9 @@ def calculate_camera_npm_and_primaries_wp(
Target whitepoint to calculate the camera's NPM matrix for.
chromatic_adaptation_transform
*Chromatic adaptation* transform.
custom_illuminant_sd
Custom illuminant spectral distribution. This is required if
`target_white_point` is *Custom*.

Returns
-------
Expand All @@ -982,10 +994,27 @@ def calculate_camera_npm_and_primaries_wp(

observer = "CIE 1931 2 Degree Standard Observer"
source_whitepoint_xy = colour.CCS_ILLUMINANTS[observer]["D60"]
target_whitepoint_xy = colour.CCS_ILLUMINANTS[observer][target_white_point]

source_whitepoint_XYZ = colour.xy_to_XYZ(source_whitepoint_xy)
target_whitepoint_XYZ = colour.xy_to_XYZ(target_whitepoint_xy)

LOGGER.debug(f"target_white_point: {target_white_point}")
if target_white_point == "Custom":
if custom_illuminant_sd is None:
raise ValueError(
"A custom illuminant spectral distribution must be provided "
"when 'target_white_point' is 'Custom'."
)
# Convert from XYZ to xy and back to XYZ to normalize the Y component. Although directly dividing by Y would suffice,
# the native function from the colour library is used to minimize potential additional errors.
# Using sd_to_XYZ_integration instead of the default ASTM E308 method of sd_to_XYZ yields more scientifically accurate source XYZ values.
target_whitepoint_XYZ = colour.xy_to_XYZ(
colour.XYZ_to_xy(colour.colorimetry.sd_to_XYZ_integration(custom_illuminant_sd)))
target_whitepoint_xy_debug = colour.XYZ_to_xy(target_whitepoint_XYZ)
LOGGER.debug(f"Custom target_whitepoint_xy_debug: {target_whitepoint_xy_debug}")
LOGGER.debug(f"Custom target_whitepoint_XYZ: {target_whitepoint_XYZ}")
else:
target_whitepoint_xy = colour.CCS_ILLUMINANTS[observer][target_white_point]
LOGGER.debug(f"target_whitepoint_xy: {target_whitepoint_xy}")
target_whitepoint_XYZ = colour.xy_to_XYZ(target_whitepoint_xy)

cat_matrix = colour.adaptation.matrix_chromatic_adaptation_VonKries(
source_whitepoint_XYZ,
Expand Down
40 changes: 39 additions & 1 deletion aces/idt/framework/project_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from colour import SpectralDistribution
from colour.hints import Dict, NDArrayFloat

from colour.utilities import as_float_array, multiline_str
Expand Down Expand Up @@ -149,6 +150,7 @@ def __init__(self, **kwargs: Dict) -> None:
IDTProjectSettings.lut_smoothing.metadata.name,
IDTProjectSettings.lut_smoothing.metadata.default_value,
)
self._custom_illuminant = None

self._data = IDTProjectSettings.data.metadata.default_value

Expand Down Expand Up @@ -628,6 +630,32 @@ def include_exposure_factor_in_clf(self) -> bool:

return self._include_exposure_factor_in_clf

@property
def custom_illuminant(self) -> SpectralDistribution | None:
"""
Getter property for the custom illuminant spectral distribution.

Returns
-------
:class:`colour.SpectralDistribution` or :py:data:`None`
Custom illuminant spectral distribution.
"""

return self._custom_illuminant

@custom_illuminant.setter
def custom_illuminant(self, value: SpectralDistribution):
"""
Setter property for the custom illuminant spectral distribution.

Parameters
----------
value : colour.SpectralDistribution
Custom illuminant spectral distribution.
"""

self._custom_illuminant = value

def get_reference_colour_checker_samples(self) -> NDArrayFloat:
"""
Return the reference colour checker samples.
Expand All @@ -638,9 +666,19 @@ def get_reference_colour_checker_samples(self) -> NDArrayFloat:
Reference colour checker samples.
"""

if self.illuminant == "Custom":
if self.custom_illuminant is None:
raise ValueError(
"Custom illuminant data not provided for 'Custom' "
"illuminant name."
)
illuminant_sd = self.custom_illuminant
else:
illuminant_sd = get_sds_illuminant(self.illuminant)

return generate_reference_colour_checker(
get_sds_colour_checker(self.reference_colour_checker),
get_sds_illuminant(self.illuminant),
illuminant_sd,
)

def get_optimization_factory(self) -> callable:
Expand Down
1 change: 1 addition & 0 deletions aces/idt/generators/log_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ def optimise(self) -> Tuple[NDArrayFloat]:
self._M,
target_white_point=self.project_settings.illuminant,
chromatic_adaptation_transform=self.project_settings.cat,
**({"custom_illuminant_sd": self.project_settings.custom_illuminant} if self.project_settings.illuminant == "Custom" else {}),
)

return self._M, self._RGB_w, self._k
Expand Down
115 changes: 112 additions & 3 deletions apps/idt_calculator_camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,66 @@ def _uid(id_) -> str:
delay=DELAY_TOOLTIP_DEFAULT,
target=_uid("ev-range-input"),
),
InputGroup(
[
InputGroupText("Flatten CLF"),
Select(
id=_uid("flatten-clf-select"),
options=[
{"label": "Yes", "value": "True"},
{"label": "No", "value": "False"},
],
value="False",
),
],
className="mb-1",
),
Tooltip(
"Whether to flatten the CLF into a single "
"1D Lut & 1 3x3 Matrix.",
delay=DELAY_TOOLTIP_DEFAULT,
target=_uid("flatten-clf-select"),
),
InputGroup(
[
InputGroupText("Include White Balance in CLF"),
Select(
id=_uid("include-white-balance-in-clf-select"),
options=[
{"label": "Yes", "value": "True"},
{"label": "No", "value": "False"},
],
value="True",
),
],
className="mb-1",
),
Tooltip(
"Whether to include the white balance "
"multipliers in the CLF.",
delay=DELAY_TOOLTIP_DEFAULT,
target=_uid("include-white-balance-in-clf-select"),
),
InputGroup(
[
InputGroupText("Include Exposure Factor in CLF"),
Select(
id=_uid("include-exposure-factor-in-clf-select"),
options=[
{"label": "Yes", "value": "True"},
{"label": "No", "value": "False"},
],
value="True",
),
],
className="mb-1",
),
Tooltip(
'Whether to include the exposure factor "k" '
"in the CLF.",
delay=DELAY_TOOLTIP_DEFAULT,
target=_uid("include-exposure-factor-in-clf-select"),
),
],
id=_uid("advanced-options-collapse"),
className="mb-1",
Expand Down Expand Up @@ -1150,6 +1210,9 @@ def toggle_modal(n_clicks, is_open):
State(_uid("grey-card-reflectance"), "value"),
State(_uid("lut-size-select"), "value"),
State(_uid("lut-smoothing-input-number"), "value"),
State(_uid("flatten-clf-select"), "value"),
State(_uid("include-white-balance-in-clf-select"), "value"),
State(_uid("include-exposure-factor-in-clf-select"), "value"),
],
prevent_initial_call=True,
)
Expand Down Expand Up @@ -1179,6 +1242,9 @@ def compute_idt_camera(
grey_card_reflectance,
LUT_size,
LUT_smoothing,
flatten_clf,
include_white_balance_in_clf,
include_exposure_factor_in_clf,
):
"""
Compute the *Input Device Transform* (IDT) for a camera.
Expand Down Expand Up @@ -1238,6 +1304,12 @@ def compute_idt_camera(
LUT_smoothing : integer
Standard deviation of the gaussian convolution kernel used for
smoothing.
flatten_clf : str
Whether to flatten the CLF into a single 1D Lut & 1 3x3 Matrix.
include_white_balance_in_clf : str
Whether to include the white balance multipliers in the CLF.
include_exposure_factor_in_clf : str
Whether to include the exposure factor "k" in the CLF.

Returns
-------
Expand All @@ -1257,7 +1329,10 @@ def compute_idt_camera(
'\tEV Range : "%s"\n'
'\tGrey Card Reflectance : "%s"\n'
'\tLUT Size : "%s"\n'
'\tLUT Smoothing : "%s"\n',
'\tLUT Smoothing : "%s"\n'
'\tFlatten CLF : "%s"\n'
'\tInclude White Balance in CLF : "%s"\n'
'\tInclude Exposure Factor in CLF : "%s"\n',
generator_name,
RGB_display_colourspace,
illuminant_name,
Expand All @@ -1270,6 +1345,9 @@ def compute_idt_camera(
grey_card_reflectance,
LUT_size,
LUT_smoothing,
flatten_clf,
include_white_balance_in_clf,
include_exposure_factor_in_clf,
)

aces_transform_id = str(aces_transform_id)
Expand All @@ -1284,6 +1362,8 @@ def compute_idt_camera(
debayering_settings = str(debayering_settings)
encoding_colourspace = str(encoding_colourspace or "")
encoding_transfer_function = str(encoding_transfer_function or "")
LUT_size = int(LUT_size)
LUT_smoothing = int(LUT_smoothing)

# Validation: Check if the inputs are valid
is_valid, errors = IDTProjectSettings.validate_core_requirements(
Expand Down Expand Up @@ -1359,7 +1439,27 @@ def compute_idt_camera(
encoding_colourspace=encoding_colourspace,
encoding_transfer_function=encoding_transfer_function,
illuminant=illuminant_name,
rgb_display_colourspace=RGB_display_colourspace,
cat=chromatic_adaptation_transform,
optimisation_space=optimisation_space,
illuminant_interpolator=illuminant_interpolator,
decoding_method=decoding_method,
ev_range=[float(value) for value in EV_range.split(" ") if value],
grey_card_reference=[
float(value) for value in grey_card_reflectance.split(" ") if value
],
lut_size=LUT_size,
lut_smoothing=LUT_smoothing,
flatten_clf=(lambda v: True if v == "True" else False)(flatten_clf),
include_white_balance_in_clf=(lambda v: True if v == "True" else False)(
include_white_balance_in_clf),
include_exposure_factor_in_clf=(lambda v: True if v == "True" else False)(
include_exposure_factor_in_clf),
)

if illuminant_name == "Custom":
project_settings.custom_illuminant = illuminant

_IDT_GENERATOR_APPLICATION = IDTGeneratorApplication(
generator_name, project_settings
)
Expand All @@ -1372,16 +1472,27 @@ def compute_idt_camera(
_IDT_GENERATOR_APPLICATION.extract(_PATH_UPLOADED_IDT_ARCHIVE)
os.remove(_PATH_UPLOADED_IDT_ARCHIVE)
_IDT_GENERATOR_APPLICATION.generator.sample()
colour_checker_segmentation = (
_IDT_GENERATOR_APPLICATION.generator.png_colour_checker_segmentation()
)
grey_card_sampling = (
_IDT_GENERATOR_APPLICATION.generator.png_grey_card_sampling()
)

_CACHE_DATA_ARCHIVE_TO_SAMPLES[_HASH_IDT_ARCHIVE] = (
_IDT_GENERATOR_APPLICATION.project_settings.data,
_IDT_GENERATOR_APPLICATION.generator.samples_analysis,
_IDT_GENERATOR_APPLICATION.generator.baseline_exposure,
colour_checker_segmentation,
grey_card_sampling,
)
else:
(
_IDT_GENERATOR_APPLICATION.project_settings.data,
_IDT_GENERATOR_APPLICATION.generator._samples_analysis, # noqa: SLF001
_IDT_GENERATOR_APPLICATION.generator._baseline_exposure, # noqa: SLF001
colour_checker_segmentation,
grey_card_sampling,
) = _CACHE_DATA_ARCHIVE_TO_SAMPLES[_HASH_IDT_ARCHIVE]

generator = _IDT_GENERATOR_APPLICATION.generator
Expand Down Expand Up @@ -1481,7 +1592,6 @@ def RGB_working_to_RGB_display(RGB):
]

# Segmentation
colour_checker_segmentation = generator.png_colour_checker_segmentation()
if colour_checker_segmentation is not None:
components += [
H3("Segmentation", style={"textAlign": "center"}),
Expand All @@ -1490,7 +1600,6 @@ def RGB_working_to_RGB_display(RGB):
style={"width": "100%"},
),
]
grey_card_sampling = generator.png_grey_card_sampling()
if grey_card_sampling is not None:
components += [
Img(
Expand Down