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 panasonic_gh4
23 from aces_ocio.colorspaces import red
24 from aces_ocio.colorspaces import sony
25 from aces_ocio.process import Process
27 from aces_ocio.utilities import (
29 colorspace_prefixed_name,
34 __author__ = 'ACES Developers'
35 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
37 __maintainer__ = 'ACES Developers'
38 __email__ = 'aces@oscars.org'
39 __status__ = 'Production'
41 __all__ = ['ACES_OCIO_CTL_DIRECTORY_ENVIRON',
42 'ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON',
44 'create_ocio_transform',
45 'add_colorspace_aliases',
51 'generate_baked_LUTs',
52 'generate_config_directory',
56 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
57 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
60 def set_config_roles(config,
71 compositing_linear=None):
73 Sets given *OCIO* configuration roles to the config.
78 color_picking : str or unicode, optional
79 Color Picking role title.
80 color_timing : str or unicode, optional
81 Color Timing role title.
82 compositing_log : str or unicode, optional
83 Compositing Log role title.
84 data : str or unicode, optional
86 default : str or unicode, optional
88 matte_paint : str or unicode, optional
89 Matte Painting role title.
90 reference : str or unicode, optional
92 scene_linear : str or unicode, optional
93 Scene Linear role title.
94 texture_paint : str or unicode, optional
95 Texture Painting role title.
96 rendering : str or unicode, optional
98 compositing_linear : str or unicode, optional
99 Compositing Linear role title.
106 if color_picking is not None:
107 config.setRole(ocio.Constants.ROLE_COLOR_PICKING, color_picking)
108 if color_timing is not None:
109 config.setRole(ocio.Constants.ROLE_COLOR_TIMING, color_timing)
110 if compositing_log is not None:
111 config.setRole(ocio.Constants.ROLE_COMPOSITING_LOG, compositing_log)
113 config.setRole(ocio.Constants.ROLE_DATA, data)
114 if default is not None:
115 config.setRole(ocio.Constants.ROLE_DEFAULT, default)
116 if matte_paint is not None:
117 config.setRole(ocio.Constants.ROLE_MATTE_PAINT, matte_paint)
118 if reference is not None:
119 config.setRole(ocio.Constants.ROLE_REFERENCE, reference)
120 if texture_paint is not None:
121 config.setRole(ocio.Constants.ROLE_TEXTURE_PAINT, texture_paint)
123 # *rendering* and *compositing_linear* roles default to the *scene_linear*
124 # value if not set explicitly.
125 if rendering is not None:
126 config.setRole('rendering', rendering)
127 if compositing_linear is not None:
128 config.setRole('compositing_linear', compositing_linear)
129 if scene_linear is not None:
130 config.setRole(ocio.Constants.ROLE_SCENE_LINEAR, scene_linear)
131 if rendering is None:
132 config.setRole('rendering', scene_linear)
133 if compositing_linear is None:
134 config.setRole('compositing_linear', scene_linear)
139 def create_ocio_transform(transforms):
141 Returns an *OCIO* transform from given array of transform descriptions.
145 transforms : array_like
146 Transform descriptions as an array_like of dicts:
147 {'type', 'src', 'dst', 'direction'}
155 direction_options = {
156 'forward': ocio.Constants.TRANSFORM_DIR_FORWARD,
157 'inverse': ocio.Constants.TRANSFORM_DIR_INVERSE}
161 for transform in transforms:
163 # *lutFile* transform
164 if transform['type'] == 'lutFile':
165 ocio_transform = ocio.FileTransform()
167 if 'path' in transform:
168 ocio_transform.setSrc(transform['path'])
170 if 'cccid' in transform:
171 ocio_transform.setCCCId(transform['cccid'])
173 if 'interpolation' in transform:
174 ocio_transform.setInterpolation(transform['interpolation'])
176 ocio_transform.setInterpolation(ocio.Constants.INTERP_BEST)
178 if 'direction' in transform:
179 ocio_transform.setDirection(
180 direction_options[transform['direction']])
182 ocio_transforms.append(ocio_transform)
185 elif transform['type'] == 'matrix':
186 ocio_transform = ocio.MatrixTransform()
187 # `MatrixTransform` member variables can't be initialized directly,
188 # each must be set individually.
189 ocio_transform.setMatrix(transform['matrix'])
191 if 'offset' in transform:
192 ocio_transform.setOffset(transform['offset'])
194 if 'direction' in transform:
195 ocio_transform.setDirection(
196 direction_options[transform['direction']])
198 ocio_transforms.append(ocio_transform)
200 # *exponent* transform
201 elif transform['type'] == 'exponent':
202 ocio_transform = ocio.ExponentTransform()
204 if 'value' in transform:
205 ocio_transform.setValue(transform['value'])
207 ocio_transforms.append(ocio_transform)
210 elif transform['type'] == 'log':
211 ocio_transform = ocio.LogTransform()
213 if 'base' in transform:
214 ocio_transform.setBase(transform['base'])
216 if 'direction' in transform:
217 ocio_transform.setDirection(
218 direction_options[transform['direction']])
220 ocio_transforms.append(ocio_transform)
222 # *colorspace* transform
223 elif transform['type'] == 'colorspace':
224 ocio_transform = ocio.ColorSpaceTransform()
226 if 'src' in transform:
227 ocio_transform.setSrc(transform['src'])
229 if 'dst' in transform:
230 ocio_transform.setDst(transform['dst'])
232 if 'direction' in transform:
233 ocio_transform.setDirection(
234 direction_options[transform['direction']])
236 ocio_transforms.append(ocio_transform)
239 elif transform['type'] == 'look':
240 ocio_transform = ocio.LookTransform()
241 if 'look' in transform:
242 ocio_transform.setLooks(transform['look'])
244 if 'src' in transform:
245 ocio_transform.setSrc(transform['src'])
247 if 'dst' in transform:
248 ocio_transform.setDst(transform['dst'])
250 if 'direction' in transform:
251 ocio_transform.setDirection(
252 direction_options[transform['direction']])
254 ocio_transforms.append(ocio_transform)
258 print('Ignoring unknown transform type : %s' % transform['type'])
260 if len(ocio_transforms) > 1:
261 group_transform = ocio.GroupTransform()
262 for transform in ocio_transforms:
263 group_transform.push_back(transform)
264 transform = group_transform
266 transform = ocio_transforms[0]
271 def add_colorspace_aliases(config,
272 reference_colorspace,
274 colorspace_alias_names,
277 Adds given colorspace aliases to the *OCIO* config.
282 *OCIO* configuration.
283 reference_colorspace : Colorspace
284 Reference colorspace.
285 colorspace : Colorspace
286 Colorspace to set the aliases into the *OCIO* config.
296 for alias_name in colorspace_alias_names:
297 if alias_name.lower() == colorspace.name.lower():
298 print('Skipping alias creation for %s, alias %s, '
299 'because lower cased names match' % (
300 colorspace.name, alias_name))
303 print('Adding alias colorspace space %s, alias to %s' % (
304 alias_name, colorspace.name))
306 compact_family_name = family
308 description = colorspace.description
309 if colorspace.aces_transform_id:
311 '\n\nACES Transform ID : %s' % colorspace.aces_transform_id)
313 ocio_colorspace_alias = ocio.ColorSpace(
315 bitDepth=colorspace.bit_depth,
316 description=description,
317 equalityGroup=colorspace.equality_group,
318 family=compact_family_name,
319 isData=colorspace.is_data,
320 allocation=colorspace.allocation_type,
321 allocationVars=colorspace.allocation_vars)
323 if colorspace.to_reference_transforms:
324 print('\tGenerating To-Reference transforms')
325 ocio_transform = create_ocio_transform(
326 [{'type': 'colorspace',
327 'src': colorspace.name,
328 'dst': reference_colorspace.name,
329 'direction': 'forward'}])
330 ocio_colorspace_alias.setTransform(
332 ocio.Constants.COLORSPACE_DIR_TO_REFERENCE)
334 if colorspace.from_reference_transforms:
335 print('\tGenerating From-Reference transforms')
336 ocio_transform = create_ocio_transform(
337 [{'type': 'colorspace',
338 'src': reference_colorspace.name,
339 'dst': colorspace.name,
340 'direction': 'forward'}])
341 ocio_colorspace_alias.setTransform(
343 ocio.Constants.COLORSPACE_DIR_FROM_REFERENCE)
345 config.addColorSpace(ocio_colorspace_alias)
354 Adds given look to the *OCIO* config.
359 *OCIO* configuration.
361 Look description: {'name', 'colorspace', 'lut', 'cccid'}
362 custom_lut_dir : str or unicode
363 Directory to copy the look lut into.
364 reference_name : str or unicode
367 Colorspaces and transforms converting between those colorspaces and
368 the reference colorspace, *ACES*.
376 look_name, look_colorspace, look_lut, look_cccid = unpack_default(look, 4)
378 print('Adding look %s - %s' % (look_name, ', '.join(look)))
380 # Copy *look LUT* if `custom_lut_dir` is provided.
382 if '$' not in look_lut:
383 print('Getting ready to copy look lut : %s' % look_lut)
384 shutil.copy2(look_lut, custom_lut_dir)
385 look_lut = os.path.split(look_lut)[1]
387 print('Skipping LUT copy because path contains a context variable')
389 print('Adding look to config')
390 ocio_look = ocio.Look()
391 ocio_look.setName(look_name)
392 ocio_look.setProcessSpace(look_colorspace)
394 keys = {'type': 'lutFile',
396 'direction': 'forward'}
398 keys['cccid'] = look_cccid
400 ocio_transform = create_ocio_transform([keys])
401 ocio_look.setTransform(ocio_transform)
403 config.addLook(ocio_look)
405 print('Creating aliased colorspace')
407 # Creating *OCIO* colorspace referencing the look:
408 # - Needed for implementations that don't process looks properly.
409 # - Needed for implementations that don't expose looks properly.
410 look_aliases = ['look_%s' % compact(look_name)]
411 colorspace = ColorSpace(look_name,
412 aliases=look_aliases,
413 description='The %s Look colorspace' % look_name,
416 colorspace.from_reference_transforms = [{'type': 'look',
418 'src': reference_name,
419 'dst': reference_name,
420 'direction': 'forward'}]
422 print('Adding colorspace %s, alias to look %s to config data' % (
423 look_name, look_name))
425 config_data['colorSpaces'].append(colorspace)
430 def add_looks_to_views(looks,
433 multiple_displays=False):
435 Integrates a set of looks into the *OCIO* config's Displays and Views
439 looks : array of str or unicode
441 reference_name : str or unicode
442 The name of the *OCIO* reference colorspace
444 Colorspaces and transforms converting between those colorspaces and
445 the reference colorspace, *ACES*.
446 multiple_displays : bool
447 If true, looks are added to the config_data looks list
448 If false, looks are integrated directly into the list of displays and
449 views. This may be necessary due to limitations of some applications'
450 currently implementation of OCIO, ex. Maya 2016.
456 look_names = [look[0] for look in looks]
459 # - Adding a *look* per *Display*.
460 # - Assuming there is a *Display* for each *ACES* *Output Transform*.
461 if multiple_displays:
462 for look_name in look_names:
463 config_data['looks'].append(look_name)
466 # - Copy each *Output Transform* colorspace.
467 # - For each copy, add a *LookTransform* to the head of the
468 # `from_reference` transform list.
469 # - Add these the copy colorspaces for the *Displays* / *Views*.
471 for display, view_list in config_data['displays'].iteritems():
473 look_names_string = ''
474 for view_name, output_colorspace in view_list.iteritems():
475 if view_name == 'Output Transform':
477 print('Adding new View that incorporates looks')
479 colorspace_c = copy.deepcopy(output_colorspace)
481 for i, look_name in enumerate(look_names):
482 look_name = look_names[i]
484 # Add the `LookTransform` to the head of the
485 # `from_reference` transform list.
486 if colorspace_c.from_reference_transforms:
487 colorspace_c.from_reference_transforms.insert(
491 'src': reference_name,
492 'dst': reference_name,
493 'direction': 'forward'})
495 # Add the `LookTransform` to the end of
496 # the `to_reference` transform list.
497 if colorspace_c.to_reference_transforms:
498 inverse_look_name = look_names[
499 len(look_names) - 1 - i]
501 colorspace_c.to_reference_transforms.append(
503 'look': inverse_look_name,
504 'src': reference_name,
505 'dst': reference_name,
506 'direction': 'inverse'})
508 if look_name not in config_data['looks']:
509 config_data['looks'].append(look_name)
511 look_names_string = ', '.join(look_names)
512 colorspace_c.name = '%s with %s' % (
513 output_colorspace.name, look_names_string)
514 colorspace_c.aliases = [
515 'out_%s' % compact(colorspace_c.name)]
517 print('Colorspace that incorporates looks '
518 'created : %s' % colorspace_c.name)
520 config_data['colorSpaces'].append(colorspace_c)
523 print('Adding colorspace that incorporates looks '
526 # Updating the *View* name.
527 view_list['Output Transform with %s' % look_names_string] = (
529 config_data['displays'][display] = view_list
532 def create_config(config_data,
535 multiple_displays=False,
537 custom_lut_dir=None):
539 Create the *OCIO* config based on the configuration data
544 Colorspaces and transforms converting between those colorspaces and
545 the reference colorspace, *ACES*, along with other data needed to
546 generate a complete *OCIO* configuration
547 aliases : bool, optional
548 Whether or not to include Alias colorspaces
549 prefix : bool, optional
550 Whether or not to prefix the colorspace names with their Family names
551 multiple_displays : bool, optional
552 Whether to create a single display named *ACES* with Views for each
553 Output Transform or multiple displays, one for each Output Transform
554 look_info : array of str or unicode, optional
555 Paths and names for look data
556 custom_lut_dir : str or unicode, optional
557 Directory to use for storing custom look files
562 The constructed OCIO configuration
565 if look_info is None:
569 alias_colorspaces = []
571 config = ocio.Config()
573 config.setDescription('An ACES config generated from python')
575 search_path = ['luts']
577 search_path.append('custom')
578 config.setSearchPath(':'.join(search_path))
580 reference_data = config_data['referenceColorSpace']
582 # Adding the colorspace *Family* into the name which helps with
583 # applications that presenting colorspaces as one a flat list.
585 prefixed_name = colorspace_prefixed_name(reference_data)
586 prefixed_names[reference_data.name] = prefixed_name
587 reference_data.name = prefixed_name
589 print('Adding the reference color space : %s' % reference_data.name)
591 reference = ocio.ColorSpace(
592 name=reference_data.name,
593 bitDepth=reference_data.bit_depth,
594 description=reference_data.description,
595 equalityGroup=reference_data.equality_group,
596 family=reference_data.family,
597 isData=reference_data.is_data,
598 allocation=reference_data.allocation_type,
599 allocationVars=reference_data.allocation_vars)
601 config.addColorSpace(reference)
604 if reference_data.aliases:
605 # Deferring adding alias colorspaces until end, which helps with
606 # applications listing the colorspaces in the order that they were
607 # defined in the configuration: alias colorspaces are usually named
608 # lower case with spaces but normal colorspaces names are longer
609 # and more verbose, thus it becomes harder for user to visually
610 # parse the list of colorspaces when there are names such as
611 # "crv_canonlog" interspersed with names like
612 # "Input - Canon - Curve - Canon-Log".
613 # Moving the alias colorspace definitions to the end of the
614 # configuration avoids the above problem.
615 alias_colorspaces.append(
616 [reference_data, reference_data, reference_data.aliases])
621 print('Adding looks')
623 config_data['looks'] = []
625 for look in look_info:
632 add_looks_to_views(look_info,
639 print('Adding regular colorspaces')
641 for colorspace in sorted(config_data['colorSpaces'],
642 cmp=lambda x,y: cmp(x.family.lower(), y.family.lower())):
643 # Adding the colorspace *Family* into the name which helps with
644 # applications that presenting colorspaces as one a flat list.
646 prefixed_name = colorspace_prefixed_name(colorspace)
647 prefixed_names[colorspace.name] = prefixed_name
648 colorspace.name = prefixed_name
650 print('Creating new color space : %s' % colorspace.name)
652 description = colorspace.description
653 if colorspace.aces_transform_id:
655 '\n\nACES Transform ID : %s' % colorspace.aces_transform_id)
657 ocio_colorspace = ocio.ColorSpace(
658 name=colorspace.name,
659 bitDepth=colorspace.bit_depth,
660 description=description,
661 equalityGroup=colorspace.equality_group,
662 family=colorspace.family,
663 isData=colorspace.is_data,
664 allocation=colorspace.allocation_type,
665 allocationVars=colorspace.allocation_vars)
667 if colorspace.to_reference_transforms:
668 print('\tGenerating To-Reference transforms')
669 ocio_transform = create_ocio_transform(
670 colorspace.to_reference_transforms)
671 ocio_colorspace.setTransform(
673 ocio.Constants.COLORSPACE_DIR_TO_REFERENCE)
675 if colorspace.from_reference_transforms:
676 print('\tGenerating From-Reference transforms')
677 ocio_transform = create_ocio_transform(
678 colorspace.from_reference_transforms)
679 ocio_colorspace.setTransform(
681 ocio.Constants.COLORSPACE_DIR_FROM_REFERENCE)
683 config.addColorSpace(ocio_colorspace)
686 if colorspace.aliases:
687 # Deferring adding alias colorspaces until end, which helps
688 # with applications listing the colorspaces in the order that
689 # they were defined in the configuration.
690 alias_colorspaces.append(
691 [reference_data, colorspace, colorspace.aliases])
697 # Adding roles early so that alias colorspaces can be created
698 # with roles names before remaining colorspace aliases are added
699 # to the configuration.
700 print('Setting the roles')
705 color_picking=prefixed_names[
706 config_data['roles']['color_picking']],
707 color_timing=prefixed_names[config_data['roles']['color_timing']],
708 compositing_log=prefixed_names[
709 config_data['roles']['compositing_log']],
710 data=prefixed_names[config_data['roles']['data']],
711 default=prefixed_names[config_data['roles']['default']],
712 matte_paint=prefixed_names[config_data['roles']['matte_paint']],
713 reference=prefixed_names[config_data['roles']['reference']],
714 scene_linear=prefixed_names[config_data['roles']['scene_linear']],
715 compositing_linear=prefixed_names[config_data['roles']['scene_linear']],
716 rendering=prefixed_names[config_data['roles']['scene_linear']],
717 texture_paint=prefixed_names[
718 config_data['roles']['texture_paint']])
720 # Add the aliased colorspaces for each role
721 for role_name, role_colorspace_name in config_data['roles'].iteritems():
722 role_colorspace_prefixed_name = prefixed_names[role_colorspace_name]
724 #print( 'Finding colorspace : %s' % role_colorspace_prefixed_name )
725 # Find the colorspace pointed to by the role
726 role_colorspaces = [colorspace
727 for colorspace in config_data['colorSpaces']
728 if colorspace.name == role_colorspace_prefixed_name]
729 role_colorspace = None
730 if len(role_colorspaces) > 0:
731 role_colorspace = role_colorspaces[0]
733 if reference_data.name == role_colorspace_prefixed_name:
734 role_colorspace = reference_data
737 # The alias colorspace shouldn't match the role name exactly
738 role_name_alias1 = "role_%s" % role_name
739 role_name_alias2 = "Role - %s" % role_name
741 print( 'Adding a role colorspace named %s, pointing to %s' % (
742 role_name_alias2, role_colorspace.name))
744 alias_colorspaces.append(
745 (reference_data, role_colorspace, [role_name_alias1]))
747 add_colorspace_aliases(
748 config, reference_data, role_colorspace, [role_name_alias2], 'Roles')
753 color_picking=config_data['roles']['color_picking'],
754 color_timing=config_data['roles']['color_timing'],
755 compositing_log=config_data['roles']['compositing_log'],
756 data=config_data['roles']['data'],
757 default=config_data['roles']['default'],
758 matte_paint=config_data['roles']['matte_paint'],
759 reference=config_data['roles']['reference'],
760 scene_linear=config_data['roles']['scene_linear'],
761 compositing_linear=config_data['roles']['scene_linear'],
762 rendering=config_data['roles']['scene_linear'],
763 texture_paint=config_data['roles']['texture_paint'])
765 # Add the aliased colorspaces for each role
766 for role_name, role_colorspace_name in config_data['roles'].iteritems():
767 # Find the colorspace pointed to by the role
768 role_colorspaces = [colorspace
769 for colorspace in config_data['colorSpaces']
770 if colorspace.name == role_colorspace_name]
771 role_colorspace = None
772 if len(role_colorspaces) > 0:
773 role_colorspace = role_colorspaces[0]
775 if reference_data.name == role_colorspace_name:
776 role_colorspace = reference_data
779 # The alias colorspace shouldn't match the role name exactly
780 role_name_alias1 = "role_%s" % role_name
781 role_name_alias2 = "Role - %s" % role_name
783 print('Adding a role colorspace named %s, pointing to %s' % (
784 role_name_alias2, role_colorspace.name))
786 alias_colorspaces.append(
787 (reference_data, role_colorspace, [role_name_alias1]))
789 add_colorspace_aliases(
790 config, reference_data, role_colorspace, [role_name_alias2], 'Roles')
794 # Adding alias colorspaces at the end as some applications use
795 # colorspaces definitions order of the configuration to order
796 # the colorspaces in their selection lists, some applications
797 # use alphabetical ordering.
798 # This should keep the alias colorspaces out of the way for applications
799 # using the configuration order.
800 print('Adding the alias colorspaces')
801 for reference, colorspace, aliases in alias_colorspaces:
802 add_colorspace_aliases(config, reference, colorspace, aliases)
806 print('Adding the diplays and views')
808 # Setting the *color_picking* role to be the first *Display*'s
809 # *Output Transform* *View*.
810 default_display_name = config_data['defaultDisplay']
811 default_display_views = config_data['displays'][default_display_name]
812 default_display_colorspace = default_display_views['Output Transform']
814 # Defining *Displays* and *Views*.
815 displays, views = [], []
817 # Defining a generic *Display* and *View* setup.
818 if multiple_displays:
819 looks = config_data['looks'] if ('looks' in config_data) else []
820 looks = ', '.join(looks)
821 print('Creating multiple displays, with looks : %s' % looks)
823 # *Displays* are not reordered to put the *defaultDisplay* first
824 # because *OCIO* will order them alphabetically when the configuration
825 # is written to disk.
826 for display, view_list in config_data['displays'].iteritems():
827 for view_name, colorspace in view_list.iteritems():
828 config.addDisplay(display, view_name, colorspace.name, looks)
829 if 'Output Transform' in view_name and looks != '':
830 # *Views* without *Looks*.
831 config.addDisplay(display, view_name, colorspace.name)
833 # *Views* with *Looks*.
834 view_name_with_looks = '%s with %s' % (view_name, looks)
835 config.addDisplay(display, view_name_with_looks,
836 colorspace.name, looks)
838 config.addDisplay(display, view_name, colorspace.name)
839 if not (view_name in views):
840 views.append(view_name)
841 displays.append(display)
843 # *Displays* and *Views* useful in a *GUI* context.
845 single_display_name = 'ACES'
846 displays.append(single_display_name)
848 # Ensuring the *defaultDisplay* is first.
849 display_names = sorted(config_data['displays'])
850 display_names.insert(0, display_names.pop(
851 display_names.index(default_display_name)))
853 looks = config_data['looks'] if ('looks' in config_data) else []
854 look_names = ', '.join(looks)
856 displays_views_colorspaces = []
858 for display in display_names:
859 view_list = config_data['displays'][display]
860 for view_name, colorspace in view_list.iteritems():
861 if 'Output Transform' in view_name:
863 # We use the *Display* names as the *View* names in this
864 # case as there is a single *Display* containing all the
866 # This works for more applications than not,as of the time
867 # of this implementation.
869 # Autodesk Maya 2016 doesn't support parentheses in
871 sanitised_display = replace(display, {')': '', '(': ''})
873 # *View* with *Looks*.
874 if 'with' in view_name:
875 sanitised_display = '%s with %s' % (
876 sanitised_display, look_names)
878 views_with_looks_at_end = False
879 # Storing combo of *Display*, *View* and *Colorspace*
880 # name so they can be added to the end of the list.
881 if views_with_looks_at_end:
882 displays_views_colorspaces.append(
883 [single_display_name, sanitised_display,
886 config.addDisplay(single_display_name,
890 if not (sanitised_display in views):
891 views.append(sanitised_display)
893 # *View* without *Looks*.
895 config.addDisplay(single_display_name,
899 if not (sanitised_display in views):
900 views.append(sanitised_display)
902 # Adding to the configuration any *Display*, *View* combinations that
903 # were saved for later.
904 # This list should be empty unless `views_with_looks_at_end` is
906 for display_view_colorspace in displays_views_colorspaces:
907 single_display_name, sanitised_display, colorspace_name = (
908 display_view_colorspace)
910 config.addDisplay(single_display_name,
914 if not (sanitised_display in views):
915 views.append(sanitised_display)
917 raw_display_space_name = config_data['roles']['data']
918 log_display_space_name = config_data['roles']['compositing_log']
921 raw_display_space_name = prefixed_names[raw_display_space_name]
922 log_display_space_name = prefixed_names[log_display_space_name]
924 config.addDisplay(single_display_name, 'Raw', raw_display_space_name)
926 config.addDisplay(single_display_name, 'Log', log_display_space_name)
929 config.setActiveDisplays(','.join(sorted(displays)))
930 config.setActiveViews(','.join(views))
934 # Ensuring the configuration is valid.
937 # Resetting colorspace names to their non-prefixed versions.
939 prefixed_names_inverse = {}
940 for original, prefixed in prefixed_names.iteritems():
941 prefixed_names_inverse[prefixed] = original
943 reference_data.name = prefixed_names_inverse[reference_data.name]
946 for colorspace in config_data['colorSpaces']:
947 colorspace.name = prefixed_names_inverse[colorspace.name]
949 print('Error with Prefixed names')
950 for original, prefixed in prefixed_names.iteritems():
951 print('%s, %s' % (original, prefixed))
955 print('Inverse Lookup of Prefixed names')
956 for prefixed, original in prefixed_names_inverse.iteritems():
957 print('%s, %s' % (prefixed, original))
963 def create_config_data(odt_info,
968 lut_resolution_1d=4096,
969 lut_resolution_3d=64,
972 Create the *ACES* LUTs and data structures needed for later *OCIO*
973 configuration generation
977 odt_info : array of dicts of str or unicode
978 Descriptions of the *ACES* Output Transforms
979 lmt_info : array of dicts of str or unicode
980 Descriptions of the *ACES* Look Transforms
981 shaper_name : str or unicode
982 The name of the Shaper function to use when generating LUTs.
983 Options: Log2, DolbyPQ
984 aces_ctl_directory : str or unicode
985 The path to the aces 'transforms/ctl/utilities'
986 lut_directory : str or unicode
987 The path to use when writing LUTs
988 lut_resolution_1d : int, optional
989 The resolution of generated 1D LUTs
990 lut_resolution_3d : int, optional
991 The resolution of generated 3D LUTs
993 Whether or not to clean up the intermediate images
998 Colorspaces, LUT paths and transforms converting between those
999 colorspaces and the reference colorspace, *ACES*.
1002 print('create_config_data - begin')
1005 config_data['displays'] = {}
1006 config_data['colorSpaces'] = []
1008 # -------------------------------------------------------------------------
1009 # *ACES Color Spaces*
1010 # -------------------------------------------------------------------------
1012 # *ACES* colorspaces
1016 aces_log_display_space,
1018 aces_default_display) = aces.create_colorspaces(aces_ctl_directory,
1027 config_data['referenceColorSpace'] = aces_reference
1028 config_data['roles'] = aces_roles
1030 for cs in aces_colorspaces:
1031 config_data['colorSpaces'].append(cs)
1033 for name, data in aces_displays.iteritems():
1034 config_data['displays'][name] = data
1036 config_data['defaultDisplay'] = aces_default_display
1037 config_data['linearDisplaySpace'] = aces_reference
1038 config_data['logDisplaySpace'] = aces_log_display_space
1040 # -------------------------------------------------------------------------
1041 # *Camera Input Transforms*
1042 # -------------------------------------------------------------------------
1044 # *ARRI Log-C* to *ACES*
1045 arri_colorspaces = arri.create_colorspaces(lut_directory,
1047 for cs in arri_colorspaces:
1048 config_data['colorSpaces'].append(cs)
1050 # *Canon-Log* to *ACES*
1051 canon_colorspaces = canon.create_colorspaces(lut_directory,
1053 for cs in canon_colorspaces:
1054 config_data['colorSpaces'].append(cs)
1056 # *GoPro Protune* to *ACES*
1057 gopro_colorspaces = gopro.create_colorspaces(lut_directory,
1059 for cs in gopro_colorspaces:
1060 config_data['colorSpaces'].append(cs)
1062 # *Panasonic V-Log* to *ACES*
1063 panasonic_colorspaces = panasonic.create_colorspaces(lut_directory,
1065 for cs in panasonic_colorspaces:
1066 config_data['colorSpaces'].append(cs)
1068 # *Panasonic GH4* to *ACES*
1069 panasonic_gh4_colorspaces = panasonic_gh4.create_colorspaces(
1070 lut_directory, lut_resolution_1d)
1071 for cs in panasonic_gh4_colorspaces:
1072 config_data['colorSpaces'].append(cs)
1074 # *RED* colorspaces to *ACES*
1075 red_colorspaces = red.create_colorspaces(lut_directory,
1077 for cs in red_colorspaces:
1078 config_data['colorSpaces'].append(cs)
1081 sony_colorspaces = sony.create_colorspaces(lut_directory,
1083 for cs in sony_colorspaces:
1084 config_data['colorSpaces'].append(cs)
1086 # -------------------------------------------------------------------------
1087 # General Colorspaces
1088 # -------------------------------------------------------------------------
1089 general_colorspaces = general.create_colorspaces(lut_directory,
1091 for cs in general_colorspaces:
1092 config_data['colorSpaces'].append(cs)
1094 # The *Raw* colorspace
1095 raw = general.create_raw()
1096 config_data['colorSpaces'].append(raw)
1098 # Overriding various roles
1099 config_data['roles']['data'] = raw.name
1100 config_data['roles']['reference'] = raw.name
1101 config_data['roles']['texture_paint'] = raw.name
1103 print('create_config_data - end')
1108 def write_config(config, config_path, sanity_check=True):
1110 Writes the configuration to given path.
1115 *OCIO* configuration.
1116 config_path : str or unicode
1117 Path to write the configuration path.
1119 Performs configuration sanity checking prior to writing it on disk.
1129 config.sanityCheck()
1130 except Exception, e:
1132 print 'Configuration was not written due to a failed Sanity Check'
1135 with open(config_path, mode='w') as fp:
1136 fp.write(config.serialize())
1139 def generate_baked_LUTs(odt_info,
1144 lut_resolution_shaper=1024,
1147 Generate baked representations of the transforms from the *ACES* *OCIO*
1152 odt_info : array of dicts of str or unicode
1153 Descriptions of the *ACES* Output Transforms
1154 shaper_name : str or unicode
1155 The name of the Shaper function to use when generating LUTs.
1156 Options: Log2, DolbyPQ
1157 baked_directory : str or unicode
1158 The path to use when writing baked LUTs
1159 config_path : str or unicode
1160 The path to the *OCIO* configuration
1161 lut_resolution_3d : int, optional
1162 The resolution of generated 3D LUTs
1163 lut_resolution_shaper : int, optional
1164 The resolution of shaper used as part of some 3D LUTs
1165 prefix : bool, optional
1166 Whether or not colorspace names will use their Family names as prefixes
1167 in the *OCIO* config
1174 odt_info_C = dict(odt_info)
1176 # Older behavior for *ODTs* that have support for full and legal ranges,
1177 # generating a LUT for both ranges.
1179 # Create two entries for ODTs that have full and legal range support
1180 for odt_ctl_name, odt_values in odt_info.iteritems():
1181 if odt_values['transformHasFullLegalSwitch']:
1182 odt_name = odt_values['transformUserName']
1184 odt_values_legal = dict(odt_values)
1185 odt_values_legal['transformUserName'] = '%s - Legal' % odt_name
1186 odt_info_C['%s - Legal' % odt_ctl_name] = odt_values_legal
1188 odt_values_full = dict(odt_values)
1189 odt_values_full['transformUserName'] = '%s - Full' % odt_name
1190 odt_info_C['%s - Full' % odt_ctl_name] = odt_values_full
1192 del (odt_info_C[odt_ctl_name])
1195 for odt_ctl_name, odt_values in odt_info_C.iteritems():
1196 odt_prefix = odt_values['transformUserNamePrefix']
1197 odt_name = odt_values['transformUserName']
1199 if odt_name in ['P3-D60 ST2048 (1000 nits)', 'Rec.2020 ST2048 (1000 nits)']:
1200 odt_shaper = shaper_name.replace("48 nits", "1000 nits")
1201 elif odt_name in ['P3-D60 ST2048 (2000 nits)']:
1202 odt_shaper = shaper_name.replace("48 nits", "2000 nits")
1203 elif odt_name in ['P3-D60 ST2048 (4000 nits)']:
1204 odt_shaper = shaper_name.replace("48 nits", "4000 nits")
1206 odt_shaper = shaper_name
1209 for input_space in ['ACEScc', 'ACESproxy']:
1210 args = ['--iconfig', config_path,
1213 args += ['--inputspace', 'ACES - %s' % input_space]
1214 args += ['--outputspace', 'Output - %s' % odt_name]
1216 args += ['--inputspace', input_space]
1217 args += ['--outputspace', odt_name]
1219 args += ['--description',
1220 '%s - %s for %s data' % (odt_prefix,
1224 args += ['--shaperspace', 'Utility - %s' % odt_shaper,
1225 '--shapersize', str(lut_resolution_shaper)]
1227 args += ['--shaperspace', odt_shaper,
1228 '--shapersize', str(lut_resolution_shaper)]
1229 args += ['--cubesize', str(lut_resolution_3d)]
1230 args += ['--format',
1232 os.path.join(baked_directory,
1234 '%s for %s.icc' % (odt_name, input_space))]
1236 bake_lut = Process(description='bake a LUT',
1242 for input_space in ['ACEScc', 'ACESproxy']:
1243 args = ['--iconfig', config_path,
1246 args += ['--inputspace', 'ACES - %s' % input_space]
1247 args += ['--outputspace', 'Output - %s' % odt_name]
1249 args += ['--inputspace', input_space]
1250 args += ['--outputspace', odt_name]
1251 args += ['--description',
1252 '%s - %s for %s data' % (
1253 odt_prefix, odt_name, input_space)]
1255 args += ['--shaperspace', 'Utility - %s' % odt_shaper,
1256 '--shapersize', str(lut_resolution_shaper)]
1258 args += ['--shaperspace', odt_shaper,
1259 '--shapersize', str(lut_resolution_shaper)]
1260 args += ['--cubesize', str(lut_resolution_3d)]
1262 fargs = ['--format',
1267 '%s for %s Flame.3dl' % (odt_name, input_space))]
1268 bake_lut = Process(description='bake a LUT',
1270 args=(args + fargs))
1273 largs = ['--format',
1278 '%s for %s Lustre.3dl' % (odt_name, input_space))]
1279 bake_lut = Process(description='bake a LUT',
1281 args=(args + largs))
1285 for input_space in ['ACEScg', 'ACES2065-1']:
1286 args = ['--iconfig', config_path,
1289 args += ['--inputspace', 'ACES - %s' % input_space]
1290 args += ['--outputspace', 'Output - %s' % odt_name]
1292 args += ['--inputspace', input_space]
1293 args += ['--outputspace', odt_name]
1294 args += ['--description',
1295 '%s - %s for %s data' % (
1296 odt_prefix, odt_name, input_space)]
1297 if input_space == 'ACEScg':
1298 lin_shaper_name = '%s - AP1' % odt_shaper
1300 lin_shaper_name = odt_shaper
1302 lin_shaper_name = 'Utility - %s' % lin_shaper_name
1303 args += ['--shaperspace', lin_shaper_name,
1304 '--shapersize', str(lut_resolution_shaper)]
1306 args += ['--cubesize', str(lut_resolution_3d)]
1308 margs = ['--format',
1313 '%s for %s Maya.csp' % (odt_name, input_space))]
1314 bake_lut = Process(description='bake a LUT',
1316 args=(args + margs))
1319 hargs = ['--format',
1324 '%s for %s Houdini.lut' % (odt_name, input_space))]
1325 bake_lut = Process(description='bake a LUT',
1327 args=(args + hargs))
1331 def generate_config_directory(config_directory,
1332 bake_secondary_luts=False,
1333 custom_lut_dir=None):
1335 Create the directories needed for configuration generation
1339 config_directory : str or unicode
1340 The base config directory
1341 bake_secondary_luts : bool, optional
1342 Whether or not to create directories for baked LUTs
1343 custom_lut_dir : bool, optional
1344 Whether or not to create directories for custom Look LUTs
1351 lut_directory = os.path.join(config_directory, 'luts')
1352 dirs = [config_directory, lut_directory]
1354 if bake_secondary_luts:
1355 dirs.extend([os.path.join(config_directory, 'baked'),
1356 os.path.join(config_directory, 'baked', 'flame'),
1357 os.path.join(config_directory, 'baked', 'photoshop'),
1358 os.path.join(config_directory, 'baked', 'houdini'),
1359 os.path.join(config_directory, 'baked', 'lustre'),
1360 os.path.join(config_directory, 'baked', 'maya')])
1363 dirs.append(os.path.join(config_directory, 'custom'))
1366 not os.path.exists(d) and os.mkdir(d)
1368 return lut_directory
1371 def generate_config(aces_ctl_directory,
1373 lut_resolution_1d=4096,
1374 lut_resolution_3d=64,
1375 bake_secondary_luts=True,
1376 multiple_displays=False,
1378 copy_custom_luts=True,
1380 prefix_colorspaces_with_family_names=True,
1381 shaper_base_name='Log2'):
1383 Generates LUTs, matrices and configuration data and then creates the
1384 *ACES* configuration.
1388 aces_ctl_directory : str or unicode
1389 The path to the aces 'transforms/ctl/utilities'
1390 config_directory : str or unicode
1391 The directory that will hold the generated configuration and LUTs
1392 lut_resolution_1d : int, optional
1393 The resolution of generated 1D LUTs
1394 lut_resolution_3d : int, optional
1395 The resolution of generated 3D LUTs
1396 bake_secondary_luts : bool, optional
1397 Whether or not to create directories for baked LUTs
1398 multiple_displays : bool, optional
1399 Whether to create a single display named *ACES* with Views for each
1400 Output Transform or multiple displays, one for each Output Transform
1401 look_info : array of str or unicode, optional
1402 Paths and names for look data
1403 copy_custom_luts : bool, optional
1404 Whether to reference custom look LUTs directly or to copy them into a
1405 directory within the generated configuration
1406 cleanup : bool, optional
1407 Whether or not to clean up the intermediate images
1408 prefix_colorspaces_with_family_names : bool, optional
1409 Whether or not colorspace names will use their Family names as prefixes
1410 in the *OCIO* config
1411 shaper_base_name : str or unicode
1412 The name of the Shaper function to use when generating LUTs.
1413 Options: Log2, DolbyPQ
1418 Success or failure of configuration generation process
1421 if look_info is None:
1424 custom_lut_dir = None
1425 if copy_custom_luts:
1426 custom_lut_dir = os.path.join(config_directory, 'custom')
1428 lut_directory = generate_config_directory(config_directory,
1429 bake_secondary_luts,
1431 odt_info = aces.get_ODTs_info(aces_ctl_directory)
1432 lmt_info = aces.get_LMTs_info(aces_ctl_directory)
1434 if shaper_base_name == 'DolbyPQ':
1435 shaper_name = 'Dolby PQ 48 nits Shaper'
1437 shaper_name = 'Log2 48 nits Shaper'
1439 config_data = create_config_data(odt_info,
1448 print('Creating config - with prefixes, with aliases')
1449 config = create_config(config_data,
1450 prefix=prefix_colorspaces_with_family_names,
1452 multiple_displays=multiple_displays,
1453 look_info=look_info,
1454 custom_lut_dir=custom_lut_dir)
1457 write_config(config,
1458 os.path.join(config_directory, 'config.ocio'))
1460 if bake_secondary_luts:
1461 generate_baked_LUTs(odt_info,
1463 os.path.join(config_directory, 'baked'),
1464 os.path.join(config_directory, 'config.ocio'),
1467 prefix=prefix_colorspaces_with_family_names)
1474 A simple main that allows the user to exercise the various functions
1475 defined in this file
1488 usage = '%prog [options]\n'
1490 usage += 'An OCIO config generation script for ACES 1.0.1\n'
1492 usage += 'Command-line examples'
1494 usage += ('Create a GUI-friendly ACES 1.0.1 config with no secondary, '
1496 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1497 '--lutResolution1d 4096 --lutResolution3d 65 -c aces_1.0.1 '
1498 '--dontBakeSecondaryLUTs')
1500 usage += 'Create a more OCIO-compliant ACES 1.0.1 config: \n'
1501 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1502 '--lutResolution1d 4096 --lutResolution3d 65 -c aces_1.0.1 '
1503 '--createMultipleDisplays')
1506 usage += 'Adding custom looks'
1508 usage += ('Create a GUI-friendly ACES 1.0.1 config with an ACES-style CDL '
1509 '(will be applied in the ACEScc colorspace): \n')
1510 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1511 '--lutResolution1d 4096 --lutResolution3d 65 -c aces_1.0.1 '
1512 '\n\t\t--addACESLookCDL ACESCDLName '
1513 '/path/to/SampleCDL.ccc cc03345')
1515 usage += 'Create a GUI-friendly ACES 1.0.1 config with a general CDL: \n'
1516 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1517 '--lutResolution1d 4096 --lutResolution3d 65 -c aces_1.0.1 '
1518 '\n\t\t--addCustomLookCDL CustomCDLName "ACES - ACEScc" '
1519 '/path/to/SampleCDL.ccc cc03345')
1521 usage += ('\tIn this example, the CDL will be applied in the '
1522 'ACEScc colorspace, but the user could choose other spaces '
1523 'by changing the argument after the name of the look. \n')
1525 usage += ('Create a GUI-friendly ACES 1.0.1 config with an ACES-style LUT '
1526 '(will be applied in the ACEScc colorspace): \n')
1527 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1528 '--lutResolution1d 4096 --lutResolution3d 65 -c aces_1.0.1 '
1529 '\n\t\t--addACESLookLUT ACESLUTName '
1530 '/path/to/SampleCDL.ccc cc03345')
1532 usage += 'Create a GUI-friendly ACES 1.0.1 config with a general LUT: \n'
1533 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1534 '--lutResolution1d 4096 --lutResolution3d 65 -c aces_1.0.1 '
1535 '\n\t\t--addCustomLookLUT CustomLUTName "ACES - ACEScc" '
1536 '/path/to/SampleCDL.ccc cc03345')
1538 usage += ('\tIn this example, the LUT will be applied in the '
1539 'ACEScc colorspace, but the user could choose other spaces '
1540 'by changing the argument after the name of the look. \n')
1542 usage += ('Create a GUI-friendly ACES 1.0.1 config using the Dolby PQ '
1543 'transfer function as the shaper: \n')
1544 usage += ('\tcreate_aces_config -a /path/to/aces-dev/transforms/ctl '
1545 '--lutResolution1d 4096 --lutResolution3d 65 -c aces_1.0.1 '
1551 def look_info_callback(option, opt_str, value, parser):
1552 print('look_info_callback')
1553 print(option, opt_str, value, parser)
1554 if opt_str == '--addCustomLookCDL':
1555 look_info.append(value)
1556 elif opt_str == '--addCustomLookLUT':
1557 look_info.append(value)
1558 elif opt_str == '--addACESLookCDL':
1559 look_info.append([value[0], 'ACES - ACEScc', value[1], value[2]])
1560 elif opt_str == '--addACESLookLUT':
1561 look_info.append([value[0], 'ACES - ACEScc', value[1]])
1563 p = optparse.OptionParser(description='',
1564 prog='create_aces_config',
1565 version='create_aces_config 1.0',
1567 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1568 ACES_OCIO_CTL_DIRECTORY_ENVIRON, None))
1569 p.add_option('--configDir', '-c', default=os.environ.get(
1570 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON, None))
1571 p.add_option('--lutResolution1d', default=4096)
1572 p.add_option('--lutResolution3d', default=64)
1573 p.add_option('--dontBakeSecondaryLUTs', action='store_true', default=False)
1574 p.add_option('--keepTempImages', action='store_true', default=False)
1576 p.add_option('--createMultipleDisplays', action='store_true',
1579 p.add_option('--addCustomLookLUT', '', type='string', nargs=3,
1580 action='callback', callback=look_info_callback)
1581 p.add_option('--addCustomLookCDL', '', type='string', nargs=4,
1582 action='callback', callback=look_info_callback)
1583 p.add_option('--addACESLookLUT', '', type='string', nargs=2,
1584 action='callback', callback=look_info_callback)
1585 p.add_option('--addACESLookCDL', '', type='string', nargs=3,
1586 action='callback', callback=look_info_callback)
1587 p.add_option('--copyCustomLUTs', action='store_true', default=False)
1589 p.add_option('--shaper', '-s', default='Log2')
1591 options, arguments = p.parse_args()
1593 aces_ctl_directory = options.acesCTLDir
1594 config_directory = options.configDir
1595 lut_resolution_1d = int(options.lutResolution1d)
1596 lut_resolution_3d = int(options.lutResolution3d)
1597 bake_secondary_luts = not options.dontBakeSecondaryLUTs
1598 cleanup_temp_images = not options.keepTempImages
1599 multiple_displays = options.createMultipleDisplays
1600 copy_custom_luts = options.copyCustomLUTs
1601 shaper_base_name = options.shaper
1606 print('command line : \n%s\n' % ' '.join(sys.argv))
1608 assert aces_ctl_directory is not None, (
1609 'process: No "{0}" environment variable defined or no "ACES CTL" '
1610 'directory specified'.format(
1611 ACES_OCIO_CTL_DIRECTORY_ENVIRON))
1613 assert config_directory is not None, (
1614 'process: No "{0}" environment variable defined or no configuration '
1615 'directory specified'.format(
1616 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON))
1618 return generate_config(aces_ctl_directory,
1622 bake_secondary_luts,
1626 cleanup_temp_images,
1631 if __name__ == '__main__':