Source code for lablib.operators.color
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Optional, Union
import PyOpenColorIO as OCIO
def get_direction(direction: Union[str, int]) -> int:
"""Get the direction for OCIO FileTransform.
Attributes:
direction (Union[str, int]): The direction.
Returns:
int: The direction.
"""
if direction == "inverse":
return OCIO.TransformDirection.TRANSFORM_DIR_INVERSE
return OCIO.TransformDirection.TRANSFORM_DIR_FORWARD
def get_interpolation(interpolation: str) -> int:
if interpolation == "linear":
return OCIO.Interpolation.INTERP_LINEAR
elif interpolation == "best":
return OCIO.Interpolation.INTERP_BEST
elif interpolation == "nearest":
return OCIO.Interpolation.INTERP_NEAREST
elif interpolation == "tetrahedral":
return OCIO.Interpolation.INTERP_TETRAHEDRAL
elif interpolation == "cubic":
return OCIO.Interpolation.INTERP_CUBIC
return OCIO.Interpolation.INTERP_DEFAULT
[docs]
@dataclass
class OCIOFileTransform:
"""Class for handling OCIO FileTransform effects.
Note:
Reads Foundry Hiero Timeline soft effect node class.
Attributes:
file (str): Path to the LUT file.
cccid (str): Path to the cccid file.
direction (int): The direction. Defaults to 0.
interpolation (str): The interpolation. Defaults to "linear".
"""
file: str = ""
cccid: str = ""
direction: int = 0
interpolation: str = "linear"
[docs]
def to_ocio_obj(self) -> List[OCIO.FileTransform]:
"""Converts the object to native OCIO object.
Returns:
List[OCIO.FileTransform]: The OCIO FileTransform object in a list.
"""
# define direction
direction = get_direction(self.direction)
# define interpolation
interpolation = get_interpolation(self.interpolation)
return [
OCIO.FileTransform(
src=Path(self.file).as_posix(),
cccId=self.cccid,
direction=direction,
interpolation=interpolation,
)
]
[docs]
@classmethod
def from_node_data(cls, data) -> "OCIOFileTransform":
"""Create :obj:`OCIOFileTransform` from node data.
Note:
Reads Foundry Hiero Timeline soft effect node data.
Would it be cool if we'd had a way to interface other DCC node data?
Would they even be so much different?
Args:
data (dict): The node data. List of expected but not required keys:
- file (str): Path to the LUT file.
- cccid (str): Path to the cccid file.
- direction (int): The direction. Defaults to 0.
- interpolation (str): The interpolation. Defaults to "linear".
Returns:
OCIOFileTransform: The OCIOFileTransform object.
"""
return cls(
file=data.get("file", ""),
cccid=data.get("cccid", ""),
direction=data.get("direction", 0),
interpolation=data.get("interpolation", "linear"),
)
[docs]
@dataclass
class OCIOColorSpace:
"""Foundry Hiero Timeline soft effect node class.
Attributes:
in_colorspace (str): The input colorspace.
Defaults to "ACES - ACEScg".
out_colorspace (str): The output colorspace.
Defaults to "ACES - ACEScg".
"""
in_colorspace: str = "ACES - ACEScg"
out_colorspace: str = "ACES - ACEScg"
[docs]
def to_ocio_obj(self) -> List[OCIO.ColorSpaceTransform]:
"""Returns native OCIO ColorSpaceTransform object.
Returns:
List[OCIO.ColorSpaceTransform]: The OCIO ColorSpaceTransform object
in a list.
"""
return [
OCIO.ColorSpaceTransform(
src=self.in_colorspace,
dst=self.out_colorspace,
)
]
[docs]
@classmethod
def from_node_data(cls, data) -> "OCIOColorSpace":
"""Create :obj:`OCIOColorSpace` from node data.
Arguments:
data (dict): The node data.
Returns:
OCIOColorSpace:
"""
return cls(
in_colorspace=data.get("in_colorspace", ""),
out_colorspace=data.get("out_colorspace", ""),
)
[docs]
@dataclass
class OCIOCDLTransform:
"""Foundry Hiero Timeline soft effect node class.
Note:
Since this node class combines two of OCIO classes (FileTransform and
CDLTransform), we will separate them here within
:obj:`OCIOCDLTransform.to_ocio_obj()`.
Attributes:
file (Optional[str]): Path to the LUT file.
direction (int): The direction. Defaults to 0.
cccid (str): The cccid. Defaults to "".
offset (List[float]): The offset. Defaults to [0.0, 0.0, 0.0].
power (List[float]): The power. Defaults to [1.0, 1.0, 1.0].
slope (List[float]): The slope. Defaults to [0.0, 0.0, 0.0].
saturation (float): The saturation. Defaults to 1.0.
interpolation (str): The interpolation. Defaults to "linear".
"""
file: Optional[str] = None
direction: int = 0
cccid: str = ""
offset: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0])
power: List[float] = field(default_factory=lambda: [1.0, 1.0, 1.0])
slope: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0])
saturation: float = 1.0
interpolation: str = "linear"
[docs]
def to_ocio_obj(self) -> List[Union[OCIO.FileTransform, OCIO.CDLTransform]]: # noqa: E501
"""Returns native OCIO FileTransform and CDLTransform object.
Returns:
List[Union[OCIO.FileTransform, OCIO.CDLTransform]]: The OCIO
CDLTransform and FileTransform object in a list.
If file is not provided, only CDLTransform will be returned.
"""
effects = []
# define direction
direction = get_direction(self.direction)
if self.file:
# define interpolation
interpolation = get_interpolation(self.interpolation)
lut_file = Path(self.file)
effects.append(
OCIO.FileTransform(
src=lut_file.as_posix(),
cccId=self.cccid,
interpolation=interpolation,
direction=direction,
)
)
effects.append(
OCIO.CDLTransform(
slope=self.slope,
offset=self.offset,
power=self.power,
sat=self.saturation,
direction=direction,
)
)
return effects
[docs]
@classmethod
def from_node_data(cls, data) -> "OCIOCDLTransform":
"""Create :obj:`OCIOCDLTransform` from node data.
Args:
data (dict): The node data. List of expected but not required keys:
- file (str): Path to the LUT file.
- direction (int): The direction.
Defaults to 0.
- cccid (str): The cccid.
Defaults to "".
- offset (List[float]): The offset.
Defaults to [0.0, 0.0, 0.0].
- power (List[float]): The power.
Defaults to [1.0, 1.0, 1.0].
- slope (List[float]): The slope.
Defaults to [0.0, 0.0, 0.0].
- saturation (float): The saturation.
Defaults to 1.0.
- interpolation (str): The interpolation.
Defaults to "linear".
Returns:
OCIOCDLTransform: The OCIOCDLTransform object.
"""
if data.get("file"):
return cls(
file=data.get("file", ""),
interpolation=data.get("interpolation", "linear"),
direction=data.get("direction", 0),
offset=data.get("offset", [0.0, 0.0, 0.0]),
power=data.get("power", [1.0, 1.0, 1.0]),
slope=data.get("slope", [0.0, 0.0, 0.0]),
saturation=data.get("saturation", 1.0),
cccid=data.get("cccid", ""),
)
else:
return cls(
direction=data.get("direction", 0),
offset=data.get("offset", [0.0, 0.0, 0.0]),
power=data.get("power", [1.0, 1.0, 1.0]),
slope=data.get("slope", [0.0, 0.0, 0.0]),
saturation=data.get("saturation", 1.0),
)
[docs]
@dataclass
class AYONOCIOLookProduct:
"""AYON ocioLook product dataclass
This class will hold all the necessary data for the ocioLook product, so
it can be covered into FileTransform and ColorSpaceTransform during
:obj:`AYONOCIOLookProduct.to_ocio_obj()` method.
.. admonition:: Example of input data
.. code-block::
{
"ocioLookItems": [
{
"name": "LUTfile",
"file: "path/to/lut.cube", # currently created via processor
"ext": "cube",
"input_colorspace": {
"colorspace": "Output - sRGB",
"name": "color_picking",
"type": "roles"
},
"output_colorspace": {
"colorspace": "ACES - ACEScc",
"name": "color_timing",
"type": "roles"
},
"direction": "forward",
"interpolation": "tetrahedral",
"config_data": {
"path": "path/to/config.ocio",
"template": "{BUILTIN_OCIO_ROOT}/aces_1.2/config.ocio",
"colorspace": "compositing_linear"
},
},
],
"ocioLookWorkingSpace": {
"colorspace": "ACES - ACEScg",
"name": "compositing_linear",
"type": "roles"
},
},
Attributes:
ocioLookItems (List[dict]): List of ocioLook items.
ocioLookWorkingSpace (dict): The working space.
"""
ocioLookItems: List[dict] = field(default_factory=list)
ocioLookWorkingSpace: dict = field(default_factory=dict)
[docs]
def to_ocio_obj(self) -> List[Union[OCIO.ColorSpaceTransform, OCIO.FileTransform]]: # noqa: E501
"""Converts to list of native OCIO objects.
Returns:
List[Union[OCIO.ColorSpaceTransform, OCIO.FileTransform]]: The OCIO
ColorSpaceTransform and FileTransform objects in a list. The
order of the objects is based on the order of the items in
:attr:`ocioLookItems`.
"""
look_working_colorspace = self.ocioLookWorkingSpace["colorspace"]
all_transformations = []
for index, item in enumerate(self.ocioLookItems):
filepath = item["file"]
lut_in_colorspace = item["input_colorspace"]["colorspace"]
lut_out_colorspace = item["output_colorspace"]["colorspace"]
direction = item["direction"]
interpolation = item["interpolation"]
if index == 0:
# set the first colorspace as the current working colorspace
current_working_colorspace = look_working_colorspace
if current_working_colorspace != lut_in_colorspace:
all_transformations.append(
OCIO.ColorSpaceTransform(
src=current_working_colorspace,
dst=lut_in_colorspace,
)
)
all_transformations.append(
OCIO.FileTransform(
src=Path(filepath).as_posix(),
interpolation=get_interpolation(interpolation),
direction=get_direction(direction),
)
)
current_working_colorspace = lut_out_colorspace
# making sure we are back in the working colorspace
if current_working_colorspace != look_working_colorspace:
all_transformations.append(
OCIO.ColorSpaceTransform(
src=current_working_colorspace,
dst=look_working_colorspace,
)
)
return all_transformations
[docs]
@classmethod
def from_node_data(cls, data) -> "AYONOCIOLookProduct":
"""Create :obj:`AYONOCIOLookProduct` from node data.
Arguments:
data (dict): The node data.
Returns:
AYONOCIOLookProduct: The AYONOCIOLookProduct object.
"""
return cls(
ocioLookItems=data.get("ocioLookItems", []),
ocioLookWorkingSpace=data.get("ocioLookWorkingSpace", {}),
)