NamedTransform Processor API Proposal
Currently the only way to create a processor from a named transform is to pair the named transform name with a color space name (config.getProcessor("cs", "nt") or config.getProcessor("nt", "cs")), which results in a processor that ignores the color space and applies the named transform in isolation, or if both arguments are named transforms, joins them.
What is unclear about this interface is that the position of the named transform in the getProcessor arguments determines the direction that it is applied (first argument == forward, second argument == inverse). If you specify two named transforms, the first will be applied in the forward direction, and the second in inverse. This behavior doesn't appear to be documented outside of comments in the function. We can definitely document it, but I think this interface will lead to confusion in the long run since directionality is usually explicitly specified elsewhere in the API where it would otherwise be unclear. This is not an issue with color spaces since they interact with a reference space and directionality is a product of their ordering, but since named transforms have no ties to other objects in an OCIO config, it's not immediately clear what direction would be used in these interactions.
Since named transforms have not been used much yet, I think it's a good opportunity to revise the interface (in a backwards compatible way). I propose that we add two new processor constructors to Config with these signatures:
Config::getProcessor(const ConstNamedTransformRcPtr & namedTransform, TransformDirection direction) const
Config::getProcessor(const char * & namedTransformName, TransformDirection direction) const
In addition, I think we could deprecate support of named transform names in the existing processor constructors. In 2.x it would continue to function as it has since 2.0, but we could consider a warning message or just clear documentation of it being deprecated. Then in 3.0 we could remove it.
To illustrate the current interface, see the following example code:
>>> import PyOpenColorIO as ocio
>>> config = ocio.Config.CreateRaw()
>>> ref = ocio.ColorSpace(name="ACES2065-1", isData=False)
>>> config.addColorSpace(ref)
>>> nt = ocio.NamedTransform("ST-2084 - Curve", forwardTransform=ocio.BuiltinTransform("CURVE - LINEAR_to_ST-2084"))
>>> config.addNamedTransform(nt)
# Inverse named transform
>>> proc = config.getProcessor("ACES2065-1", "ST-2084 - Curve")
>>> cpu_proc = proc.getDefaultCPUProcessor()
>>> cpu_proc.applyRGB([100, 10, 1.08])
[65504.0, 65504.0, 217.6101837158203]
# Forward named transform
>>> proc = config.getProcessor("ST-2084 - Curve", "ACES2065-1")
>>> cpu_proc = proc.getDefaultCPUProcessor()
>>> cpu_proc.applyRGB([100, 10, 1.08])
[1.0, 0.7518271207809448, 0.5158224105834961]
And the proposed revised interface:
# Inverse named transform
>>> proc = config.getProcessor(nt, direction=ocio.TRANSFORM_DIR_INVERSE)
>>> cpu_proc = proc.getDefaultCPUProcessor()
>>> cpu_proc.applyRGB([100, 10, 1.08])
[65504.0, 65504.0, 217.6101837158203]
# Forward named transform
>>> proc = config.getProcessor("ST-2084 - Curve")
>>> cpu_proc = proc.getDefaultCPUProcessor()
>>> cpu_proc.applyRGB([100, 10, 1.08])
[1.0, 0.7518271207809448, 0.5158224105834961]
Hi Michael, you are right, the documentation on this needs an upgrade! However, the code in your example is not how we intended the named transform to be used.
We should have documented it more clearly, but the way we intended to get a Processor from a NamedTransform was to call the getTransform method on the NamedTransform class and then call the getProcessor method that takes a Transform. (The same way one would apply a GroupTransform.)
Here is an example:
ConstNamedTransformRcPtr nt = config->getNamedTransform("my_named_transform");
ConstTransformRcPtr tf = nt->getTransform(OCIO::TRANSFORM_DIR_FORWARD);
OCIO::ConstProcessorRcPtr proc = config->getProcessor(tf));
We didn't add another getProcessor method just for NamedTransform since it doesn't save much code and there are already a fair number of getProcessor methods for other cases, but no objections to doing so. :)
Thanks for the info @doug-walker ! I ran some tests with the transform interface also, but I guess I saw it as just being a data accessor for the NamedTransform's transform member. It does make sense that that would be sufficient for many cases, since it is just a "named" transform, but I think an additional getProcessor(nt, direction) implementation would improve handling of inverse named transforms in particular, for example:
If I have a named transform with only the forward direction defined, requesting the inverse direction from the named transform instance will return null. That makes sense since getTransform(direction) is an accessor for a member, which in this case is not defined. Knowing that the named transform only defines one direction, we can get the direction that does exist and request the inverse direction through the getProcessor(tr, direction) call, but that requires first inspecting the named transform instance to know which direction is or is not defined. The added getProcessor(nt, direction) would hide that implementation detail for the developer.
I can work on this for 2.2 if the change seems reasonable, following discussion at the next TSC meeting.
Sounds good, thank you Michael!
This is gonna make NamedTransforms so much easier and intuitive to work with.
Does it make sense to also allow for an optional Context as well?