Skip to content

Commit 3b2d91d

Browse files
committed
Implement support for *AMF* components filtering by config variant.
Signed-off-by: Thomas Mansencal <[email protected]>
1 parent e4895b0 commit 3b2d91d

File tree

7 files changed

+201
-25
lines changed

7 files changed

+201
-25
lines changed

docs/opencolorio_config_aces.utilities.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,5 @@ Common
4040
timestamp
4141
as_bool
4242
optional
43+
filter_any
44+
filter_all

opencolorio_config_aces/config/cg/generate/config.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
)
5454
from opencolorio_config_aces.utilities import (
5555
attest,
56+
filter_all,
57+
filter_any,
5658
optional,
5759
timestamp,
5860
validate_method,
@@ -816,6 +818,7 @@ def generate_config_cg(
816818
describe=describe,
817819
scheme=scheme,
818820
analytical=False,
821+
additional_filterers=additional_filterers,
819822
additional_data=True,
820823
)
821824

@@ -969,24 +972,6 @@ def transform_filterer(transform: dict[str, Any]) -> bool:
969972

970973
return False
971974

972-
def filter_any(
973-
array: list[dict[str, Any]], filterers: list[Callable[[dict[str, Any]], bool]]
974-
) -> list[dict[str, Any]]:
975-
"""Filter array elements passing any of the filterers."""
976-
977-
filtered = [a for a in array if any(filterer(a) for filterer in filterers)]
978-
979-
return filtered
980-
981-
def filter_all(
982-
array: list[dict[str, Any]], filterers: list[Callable[[dict[str, Any]], bool]]
983-
) -> list[dict[str, Any]]:
984-
"""Filter array elements passing all of the filterers."""
985-
986-
filtered = [a for a in array if all(filterer(a) for filterer in filterers)]
987-
988-
return filtered
989-
990975
# "Colorspaces" Filtering
991976
# =======================
992977
any_colorspace_filterers = [
@@ -1161,7 +1146,8 @@ def remove_existing_named_transform(name: str) -> None:
11611146
)
11621147
) is not None:
11631148
filtered_amf_components = filter_amf_components(
1164-
amf_components, aces_transform_id.aces_transform_id
1149+
amf_components,
1150+
aces_transform_id.aces_transform_id,
11651151
)
11661152

11671153
kwargs.update(

opencolorio_config_aces/config/generation/configuration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ def extended_fields(self) -> dict[str, str]:
120120
"view_transform_filterers": [lambda x: "D60 in" not in x["name"]],
121121
"shared_view_filterers": [lambda x: "D60 in" not in x["view_transform"]],
122122
"view_filterers": [lambda x: "D60 in" not in x["view"]],
123+
"amf_component_display_filterers": [
124+
lambda x: "-D60_" not in x["transform_id"]
125+
],
123126
},
124127
},
125128
"D60 Views": {
@@ -140,6 +143,7 @@ def extended_fields(self) -> dict[str, str]:
140143
or x["view"] == "Un-tone-mapped"
141144
or x["view"] == "Raw"
142145
],
146+
"amf_component_display_filterers": [lambda x: "-D60_" in x["transform_id"]],
143147
},
144148
},
145149
"All Views": {"any": {}, "all": {}},

opencolorio_config_aces/config/reference/discover/classify.py

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232

3333
from opencolorio_config_aces.utilities import (
3434
attest,
35+
filter_all,
36+
filter_any,
3537
message_box,
3638
optional,
3739
paths_common_ancestor,
@@ -1511,8 +1513,11 @@ def generate_amf_components(
15111513

15121514

15131515
def filter_amf_components(
1514-
amf_components: dict[str, list[str]], aces_transform_id: str
1515-
) -> list[str] | None:
1516+
amf_components: dict[str, list[str]],
1517+
aces_transform_id: str,
1518+
filter_any_filterers: list[Callable[[dict[str, Any]], bool]] | None = None,
1519+
filter_all_filterers: list[Callable[[dict[str, Any]], bool]] | None = None,
1520+
) -> list[str]:
15161521
"""
15171522
Filter the *ACES* *AMF* components for specified *ACEStransformID*.
15181523
@@ -1522,16 +1527,79 @@ def filter_amf_components(
15221527
*ACES* *AMF* components to filter.
15231528
aces_transform_id : str
15241529
*ACEStransformID* to filter the *ACES* *AMF* components with.
1530+
filter_any_filterers : list[Callable[[dict[str, Any]], bool]] | None, optional
1531+
List of filter functions for OR logic filtering. Each function should
1532+
accept a dictionary with *transform_id* key and return True if the
1533+
element passes the filter condition.
1534+
filter_all_filterers : list[Callable[[dict[str, Any]], bool]] | None, optional
1535+
List of filter functions for AND logic filtering. Each function should
1536+
accept a dictionary with *transform_id* key and return True if the
1537+
element passes the filter condition.
15251538
15261539
Returns
15271540
-------
1528-
:class:`list`
1541+
list
15291542
Filtered *ACES* *AMF* components.
1543+
1544+
Examples
1545+
--------
1546+
>>> amf_components = {
1547+
... "DISPLAY - CIE-XYZ-D65_to_sRGB - MIRROR NEGS": [
1548+
... 'urn:ampas:aces:transformId:v2.0:Output.Academy.\
1549+
Rec709-D65_100nit_in_Rec709-D65_sRGB-Piecewise.a2.v1',
1550+
... 'urn:ampas:aces:transformId:v2.0:InvOutput.Academy.\
1551+
Rec709-D65_100nit_in_Rec709-D65_sRGB-Piecewise.a2.v1',
1552+
... 'urn:ampas:aces:transformId:v2.0:Output.Academy.\
1553+
Rec709-D60_100nit_in_Rec709-D65_sRGB-Piecewise.a2.v1',
1554+
... 'urn:ampas:aces:transformId:v2.0:InvOutput.Academy.\
1555+
Rec709-D60_100nit_in_Rec709-D65_sRGB-Piecewise.a2.v1'
1556+
... ]
1557+
... }
1558+
>>> filterers_all = [lambda x: "-D60_" not in x['transform_id']]
1559+
>>> filter_amf_components(
1560+
... amf_components,
1561+
... "DISPLAY - CIE-XYZ-D65_to_sRGB - MIRROR NEGS",
1562+
... filterers_all
1563+
... ) # doctest: +ELLIPSIS
1564+
['urn:ampas:aces:transformId:v2.0:Output.Academy.\
1565+
Rec709-D65_100nit_in_Rec709-D65_sRGB-Piecewise.a2.v1', \
1566+
'urn:ampas:aces:transformId:v2.0:InvOutput.Academy.\
1567+
Rec709-D65_100nit_in_Rec709-D65_sRGB-Piecewise.a2.v1']
1568+
>>> filterers_all = [lambda x: "-D60_" in x['transform_id']]
1569+
>>> filter_amf_components(
1570+
... amf_components,
1571+
... "DISPLAY - CIE-XYZ-D65_to_sRGB - MIRROR NEGS",
1572+
... filterers_all
1573+
... ) # doctest: +ELLIPSIS
1574+
['urn:ampas:aces:transformId:v2.0:Output.Academy.\
1575+
Rec709-D60_100nit_in_Rec709-D65_sRGB-Piecewise.a2.v1', \
1576+
'urn:ampas:aces:transformId:v2.0:InvOutput.Academy.\
1577+
Rec709-D60_100nit_in_Rec709-D65_sRGB-Piecewise.a2.v1']
15301578
"""
15311579

1532-
filtered_amf_components = list(amf_components.get(aces_transform_id, []))
1580+
filtered_amf_components = amf_components.get(aces_transform_id, [])
1581+
1582+
if filter_any_filterers or filter_all_filterers:
1583+
filtered_amf_components_dicts = [
1584+
{"transform_id": transform_id} for transform_id in filtered_amf_components
1585+
]
1586+
1587+
if filter_any_filterers:
1588+
filtered_amf_components_dicts = filter_any(
1589+
filtered_amf_components_dicts, filter_any_filterers
1590+
)
1591+
1592+
if filter_all_filterers:
1593+
filtered_amf_components_dicts = filter_all(
1594+
filtered_amf_components_dicts, filter_all_filterers
1595+
)
1596+
1597+
filtered_amf_components = [
1598+
filtered_amf_components_dict["transform_id"]
1599+
for filtered_amf_components_dict in filtered_amf_components_dicts
1600+
]
15331601

1534-
return filtered_amf_components if len(filtered_amf_components) else None
1602+
return filtered_amf_components
15351603

15361604

15371605
if __name__ == "__main__":

opencolorio_config_aces/config/reference/generate/config.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from opencolorio_config_aces.utilities import (
5656
as_bool,
5757
attest,
58+
optional,
5859
timestamp,
5960
validate_method,
6061
)
@@ -839,6 +840,8 @@ def generate_config_aces(
839840
config_mapping_file_path: Any = PATH_TRANSFORMS_MAPPING_FILE_REFERENCE,
840841
scheme: str = "Modern 1",
841842
analytical: bool = True,
843+
additional_filterers: dict[str, dict[str, list[Callable[[Any], bool]]]]
844+
| None = None,
842845
additional_data: bool = False,
843846
) -> Any:
844847
"""
@@ -876,6 +879,21 @@ def generate_config_aces(
876879
Whether to generate *OpenColorIO* transform families that analytically
877880
match the given *ACES* *CTL* transform, i.e., true to the *aces-dev*
878881
reference but not necessarily user-friendly.
882+
additional_filterers : dict, optional
883+
Additional filterers to further include or exclude components from the
884+
generated config.
885+
886+
.. code-block:: python
887+
888+
{
889+
"any": {},
890+
"all": {
891+
"amf_component_display_filterers": [
892+
lambda x: "D60 in" not in x["transform_id"]
893+
],
894+
},
895+
},
896+
879897
additional_data : bool, optional
880898
Whether to return additional data.
881899
@@ -892,6 +910,8 @@ def generate_config_aces(
892910
config_name_aces(build_configuration),
893911
)
894912

913+
additional_filterers = optional(additional_filterers, {"any": {}, "all": {}})
914+
895915
LOGGER.debug('Using %s "Builtin" transforms...', list(BUILTIN_TRANSFORMS.keys()))
896916

897917
ctl_transforms = unclassify_ctl_transforms(
@@ -1088,7 +1108,10 @@ def generate_config_aces(
10881108
display_style = transform_data["linked_display_colorspace_style"]
10891109

10901110
filtered_amf_components = filter_amf_components(
1091-
amf_components, display_style
1111+
amf_components,
1112+
display_style,
1113+
additional_filterers["any"].get("amf_component_display_filterers"),
1114+
additional_filterers["all"].get("amf_component_display_filterers"),
10921115
)
10931116

10941117
display = style_to_display_colorspace(

opencolorio_config_aces/utilities/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
timestamp,
2828
as_bool,
2929
optional,
30+
filter_any,
31+
filter_all,
3032
)
3133

3234
__all__ = [
@@ -55,4 +57,6 @@
5557
"timestamp",
5658
"as_bool",
5759
"optional",
60+
"filter_any",
61+
"filter_all",
5862
]

opencolorio_config_aces/utilities/common.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
"timestamp",
6161
"as_bool",
6262
"optional",
63+
"filter_any",
64+
"filter_all",
6365
]
6466

6567
LOGGER = logging.getLogger(__name__)
@@ -842,3 +844,90 @@ def optional(value: T | None, default: T) -> T:
842844
return default
843845
else:
844846
return value
847+
848+
849+
def filter_any(
850+
array: list[dict[str, Any]], filterers: list[Callable[[dict[str, Any]], bool]]
851+
) -> list[dict[str, Any]]:
852+
"""
853+
Filter array elements that pass any of the provided filter functions.
854+
855+
This function applies multiple filter functions to each element in the array
856+
and returns elements that satisfy at least one filter condition (OR logic).
857+
858+
Parameters
859+
----------
860+
array : list[dict[str, Any]]
861+
The list of dictionaries to filter.
862+
filterers : list[Callable[[dict[str, Any]], bool]]
863+
A list of callable filter functions. Each function should accept a
864+
dictionary and return True if the element passes the filter condition,
865+
False otherwise.
866+
867+
Returns
868+
-------
869+
list[dict[str, Any]]
870+
A new list containing only the elements that pass at least one of the
871+
filter conditions.
872+
873+
Examples
874+
--------
875+
>>> transforms = [
876+
... {'name': 'ACEScc', 'type': 'CSC', 'family': 'aces'},
877+
... {'name': 'ACEScg', 'type': 'CSC', 'family': 'aces'},
878+
... {'name': 'sRGB', 'type': 'Output', 'family': 'display'},
879+
... ]
880+
>>> is_csc = lambda x: x['type'] == 'CSC'
881+
>>> is_output = lambda x: x['type'] == 'Output'
882+
>>> filter_any(transforms, [is_csc, is_output])
883+
[{'name': 'ACEScc', 'type': 'CSC', 'family': 'aces'}, \
884+
{'name': 'ACEScg', 'type': 'CSC', 'family': 'aces'}, \
885+
{'name': 'sRGB', 'type': 'Output', 'family': 'display'}]
886+
"""
887+
888+
filtered = [a for a in array if any(filterer(a) for filterer in filterers)]
889+
890+
return filtered
891+
892+
893+
def filter_all(
894+
array: list[dict[str, Any]], filterers: list[Callable[[dict[str, Any]], bool]]
895+
) -> list[dict[str, Any]]:
896+
"""
897+
Filter array elements that pass all of the provided filter functions.
898+
899+
This function applies multiple filter functions to each element in the array
900+
and returns only elements that satisfy every filter condition (AND logic).
901+
902+
Parameters
903+
----------
904+
array : list[dict[str, Any]]
905+
The list of dictionaries to filter.
906+
filterers : list[Callable[[dict[str, Any]], bool]]
907+
A list of callable filter functions. Each function should accept a
908+
dictionary and return True if the element passes the filter condition,
909+
False otherwise.
910+
911+
Returns
912+
-------
913+
list[dict[str, Any]]
914+
A new list containing only the elements that pass all of the filter
915+
conditions.
916+
917+
Examples
918+
--------
919+
>>> transforms = [
920+
... {'name': 'ACEScc', 'type': 'CSC', 'family': 'aces'},
921+
... {'name': 'Rec709', 'type': 'Output', 'family': 'display'},
922+
... {'name': 'sRGB', 'type': 'Output', 'family': 'display'},
923+
... ]
924+
>>> is_output = lambda x: x['type'] == 'Output'
925+
>>> is_display = lambda x: x['family'] == 'display'
926+
>>> filter_all(transforms, [is_output, is_display])
927+
[{'name': 'Rec709', 'type': 'Output', 'family': 'display'}, \
928+
{'name': 'sRGB', 'type': 'Output', 'family': 'display'}]
929+
"""
930+
931+
filtered = [a for a in array if all(filterer(a) for filterer in filterers)]
932+
933+
return filtered

0 commit comments

Comments
 (0)