2 # -*- coding: utf-8 -*-
5 Defines objects creating the *ACES* configuration.
8 from __future__ import division
15 import PyOpenColorIO as ocio
16 from aces_ocio.colorspaces import aces
17 from aces_ocio.colorspaces import arri
18 from aces_ocio.colorspaces import canon
19 from aces_ocio.colorspaces import general
20 from aces_ocio.colorspaces import gopro
21 from aces_ocio.colorspaces import panasonic
22 from aces_ocio.colorspaces import red
23 from aces_ocio.colorspaces import sony
24 from aces_ocio.process import Process
26 from aces_ocio.utilities import (
28 colorspace_prefixed_name,
33 __author__ = 'ACES Developers'
34 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
36 __maintainer__ = 'ACES Developers'
37 __email__ = 'aces@oscars.org'
38 __status__ = 'Production'
40 __all__ = ['ACES_OCIO_CTL_DIRECTORY_ENVIRON',
41 'ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON',
43 'create_ocio_transform',
44 'add_colorspace_aliases',
50 'generate_baked_LUTs',
51 'generate_config_directory',
55 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
56 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
59 def set_config_roles(config,
70 compositing_linear=None):
72 Sets given *OCIO* configuration roles to the config.
77 color_picking : str or unicode, optional
78 Color Picking role title.
79 color_timing : str or unicode, optional
80 Color Timing role title.
81 compositing_log : str or unicode, optional
82 Compositing Log role title.
83 data : str or unicode, optional
85 default : str or unicode, optional
87 matte_paint : str or unicode, optional
88 Matte Painting role title.
89 reference : str or unicode, optional
91 scene_linear : str or unicode, optional
92 Scene Linear role title.
93 texture_paint : str or unicode, optional
94 Texture Painting role title.
95 rendering : str or unicode, optional
97 compositing_linear : str or unicode, optional
98 Compositing Linear role title.
105 if color_picking is not None:
106 config.setRole(ocio.Constants.ROLE_COLOR_PICKING, color_picking)
107 if color_timing is not None:
108 config.setRole(ocio.Constants.ROLE_COLOR_TIMING, color_timing)
109 if compositing_log is not None:
110 config.setRole(ocio.Constants.ROLE_COMPOSITING_LOG, compositing_log)
112 config.setRole(ocio.Constants.ROLE_DATA, data)
113 if default is not None:
114 config.setRole(ocio.Constants.ROLE_DEFAULT, default)
115 if matte_paint is not None:
116 config.setRole(ocio.Constants.ROLE_MATTE_PAINT, matte_paint)
117 if reference is not None:
118 config.setRole(ocio.Constants.ROLE_REFERENCE, reference)
119 if texture_paint is not None:
120 config.setRole(ocio.Constants.ROLE_TEXTURE_PAINT, texture_paint)
122 # *rendering* and *compositing_linear* roles default to the *scene_linear*
123 # value if not set explicitly.
124 if rendering is not None:
125 config.setRole('rendering', rendering)
126 if compositing_linear is not None:
127 config.setRole('compositing_linear', compositing_linear)
128 if scene_linear is not None:
129 config.setRole(ocio.Constants.ROLE_SCENE_LINEAR, scene_linear)
130 if rendering is None:
131 config.setRole('rendering', scene_linear)
132 if compositing_linear is None:
133 config.setRole('compositing_linear', scene_linear)
138 def create_ocio_transform(transforms):
140 Returns an *OCIO* transform from given array of transform descriptions.
144 transforms : array_like
145 Transform descriptions as an array_like of dicts:
146 {'type', 'src', 'dst', 'direction'}
154 direction_options = {
155 'forward': ocio.Constants.TRANSFORM_DIR_FORWARD,
156 'inverse': ocio.Constants.TRANSFORM_DIR_INVERSE}
160 for transform in transforms:
162 # *lutFile* transform
163 if transform['type'] == 'lutFile':
164 ocio_transform = ocio.FileTransform()
166 if 'path' in transform:
167 ocio_transform.setSrc(transform['path'])
169 if 'cccid' in transform:
170 ocio_transform.setCCCId(transform['cccid'])
172 if 'interpolation' in transform:
173 ocio_transform.setInterpolation(transform['interpolation'])
175 ocio_transform.setInterpolation(ocio.Constants.INTERP_BEST)
177 if 'direction' in transform:
178 ocio_transform.setDirection(
179 direction_options[transform['direction']])
181 ocio_transforms.append(ocio_transform)
184 elif transform['type'] == 'matrix':
185 ocio_transform = ocio.MatrixTransform()
186 # `MatrixTransform` member variables can't be initialized directly,
187 # each must be set individually.
188 ocio_transform.setMatrix(transform['matrix'])
190 if 'offset' in transform:
191 ocio_transform.setOffset(transform['offset'])
193 if 'direction' in transform:
194 ocio_transform.setDirection(
195 direction_options[transform['direction']])
197 ocio_transforms.append(ocio_transform)
199 # *exponent* transform
200 elif transform['type'] == 'exponent':
201 ocio_transform = ocio.ExponentTransform()
203 if 'value' in transform:
204 ocio_transform.setValue(transform['value'])
206 ocio_transforms.append(ocio_transform)
209 elif transform['type'] == 'log':
210 ocio_transform = ocio.LogTransform()
212 if 'base' in transform:
213 ocio_transform.setBase(transform['base'])
215 if 'direction' in transform:
216 ocio_transform.setDirection(
217 direction_options[transform['direction']])
219 ocio_transforms.append(ocio_transform)
221 # *colorspace* transform
222 elif transform['type'] == 'colorspace':
223 ocio_transform = ocio.ColorSpaceTransform()
225 if 'src' in transform:
226 ocio_transform.setSrc(transform['src'])
228 if 'dst' in transform:
229 ocio_transform.setDst(transform['dst'])
231 if 'direction' in transform:
232 ocio_transform.setDirection(
233 direction_options[transform['direction']])
235 ocio_transforms.append(ocio_transform)
238 elif transform['type'] == 'look':
239 ocio_transform = ocio.LookTransform()
240 if 'look' in transform:
241 ocio_transform.setLooks(transform['look'])
243 if 'src' in transform:
244 ocio_transform.setSrc(transform['src'])
246 if 'dst' in transform:
247 ocio_transform.setDst(transform['dst'])
249 if 'direction' in transform:
250 ocio_transform.setDirection(
251 direction_options[transform['direction']])
253 ocio_transforms.append(ocio_transform)
257 print('Ignoring unknown transform type : %s' % transform['type'])
259 if len(ocio_transforms) > 1:
260 group_transform = ocio.GroupTransform()
261 for transform in ocio_transforms:
262 group_transform.push_back(transform)
263 transform = group_transform
265 transform = ocio_transforms[0]
270 def add_colorspace_aliases(config,
271 reference_colorspace,
273 colorspace_alias_names,
276 Adds given colorspace aliases to the *OCIO* config.
281 *OCIO* configuration.
282 reference_colorspace : Colorspace
283 Reference colorspace.
284 colorspace : Colorspace
285 Colorspace to set the aliases into the *OCIO* config.
295 for alias_name in colorspace_alias_names:
296 if alias_name.lower() == colorspace.name.lower():
297 print('Skipping alias creation for %s, alias %s, '
298 'because lower cased names match' % (
299 colorspace.name, alias_name))
302 print('Adding alias colorspace space %s, alias to %s' % (
303 alias_name, colorspace.name))
305 compact_family_name = family
307 description = colorspace.description
308 if colorspace.aces_transform_id:
310 '\n\nACES Transform ID : %s' % colorspace.aces_transform_id)
312 ocio_colorspace_alias = ocio.ColorSpace(
314 bitDepth=colorspace.bit_depth,
315 description=description,
316 equalityGroup=colorspace.equality_group,
317 family=compact_family_name,
318 isData=colorspace.is_data,
319 allocation=colorspace.allocation_type,
320 allocationVars=colorspace.allocation_vars)
322 if colorspace.to_reference_transforms:
323 print('\tGenerating To-Reference transforms')
324 ocio_transform = create_ocio_transform(
325 [{'type': 'colorspace',
326 'src': colorspace.name,
327 'dst': reference_colorspace.name,
328 'direction': 'forward'}])
329 ocio_colorspace_alias.setTransform(
331 ocio.Constants.COLORSPACE_DIR_TO_REFERENCE)
333 if colorspace.from_reference_transforms:
334 print('\tGenerating From-Reference transforms')
335 ocio_transform = create_ocio_transform(
336 [{'type': 'colorspace',
337 'src': reference_colorspace.name,
338 'dst': colorspace.name,
339 'direction': 'forward'}])
340 ocio_colorspace_alias.setTransform(
342 ocio.Constants.COLORSPACE_DIR_FROM_REFERENCE)
344 config.addColorSpace(ocio_colorspace_alias)
353 Adds given look to the *OCIO* config.
358 *OCIO* configuration.
360 Look description: {'name', 'colorspace', 'lut', 'cccid'}
361 custom_lut_dir : str or unicode
362 Directory to copy the look lut into.
363 reference_name : str or unicode
366 Colorspaces and transforms converting between those colorspaces and
367 the reference colorspace, *ACES*.
375 look_name, look_colorspace, look_lut, look_cccid = unpack_default(look, 4)
377 print('Adding look %s - %s' % (look_name, ', '.join(look)))
379 # Copy *look LUT* if `custom_lut_dir` is provided.
381 if '$' not in look_lut:
382 print('Getting ready to copy look lut : %s' % look_lut)
383 shutil.copy2(look_lut, custom_lut_dir)
384 look_lut = os.path.split(look_lut)[1]
386 print('Skipping LUT copy because path contains a context variable')
388 print('Adding look to config')
389 ocio_look = ocio.Look()
390 ocio_look.setName(look_name)
391 ocio_look.setProcessSpace(look_colorspace)
393 keys = {'type': 'lutFile',
395 'direction': 'forward'}
397 keys['cccid'] = look_cccid
399 ocio_transform = create_ocio_transform([keys])
400 ocio_look.setTransform(ocio_transform)
402 config.addLook(ocio_look)
404 print('Creating aliased colorspace')
406 # Creating *OCIO* colorspace referencing the look:
407 # - Needed for implementations that don't process looks properly.
408 # - Needed for implementations that don't expose looks properly.
409 look_aliases = ['look_%s' % compact(look_name)]
410 colorspace = ColorSpace(look_name,
411 aliases=look_aliases,
412 description='The %s Look colorspace' % look_name,
415 colorspace.from_reference_transforms = [{'type': 'look',
417 'src': reference_name,
418 'dst': reference_name,
419 'direction': 'forward'}]
421 print('Adding colorspace %s, alias to look %s to config data' % (
422 look_name, look_name))
424 config_data['colorSpaces'].append(colorspace)
429 def add_looks_to_views(looks,
432 multiple_displays=False):
434 Integrates a set of looks into the *OCIO* config's Displays and Views
438 looks : array of str or unicode
440 reference_name : str or unicode
441 The name of the *OCIO* reference colorspace
443 Colorspaces and transforms converting between those colorspaces and
444 the reference colorspace, *ACES*.
445 multiple_displays : bool
446 If true, looks are added to the config_data looks list
447 If false, looks are integrated directly into the list of displays and
448 views. This may be necessary due to limitations of some applications'
449 currently implementation of OCIO, ex. Maya 2016.
455 look_names = [look[0] for look in looks]
458 # - Adding a *look* per *Display*.
459 # - Assuming there is a *Display* for each *ACES* *Output Transform*.
460 if multiple_displays:
461 for look_name in look_names:
462 config_data['looks'].append(look_name)
465 # - Copy each *Output Transform* colorspace.
466 # - For each copy, add a *LookTransform* to the head of the
467 # `from_reference` transform list.
468 # - Add these the copy colorspaces for the *Displays* / *Views*.
470 for display, view_list in config_data['displays'].iteritems():
472 look_names_string = ''
473 for view_name, output_colorspace in view_list.iteritems():
474 if view_name == 'Output Transform':
476 print('Adding new View that incorporates looks')
478 colorspace_c = copy.deepcopy(output_colorspace)
480 for i, look_name in enumerate(look_names):
481 look_name = look_names[i]
483 # Add the `LookTransform` to the head of the
484 # `from_reference` transform list.
485 if colorspace_c.from_reference_transforms:
486 colorspace_c.from_reference_transforms.insert(
490 'src': reference_name,
491 'dst': reference_name,
492 'direction': 'forward'})
494 # Add the `LookTransform` to the end of
495 # the `to_reference` transform list.
496 if colorspace_c.to_reference_transforms:
497 inverse_look_name = look_names[
498 len(look_names) - 1 - i]
500 colorspace_c.to_reference_transforms.append(
502 'look': inverse_look_name,
503 'src': reference_name,
504 'dst': reference_name,
505 'direction': 'inverse'})
507 if look_name not in config_data['looks']:
508 config_data['looks'].append(look_name)
510 look_names_string = ', '.join(look_names)
511 colorspace_c.name = '%s with %s' % (
512 output_colorspace.name, look_names_string)
513 colorspace_c.aliases = [
514 'out_%s' % compact(colorspace_c.name)]
516 print('Colorspace that incorporates looks '
517 'created : %s' % colorspace_c.name)
519 config_data['colorSpaces'].append(colorspace_c)
522 print('Adding colorspace that incorporates looks '
525 # Updating the *View* name.
526 view_list['Output Transform with %s' % look_names_string] = (
528 config_data['displays'][display] = view_list
531 def create_config(config_data,
534 multiple_displays=False,
536 custom_lut_dir=None):
538 Create the *OCIO* config based on the configuration data
543 Colorspaces and transforms converting between those colorspaces and
544 the reference colorspace, *ACES*, along with other data needed to
545 generate a complete *OCIO* configuration
546 aliases : bool, optional
547 Whether or not to include Alias colorspaces
548 prefix : bool, optional
549 Whether or not to prefix the colorspace names with their Family names
550 multiple_displays : bool, optional
551 Whether to create a single display named *ACES* with Views for each
552 Output Transform or multiple displays, one for each Output Transform
553 look_info : array of str or unicode, optional
554 Paths and names for look data
555 custom_lut_dir : str or unicode, optional
556 Directory to use for storing custom look files
561 The constructed OCIO configuration
564 if look_info is None:
568 alias_colorspaces = []
570 config = ocio.Config()
572 config.setDescription('An ACES config generated from python')
574 search_path = ['luts']
576 search_path.append('custom')
577 config.setSearchPath(':'.join(search_path))
579 reference_data = config_data['referenceColorSpace']
581 # Adding the colorspace *Family* into the name which helps with
582 # applications that presenting colorspaces as one a flat list.
584 prefixed_name = colorspace_prefixed_name(reference_data)
585 prefixed_names[reference_data.name] = prefixed_name
586 reference_data.name = prefixed_name
588 print('Adding the reference color space : %s' % reference_data.name)
590 reference = ocio.ColorSpace(
591 name=reference_data.name,
592 bitDepth=reference_data.bit_depth,
593 description=reference_data.description,
594 equalityGroup=reference_data.equality_group,
595 family=reference_data.family,
596 isData=reference_data.is_data,
597 allocation=reference_data.allocation_type,
598 allocationVars=reference_data.allocation_vars)
600 config.addColorSpace(reference)
603 if reference_data.aliases:
604 # Deferring adding alias colorspaces until end, which helps with
605 # applications listing the colorspaces in the order that they were
606 # defined in the configuration: alias colorspaces are usually named
607 # lower case with spaces but normal colorspaces names are longer
608 # and more verbose, thus it becomes harder for user to visually
609 # parse the list of colorspaces when there are names such as
610 # "crv_canonlog" interspersed with names like
611 # "Input - Canon - Curve - Canon-Log".
612 # Moving the alias colorspace definitions to the end of the
613 # configuration avoids the above problem.
614 alias_colorspaces.append(
615 [reference_data, reference_data, reference_data.aliases])
620 print('Adding looks')
622 config_data['looks'] = []
624 for look in look_info:
631 add_looks_to_views(look_info,
638 print('Adding regular colorspaces')
640 for colorspace in sorted(config_data['colorSpaces'],
641 cmp=lambda x,y: cmp(x.family.lower(), y.family.lower())):
642 # Adding the colorspace *Family* into the name which helps with
643 # applications that presenting colorspaces as one a flat list.
645 prefixed_name = colorspace_prefixed_name(colorspace)
646 prefixed_names[colorspace.name] = prefixed_name
647 colorspace.name = prefixed_name
649 print('Creating new color space : %s' % colorspace.name)
651 description = colorspace.description
652 if colorspace.aces_transform_id:
654 '\n\nACES Transform ID : %s' % colorspace.aces_transform_id)
656 ocio_colorspace = ocio.ColorSpace(
657 name=colorspace.name,
658 bitDepth=colorspace.bit_depth,
659 description=description,
660 equalityGroup=colorspace.equality_group,
661 family=colorspace.family,
662 isData=colorspace.is_data,
663 allocation=colorspace.allocation_type,
664 allocationVars=colorspace.allocation_vars)
666 if colorspace.to_reference_transforms:
667 print('\tGenerating To-Reference transforms')
668 ocio_transform = create_ocio_transform(
669 colorspace.to_reference_transforms)
670 ocio_colorspace.setTransform(
672 ocio.Constants.COLORSPACE_DIR_TO_REFERENCE)
674 if colorspace.from_reference_transforms:
675 print('\tGenerating From-Reference transforms')
676 ocio_transform = create_ocio_transform(
677 colorspace.from_reference_transforms)
678 ocio_colorspace.setTransform(
680 ocio.Constants.COLORSPACE_DIR_FROM_REFERENCE)
682 config.addColorSpace(ocio_colorspace)
685 if colorspace.aliases:
686 # Deferring adding alias colorspaces until end, which helps
687 # with applications listing the colorspaces in the order that
688 # they were defined in the configuration.
689 alias_colorspaces.append(
690 [reference_data, colorspace, colorspace.aliases])
696 # Adding roles early so that alias colorspaces can be created
697 # with roles names before remaining colorspace aliases are added
698 # to the configuration.
699 print('Setting the roles')
704 color_picking=prefixed_names[
705 config_data['roles']['color_picking']],
706 color_timing=prefixed_names[config_data['roles']['color_timing']],
707 compositing_log=prefixed_names[
708 config_data['roles']['compositing_log']],
709 data=prefixed_names[config_data['roles']['data']],
710 default=prefixed_names[config_data['roles']['default']],
711 matte_paint=prefixed_names[config_data['roles']['matte_paint']],
712 reference=prefixed_names[config_data['roles']['reference']],
713 scene_linear=prefixed_names[config_data['roles']['scene_linear']],
714 compositing_linear=prefixed_names[config_data['roles']['scene_linear']],
715 rendering=prefixed_names[config_data['roles']['scene_linear']],
716 texture_paint=prefixed_names[
717 config_data['roles']['texture_paint']])
719 # Add the aliased colorspaces for each role
720 for role_name, role_colorspace_name in config_data['roles'].iteritems():
721 role_colorspace_prefixed_name = prefixed_names[role_colorspace_name]
723 #print( 'Finding colorspace : %s' % role_colorspace_prefixed_name )
724 # Find the colorspace pointed to by the role
725 role_colorspaces = [colorspace
726 for colorspace in config_data['colorSpaces']
727 if colorspace.name == role_colorspace_prefixed_name]
728 role_colorspace = None
729 if len(role_colorspaces) > 0:
730 role_colorspace = role_colorspaces[0]
732 if reference_data.name == role_colorspace_prefixed_name:
733 role_colorspace = reference_data
736 # The alias colorspace shouldn't match the role name exactly
737 role_name_alias1 = "role_%s" % role_name
738 role_name_alias2 = "Role - %s" % role_name
740 print( 'Adding a role colorspace named %s, pointing to %s' % (
741 role_name_alias2, role_colorspace.name))
743 alias_colorspaces.append(
744 (reference_data, role_colorspace, [role_name_alias1]))
746 add_colorspace_aliases(
747 config, reference_data, role_colorspace, [role_name_alias2], 'Roles')
752 color_picking=config_data['roles']['color_picking'],
753 color_timing=config_data['roles']['color_timing'],
754 compositing_log=config_data['roles']['compositing_log'],
755 data=config_data['roles']['data'],
756 default=config_data['roles']['default'],
757 matte_paint=config_data['roles']['matte_paint'],
758 reference=config_data['roles']['reference'],
759 scene_linear=config_data['roles']['scene_linear'],
760 compositing_linear=config_data['roles']['scene_linear'],
761 rendering=config_data['roles']['scene_linear'],
762 texture_paint=config_data['roles']['texture_paint'])
764 # Add the aliased colorspaces for each role
765 for role_name, role_colorspace_name in config_data['roles'].iteritems():
766 # Find the colorspace pointed to by the role
767 role_colorspaces = [colorspace
768 for colorspace in config_data['colorSpaces']
769 if colorspace.name == role_colorspace_name]
770 role_colorspace = None
771 if len(role_colorspaces) > 0:
772 role_colorspace = role_colorspaces[0]
774 if reference_data.name == role_colorspace_name:
775 role_colorspace = reference_data
778 # The alias colorspace shouldn't match the role name exactly
779 role_name_alias1 = "role_%s" % role_name
780 role_name_alias2 = "Role - %s" % role_name
782 print('Adding a role colorspace named %s, pointing to %s' % (
783 role_name_alias2, role_colorspace.name))
785 alias_colorspaces.append(
786 (reference_data, role_colorspace, [role_name_alias1]))
788 add_colorspace_aliases(
789 config, reference_data, role_colorspace, [role_name_alias2], 'Roles')
793 # Adding alias colorspaces at the end as some applications use
794 # colorspaces definitions order of the configuration to order
795 # the colorspaces in their selection lists, some applications
796 # use alphabetical ordering.
797 # This should keep the alias colorspaces out of the way for applications
798 # using the configuration order.
799 print('Adding the alias colorspaces')
800 for reference, colorspace, aliases in alias_colorspaces:
801 add_colorspace_aliases(config, reference, colorspace, aliases)
805 print('Adding the diplays and views')
807 # Setting the *color_picking* role to be the first *Display*'s
808 # *Output Transform* *View*.
809 default_display_name = config_data['defaultDisplay']
810 default_display_views = config_data['displays'][default_display_name]
811 default_display_colorspace = default_display_views['Output Transform']
813 # Defining *Displays* and *Views*.
814 displays, views = [], []
816 # Defining a generic *Display* and *View* setup.
817 if multiple_displays:
818 looks = config_data['looks'] if ('looks' in config_data) else []
819 looks = ', '.join(looks)
820 print('Creating multiple displays, with looks : %s' % looks)
822 # *Displays* are not reordered to put the *defaultDisplay* first
823 # because *OCIO* will order them alphabetically when the configuration
824 # is written to disk.
825 for display, view_list in config_data['displays'].iteritems():
826 for view_name, colorspace in view_list.iteritems():
827 config.addDisplay(display, view_name, colorspace.name, looks)
828 if 'Output Transform' in view_name and looks != '':
829 # *Views* without *Looks*.
830 config.addDisplay(display, view_name, colorspace.name)
832 # *Views* with *Looks*.
833 view_name_with_looks = '%s with %s' % (view_name, looks)
834 config.addDisplay(display, view_name_with_looks,
835 colorspace.name, looks)
837 config.addDisplay(display, view_name, colorspace.name)
838 if not (view_name in views):
839 views.append(view_name)
840 displays.append(display)
842 # *Displays* and *Views* useful in a *GUI* context.
844 single_display_name = 'ACES'
845 displays.append(single_display_name)
847 # Ensuring the *defaultDisplay* is first.
848 display_names = sorted(config_data['displays'])
849 display_names.insert(0, display_names.pop(
850 display_names.index(default_display_name)))
852 looks = config_data['looks'] if ('looks' in config_data) else []
853 look_names = ', '.join(looks)
855 displays_views_colorspaces = []
857 for display in display_names:
858 view_list = config_data['displays'][display]
859 for view_name, colorspace in view_list.iteritems():
860 if 'Output Transform' in view_name:
862 # We use the *Display* names as the *View* names in this
863 # case as there is a single *Display* containing all the
865 # This works for more applications than not,as of the time
866 # of this implementation.
868 # Autodesk Maya 2016 doesn't support parentheses in
870 sanitised_display = replace(display, {')': '', '(': ''})
872 # *View* with *Looks*.
873 if 'with' in view_name:
874 sanitised_display = '%s with %s' % (
875 sanitised_display, look_names)
877 views_with_looks_at_end = False
878 # Storing combo of *Display*, *View* and *Colorspace*
879 # name so they can be added to the end of the list.
880 if views_with_looks_at_end:
881 displays_views_colorspaces.append(
882 [single_display_name, sanitised_display,
885 config.addDisplay(single_display_name,
889 if not (sanitised_display in views):
890 views.append(sanitised_display)
892 # *View* without *Looks*.
894 config.addDisplay(single_display_name,
898 if not (sanitised_display in views):
899 views.append(sanitised_display)
901 # Adding to the configuration any *Display*, *View* combinations that
902 # were saved for later.
903 # This list should be empty unless `views_with_looks_at_end` is
905 for display_view_colorspace in displays_views_colorspaces:
906 single_display_name, sanitised_display, colorspace_name = (
907 display_view_colorspace)
909 config.addDisplay(single_display_name,
913 if not (sanitised_display in views):
914 views.append(sanitised_display)
916 raw_display_space_name = config_data['roles']['data']
917 log_display_space_name = config_data['roles']['compositing_log']
920 raw_display_space_name = prefixed_names[raw_display_space_name]
921 log_display_space_name = prefixed_names[log_display_space_name]
923 config.addDisplay(single_display_name, 'Raw', raw_display_space_name)
925 config.addDisplay(single_display_name, 'Log', log_display_space_name)
928 config.setActiveDisplays(','.join(sorted(displays)))
929 config.setActiveViews(','.join(views))
933 # Ensuring the configuration is valid.
936 # Resetting colorspace names to their non-prefixed versions.
938 prefixed_names_inverse = {}
939 for original, prefixed in prefixed_names.iteritems():
940 prefixed_names_inverse[prefixed] = original
942 reference_data.name = prefixed_names_inverse[reference_data.name]
945 for colorspace in config_data['colorSpaces']:
946 colorspace.name = prefixed_names_inverse[colorspace.name]
948 print('Prefixed names')
949 for original, prefixed in prefixed_names.iteritems():
950 print('%s, %s' % (original, prefixed))
954 print('Inverse Lookup of Prefixed names')
955 for prefixed, original in prefixed_names_inverse.iteritems():
956 print('%s, %s' % (prefixed, original))
962 def create_config_data(odt_info,
967 lut_resolution_1d=4096,
968 lut_resolution_3d=64,
971 Create the *ACES* LUTs and data structures needed for later *OCIO*
972 configuration generation
976 odt_info : array of dicts of str or unicode
977 Descriptions of the *ACES* Output Transforms
978 lmt_info : array of dicts of str or unicode
979 Descriptions of the *ACES* Look Transforms
980 shaper_name : str or unicode
981 The name of the Shaper function to use when generating LUTs.
982 Options: Log2, DolbyPQ
983 aces_ctl_directory : str or unicode
984 The path to the aces 'transforms/ctl/utilities'
985 lut_directory : str or unicode
986 The path to use when writing LUTs
987 lut_resolution_1d : int, optional
988 The resolution of generated 1D LUTs
989 lut_resolution_3d : int, optional
990 The resolution of generated 3D LUTs
992 Whether or not to clean up the intermediate images
997 Colorspaces, LUT paths and transforms converting between those
998 colorspaces and the reference colorspace, *ACES*.
1001 print('create_config_data - begin')
1004 config_data['displays'] = {}
1005 config_data['colorSpaces'] = []
1007 # -------------------------------------------------------------------------
1008 # *ACES Color Spaces*
1009 # -------------------------------------------------------------------------
1011 # *ACES* colorspaces
1015 aces_log_display_space,
1017 aces_default_display) = aces.create_colorspaces(aces_ctl_directory,
1026 config_data['referenceColorSpace'] = aces_reference
1027 config_data['roles'] = aces_roles
1029 for cs in aces_colorspaces:
1030 config_data['colorSpaces'].append(cs)
1032 for name, data in aces_displays.iteritems():
1033 config_data['displays'][name] = data
1035 config_data['defaultDisplay'] = aces_default_display
1036 config_data['linearDisplaySpace'] = aces_reference
1037 config_data['logDisplaySpace'] = aces_log_display_space
1039 # -------------------------------------------------------------------------
1040 # *Camera Input Transforms*
1041 # -------------------------------------------------------------------------
1043 # *ARRI Log-C* to *ACES*
1044 arri_colorspaces = arri.create_colorspaces(lut_directory,
1046 for cs in arri_colorspaces:
1047 config_data['colorSpaces'].append(cs)
1049 # *Canon-Log* to *ACES*
1050 canon_colorspaces = canon.create_colorspaces(lut_directory,
1052 for cs in canon_colorspaces:
1053 config_data['colorSpaces'].append(cs)
1055 # *GoPro Protune* to *ACES*
1056 gopro_colorspaces = gopro.create_colorspaces(lut_directory,
1058 for cs in gopro_colorspaces:
1059 config_data['colorSpaces'].append(cs)
1061 # *Panasonic V-Log* to *ACES*
1062 panasonic_colorspaces = panasonic.create_colorspaces(lut_directory,
1064 for cs in panasonic_colorspaces:
1065 config_data['colorSpaces'].append(cs)
1067 # *RED* colorspaces to *ACES*
1068 red_colorspaces = red.create_colorspaces(lut_directory,
1070 for cs in red_colorspaces:
1071 config_data['colorSpaces'].append(cs)
1074 sony_colorspaces = sony.create_colorspaces(lut_directory,
1076 for cs in sony_colorspaces:
1077 config_data['colorSpaces'].append(cs)
1079 # -------------------------------------------------------------------------
1080 # General Colorspaces
1081 # -------------------------------------------------------------------------
1082 general_colorspaces = general.create_colorspaces(lut_directory,
1084 for cs in general_colorspaces:
1085 config_data['colorSpaces'].append(cs)
1087 # The *Raw* colorspace
1088 raw = general.create_raw()
1089 config_data['colorSpaces'].append(raw)
1091 # Overriding various roles
1092 config_data['roles']['data'] = raw.name
1093 config_data['roles']['reference'] = raw.name
1094 config_data['roles']['texture_paint'] = raw.name
1096 print('create_config_data - end')
1101 def write_config(config, config_path, sanity_check=True):
1103 Writes the configuration to given path.
1108 *OCIO* configuration.
1109 config_path : str or unicode
1110 Path to write the configuration path.
1112 Performs configuration sanity checking prior to writing it on disk.
1122 config.sanityCheck()
1123 except Exception, e:
1125 print 'Configuration was not written due to a failed Sanity Check'
1128 with open(config_path, mode='w') as fp:
1129 fp.write(config.serialize())
1132 def generate_baked_LUTs(odt_info,
1137 lut_resolution_shaper=1024,
1140 Generate baked representations of the transforms from the *ACES* *OCIO*
1145 odt_info : array of dicts of str or unicode
1146 Descriptions of the *ACES* Output Transforms
1147 shaper_name : str or unicode
1148 The name of the Shaper function to use when generating LUTs.
1149 Options: Log2, DolbyPQ
1150 baked_directory : str or unicode
1151 The path to use when writing baked LUTs
1152 config_path : str or unicode
1153 The path to the *OCIO* configuration
1154 lut_resolution_3d : int, optional
1155 The resolution of generated 3D LUTs
1156 lut_resolution_shaper : int, optional
1157 The resolution of shaper used as part of some 3D LUTs
1158 prefix : bool, optional
1159 Whether or not colorspace names will use their Family names as prefixes
1160 in the *OCIO* config
1167 odt_info_C = dict(odt_info)
1169 # Older behavior for *ODTs* that have support for full and legal ranges,
1170 # generating a LUT for both ranges.
1172 # Create two entries for ODTs that have full and legal range support
1173 for odt_ctl_name, odt_values in odt_info.iteritems():
1174 if odt_values['transformHasFullLegalSwitch']:
1175 odt_name = odt_values['transformUserName']
1177 odt_values_legal = dict(odt_values)
1178 odt_values_legal['transformUserName'] = '%s - Legal' % odt_name
1179 odt_info_C['%s - Legal' % odt_ctl_name] = odt_values_legal
1181 odt_values_full = dict(odt_values)
1182 odt_values_full['transformUserName'] = '%s - Full' % odt_name
1183 odt_info_C['%s - Full' % odt_ctl_name] = odt_values_full
1185 del (odt_info_C[odt_ctl_name])
1188 for odt_ctl_name, odt_values in odt_info_C.iteritems():
1189 odt_prefix = odt_values['transformUserNamePrefix']
1190 odt_name = odt_values['transformUserName']
1192 if odt_name in ['P3-D60 PQ (1000 nits)']:
1193 odt_shaper = shaper_name.replace("48 nits", "1000 nits")
1194 elif odt_name in ['P3-D60 PQ (2000 nits)']:
1195 odt_shaper = shaper_name.replace("48 nits", "2000 nits")
1196 elif odt_name in ['P3-D60 PQ (4000 nits)']:
1197 odt_shaper = shaper_name.replace("48 nits", "4000 nits")
1199 odt_shaper = shaper_name
1202 for input_space in ['ACEScc', 'ACESproxy']:
1203 args = ['--iconfig', config_path,
1206 args += ['--inputspace', 'ACES - %s' % input_space]
1207 args += ['--outputspace', 'Output - %s' % odt_name]
1209 args += ['--inputspace', input_space]
1210 args += ['--outputspace', odt_name]
1212 args += ['--description',
1213 '%s - %s for %s data' % (odt_prefix,
1217 args += ['--shaperspace', 'Utility - %s' % odt_shaper,
1218 '--shapersize', str(lut_resolution_shaper)]
1220 args += ['--shaperspace', odt_shaper,
1221 '--shapersize', str(lut_resolution_shaper)]
1222 args += ['--cubesize', str(lut_resolution_3d)]
1223 args += ['--format',
1225 os.path.join(baked_directory,
1227 '%s for %s.icc' % (odt_name, input_space))]
1229 bake_lut = Process(description='bake a LUT',
1235 for input_space in ['ACEScc', 'ACESproxy']:
1236 args = ['--iconfig', config_path,
1239 args += ['--inputspace', 'ACES - %s' % input_space]
1240 args += ['--outputspace', 'Output - %s' % odt_name]
1242 args += ['--inputspace', input_space]
1243 args += ['--outputspace', odt_name]
1244 args += ['--description',
1245 '%s - %s for %s data' % (
1246 odt_prefix, odt_name, input_space)]
1248 args += ['--shaperspace', 'Utility - %s' % odt_shaper,
1249 '--shapersize', str(lut_resolution_shaper)]
1251 args += ['--shaperspace', odt_shaper,
1252 '--shapersize', str(lut_resolution_shaper)]
1253 args += ['--cubesize', str(lut_resolution_3d)]
1255 fargs = ['--format',
1260 '%s for %s Flame.3dl' % (odt_name, input_space))]
1261 bake_lut = Process(description='bake a LUT',
1263 args=(args + fargs))
1266 largs = ['--format',
1271 '%s for %s Lustre.3dl' % (odt_name, input_space))]
1272 bake_lut = Process(description='bake a LUT',
1274 args=(args + largs))
1278 for input_space in ['ACEScg', 'ACES2065-1']:
1279 args = ['--iconfig', config_path,
1282 args += ['--inputspace', 'ACES - %s' % input_space]
1283 args += ['--outputspace', 'Output - %s' % odt_name]
1285 args += ['--inputspace', input_space]
1286 args += ['--outputspace', odt_name]
1287 args += ['--description',
1288 '%s - %s for %s data' % (
1289 odt_prefix, odt_name, input_space)]
1290 if input_space == 'ACEScg':
1291 lin_shaper_name = '%s - AP1' % odt_shaper
1293 lin_shaper_name = odt_shaper
1295 lin_shaper_name = 'Utility - %s' % lin_shaper_name
1296 args += ['--shaperspace', lin_shaper_name,
1297 '--shapersize', str(lut_resolution_shaper)]
1299 args += ['--cubesize', str(lut_resolution_3d)]
1301 margs = ['--format',
1306 '%s for %s Maya.csp' % (odt_name, input_space))]
1307 bake_lut = Process(description='bake a LUT',
1309 args=(args + margs))
1312 hargs = ['--format',
1317 '%s for %s Houdini.lut' % (odt_name, input_space))]
1318 bake_lut = Process(description='bake a LUT',
1320 args=(args + hargs))
1324 def generate_config_directory(config_directory,
1325 bake_secondary_luts=False,
1326 custom_lut_dir=None):
1328 Create the directories needed for configuration generation
1332 config_directory : str or unicode
1333 The base config directory
1334 bake_secondary_luts : bool, optional
1335 Whether or not to create directories for baked LUTs
1336 custom_lut_dir : bool, optional
1337 Whether or not to create directories for custom Look LUTs
1344 lut_directory = os.path.join(config_directory, 'luts')
1345 dirs = [config_directory, lut_directory]
1347 if bake_secondary_luts:
1348 dirs.extend([os.path.join(config_directory, 'baked'),
1349 os.path.join(config_directory, 'baked', 'flame'),
1350 os.path.join(config_directory, 'baked', 'photoshop'),
1351 os.path.join(config_directory, 'baked', 'houdini'),
1352 os.path.join(config_directory, 'baked', 'lustre'),
1353 os.path.join(config_directory, 'baked', 'maya')])
1356 dirs.append(os.path.join(config_directory, 'custom'))
1359 not os.path.exists(d) and os.mkdir(d)
1361 return lut_directory
1364 def generate_config(aces_ctl_directory,
1366 lut_resolution_1d=4096,
1367 lut_resolution_3d=64,
1368 bake_secondary_luts=True,
1369 multiple_displays=False,
1371 copy_custom_luts=True,
1373 prefix_colorspaces_with_family_names=True,
1374 shaper_base_name='Log2'):
1376 Creates the ACES configuration.
1380 aces_ctl_directory : str or unicode
1381 The path to the aces 'transforms/ctl/utilities'
1382 config_directory : str or unicode
1383 The directory that will hold the generated configuration and LUTs
1384 lut_resolution_1d : int, optional
1385 The resolution of generated 1D LUTs
1386 lut_resolution_3d : int, optional
1387 The resolution of generated 3D LUTs
1388 bake_secondary_luts : bool, optional
1389 Whether or not to create directories for baked LUTs
1390 multiple_displays : bool, optional
1391 Whether to create a single display named *ACES* with Views for each
1392 Output Transform or multiple displays, one for each Output Transform
1393 look_info : array of str or unicode, optional
1394 Paths and names for look data
1395 copy_custom_luts : bool, optional
1396 Whether to reference custom look LUTs directly or to copy them into a
1397 directory within the generated configuration
1398 cleanup : bool, optional
1399 Whether or not to clean up the intermediate images
1400 prefix_colorspaces_with_family_names : bool, optional
1401 Whether or not colorspace names will use their Family names as prefixes
1402 in the *OCIO* config
1403 shaper_base_name : str or unicode
1404 The name of the Shaper function to use when generating LUTs.
1405 Options: Log2, DolbyPQ
1410 Success or failure of configuration generation process
1413 if look_info is None:
1416 custom_lut_dir = None
1417 if copy_custom_luts:
1418 custom_lut_dir = os.path.join(config_directory, 'custom')
1420 lut_directory = generate_config_directory(config_directory,
1421 bake_secondary_luts,
1423 odt_info = aces.get_ODTs_info(aces_ctl_directory)
1424 lmt_info = aces.get_LMTs_info(aces_ctl_directory)
1426 if shaper_base_name == 'DolbyPQ':
1427 shaper_name = 'Dolby PQ 48 nits Shaper'
1429 shaper_name = 'Log2 48 nits Shaper'
1431 config_data = create_config_data(odt_info,
1440 print('Creating config - with prefixes, with aliases')
1441 config = create_config(config_data,
1442 prefix=prefix_colorspaces_with_family_names,
1444 multiple_displays=multiple_displays,
1445 look_info=look_info,
1446 custom_lut_dir=custom_lut_dir)
1449 write_config(config,
1450 os.path.join(config_directory, 'config.ocio'))
1452 if bake_secondary_luts:
1453 generate_baked_LUTs(odt_info,
1455 os.path.join(config_directory, 'baked'),
1456 os.path.join(config_directory, 'config.ocio'),
1459 prefix=prefix_colorspaces_with_family_names)
1466 A simple main that allows the user to exercise the various functions
1467 defined in this file
1480 usage = '%prog [options]\n'
1482 usage += 'An OCIO config generation script for ACES 1.0\n'
1484 usage += 'Command line examples'
1486 usage += ('Create a GUI-friendly ACES 1.0 config with no secondary, '
1488 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1489 '--lutResolution1d 1024 --lutResolution3d 33 -c aces_1.0.0 '
1490 '--dontBakeSecondaryLUTs')
1492 usage += 'Create a more OCIO-compliant ACES 1.0 config : \n'
1493 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1494 '--lutResolution1d 1024 --lutResolution3d 33 -c aces_1.0.0 '
1495 '--createMultipleDisplays')
1498 usage += 'Adding custom looks'
1500 usage += ('Create a GUI-friendly ACES 1.0 config with an ACES-style CDL '
1501 '(will be applied in the ACEScc colorspace): \n')
1502 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1503 '--lutResolution1d 1024 --lutResolution3d 33 -c aces_1.0.0 '
1504 '\n\t\t--addACESLookCDL ACESCDLName '
1505 '/path/to/SampleCDL.ccc cc03345')
1507 usage += 'Create a GUI-friendly ACES 1.0 config with an general CDL: \n'
1508 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1509 '--lutResolution1d 1024 --lutResolution3d 33 -c aces_1.0.0 '
1510 '\n\t\t--addCustomLookCDL CustomCDLName "ACES - ACEScc" '
1511 '/path/to/SampleCDL.ccc cc03345')
1513 usage += ('\tIn this example, the CDL will be applied in the '
1514 'ACEScc colorspace, but the user could choose other spaces '
1515 'by changing the argument after the name of the look. \n')
1517 usage += ('Create a GUI-friendly ACES 1.0 config with an ACES-style LUT '
1518 '(will be applied in the ACEScc colorspace): \n')
1519 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1520 '--lutResolution1d 1024 --lutResolution3d 33 -c aces_1.0.0 '
1521 '\n\t\t--addACESLookLUT ACESLUTName '
1522 '/path/to/SampleCDL.ccc cc03345')
1524 usage += 'Create a GUI-friendly ACES 1.0 config with an general LUT: \n'
1525 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1526 '--lutResolution1d 1024 --lutResolution3d 33 -c aces_1.0.0 '
1527 '\n\t\t--addCustomLookLUT CustomLUTName "ACES - ACEScc" '
1528 '/path/to/SampleCDL.ccc cc03345')
1530 usage += ('\tIn this example, the LUT will be applied in the '
1531 'ACEScc colorspace, but the user could choose other spaces '
1532 'by changing the argument after the name of the look. \n')
1534 usage += ('Create a GUI-friendly ACES 1.0 config using the Dolby PQ '
1535 'transfer function as the shaper: \n')
1536 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1537 '--lutResolution1d 1024 --lutResolution3d 33 -c aces_1.0.0 '
1543 def look_info_callback(option, opt_str, value, parser):
1544 print('look_info_callback')
1545 print(option, opt_str, value, parser)
1546 if opt_str == '--addCustomLookCDL':
1547 look_info.append(value)
1548 elif opt_str == '--addCustomLookLUT':
1549 look_info.append(value)
1550 elif opt_str == '--addACESLookCDL':
1551 look_info.append([value[0], 'ACES - ACEScc', value[1], value[2]])
1552 elif opt_str == '--addACESLookLUT':
1553 look_info.append([value[0], 'ACES - ACEScc', value[1]])
1555 p = optparse.OptionParser(description='',
1556 prog='create_aces_config',
1557 version='create_aces_config 1.0',
1559 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1560 ACES_OCIO_CTL_DIRECTORY_ENVIRON, None))
1561 p.add_option('--configDir', '-c', default=os.environ.get(
1562 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON, None))
1563 p.add_option('--lutResolution1d', default=4096)
1564 p.add_option('--lutResolution3d', default=64)
1565 p.add_option('--dontBakeSecondaryLUTs', action='store_true', default=False)
1566 p.add_option('--keepTempImages', action='store_true', default=False)
1568 p.add_option('--createMultipleDisplays', action='store_true',
1571 p.add_option('--addCustomLookLUT', '', type='string', nargs=3,
1572 action='callback', callback=look_info_callback)
1573 p.add_option('--addCustomLookCDL', '', type='string', nargs=4,
1574 action='callback', callback=look_info_callback)
1575 p.add_option('--addACESLookLUT', '', type='string', nargs=2,
1576 action='callback', callback=look_info_callback)
1577 p.add_option('--addACESLookCDL', '', type='string', nargs=3,
1578 action='callback', callback=look_info_callback)
1579 p.add_option('--copyCustomLUTs', action='store_true', default=False)
1581 p.add_option('--shaper', '-s', default='Log2')
1583 options, arguments = p.parse_args()
1585 aces_ctl_directory = options.acesCTLDir
1586 config_directory = options.configDir
1587 lut_resolution_1d = int(options.lutResolution1d)
1588 lut_resolution_3d = int(options.lutResolution3d)
1589 bake_secondary_luts = not options.dontBakeSecondaryLUTs
1590 cleanup_temp_images = not options.keepTempImages
1591 multiple_displays = options.createMultipleDisplays
1592 copy_custom_luts = options.copyCustomLUTs
1593 shaper_base_name = options.shaper
1598 print('command line : \n%s\n' % ' '.join(sys.argv))
1600 assert aces_ctl_directory is not None, (
1601 'process: No "{0}" environment variable defined or no "ACES CTL" '
1602 'directory specified'.format(
1603 ACES_OCIO_CTL_DIRECTORY_ENVIRON))
1605 assert config_directory is not None, (
1606 'process: No "{0}" environment variable defined or no configuration '
1607 'directory specified'.format(
1608 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON))
1610 return generate_config(aces_ctl_directory,
1614 bake_secondary_luts,
1618 cleanup_temp_images,
1623 if __name__ == '__main__':