Conditional branching in configs?
We often have transforms which need to vary, say, sequence to sequence.
For example: say most of the show converts to default_log to apply a client grade.. except one sequence which needs another_log.
We currently implement this using the "lut_search_path fallback" pattern. As in, we have a fallback folder containing a fixed filename (usually symlinked to the desired LUT), and create folders for the sequences requiring overrides:
/path/to/luts/pershot-fallback:
per_shot.lut -> /path/to/default_log.lut
/path/to/luts/pershot-ZZZ:
per_shot.lut -> /path/to/another_log.lut
then lut_search_path contains something like:
lut_search_path: /path/to/luts/pershot-$SEQ:/path/to/luts/pershot-fallback
So then when the colorspace definition (or look etc) references the pershot.lut:
!<FileTransform> {src: pershot.lut}
..it will either find the LUT in pershot-ZZZ if $SEQ is set to that, and for all others values of $SEQ it will end up at the pershot-fallback folder.
While this approach works, it is far from ideal. Main issues are:
- It's convoluted to setup, requiring two search-path entries per LUT which needs switched and at least the fallback LUT created on disk (and since it must have a generic filename like
pershot, the config no longer inherently describes what this transform actually does) - The config is somewhat opaque - to someone not familiar with the pattern, it's unclear what is going on.
- Switching more complex transforms is not practical. If there is multiple steps in the transform which need to vary, this becomes a mess (separate
lut_search_pathentries, dummy transforms etc) - Switching transforms not based on files will not work (e.g MatrixTransform, ExponentTransform, ColorSpaceTransform, future things like the possible builtin-transforms), short of baking them to LUT's
One idea which was discussed a long time ago was to essentially take a ColorSpace definition config snippet and write this to a file - kind of like an "OCIO powered mega-LUT" file - then reference this in your config using the fallback pattern. This solves points 3 and 4, but not so much the other two points. It lets you switch multiple transforms, but still convoluted/opaque.
A different way this could be approached is to have a "meta-transform" (along the lines of the GroupTransform). This meta-transform would have a condition (e.g "is the SEQ context variable set to a specific value?"), then a transform to use if the condition is met, and and "else" transform
One possible way of encoding this might be (stealing inspiration from Shotgun API's filter conditions):
to_reference: !<GroupTransform>
children:
- !<ConditionalTransform>
context_var: SHOT
operation: in # also things like equals, starts_with, not in etc?
condition_value: ["abc123", "abc321", "def1212", "fed2323"]
transform_true:
!<FileTransform> {src: another_log.lut}
transform_false:
!<GroupTransform>
children:
- !<FileTransform> {src: default_log.lut}
- ...
This would encode the psuedocode logic of:
if $SHOT in ('abc123', 'abc321', or ...):
transform = FileTransform("another_log.lut")
else:
transform = FileTransform("default_log.lut")
These ConditionalTransforms could be nested inside GroupTransforms or used at top-level of a ColorSpace defintion. It could also support branching from things other than context-vars (potentially things like if it's a CPU or GPU code path, host names, host applications etc etc)
While I'm not 100% happy with the above mockup config syntax, I feel the general approach of encoding these conditional explicitly in the config file could be very valuable. Thoughts?
The first approach you describe ("OCIO powered mega-LUT") will be mostly supported by CLF/CTF in OCIO v2. You can even use Processor::write() to losslessly write any processor (comprising your transforms) to a file, which could be referenced by another config. It doesn't answer the cleaner logical switch suggestion, but is a step in the right direction.