Extract EXR Layers to Images

Problem

You rendered a sequence of multi-layer EXR and you need to work with each layer separately. You want to extract each layer and save each of them as a separate EXR image file. In addition, you would like Draft to identify each layer based on your multi-layer EXR’s channel names and to write each layer to file automatically with a meaningful filename.

Solution

import Draft
from DraftParamParser import *

startFrame = 1
endFrame = 100
inFilePattern = "//path/to/multi-layer####.exr"
outFilePattern = "//path/to/output.exr"

# Define a dictionary holding channel name equivalences
channelMap = { 'red': 'R', 'green': 'G', 'blue': 'B', 'alpha': 'A',
               'r': 'R', 'g': 'G', 'b': 'B', 'a': 'A' }

# Get the first frame's channel names
currFile = ReplaceFilenameHashesWithNumber( inFilePattern, 1 )
firstFrame = Draft.Image.ReadFromFile( currFile )
channelNames = firstFrame.GetChannelNames()

# Create a dictionary of layers,
# with the layer name as the key, and the value as the list of channels
layers = {}
for name in channelNames:
    # Specific layer
    if string.rfind( name, '.' ) >= 0:
        ( layer, channel ) = string.rsplit( name, '.', 1 )
    # Basic RGB(A) layer
    else:
        ( layer, channel ) = ( None, name )
    if channel.lower() in channelMap:
        if layer in layers:
            layers[layer].append( channel )
        else:
            layers[layer] = [channel]
    else:
        print "Ignoring the following layer.channel: ", name, " (channel not recognized as RGBA)"


# Process each of the frames in the list of frames
for currFrame in range ( startFrame, endFrame + 1 ):
    # Read in the frame.
    currFile = ReplaceFilenameHashesWithNumber( inFilePattern, currFrame )
    frame = Draft.Image.ReadFromFile( currFile )

    # Extract the layers from the image, saving each as a separate image.
    for ( layer, channels ) in layers.items():
        prefix = ''
        if layer is not None:
            prefix = layer + '.'
        channelList = [ prefix + channel for channel in channels ]
        imgLayer = Draft.Image.CreateImage( frame.width, frame.height, channelList )
        imgLayer.Copy( frame, channels = channelList )

        # Rename the channels to RGB(A).
        for channel in channels:
            if prefix + channel != channelMap[channel.lower()]:
                imgLayer.RenameChannel( prefix + channel, channelMap[channel.lower()] )

        # Add the layer name to the filename.
        currOutFile = ReplaceFilenameHashesWithNumber( outFilePattern, currFrame )
        if layer is not None:
            # Specific layer
            if string.rfind( currOutFile, '_' ) >= 0:
                ( first, second ) = string.rsplit( currOutFile, '_', 1 )
                currOutFile = first + '_' + layer + '_' + second
            # Basic RGB(A) layer
            else:
                ( root, ext ) = os.path.splitext( currOutFile )
                currOutFile = root + '_' + layer + ext

        # Write the layer to file as an image
        imgLayer.WriteToFile( currOutFile )

Discussion

In order to be able to extract layers from a wide variety of multi-layer EXR, it’s convenient to first define a dictionary of channel name equivalence:

channelMap = { 'red': 'R', 'green': 'G', 'blue': 'B', 'alpha': 'A',
               'r': 'R', 'g': 'G', 'b': 'B', 'a': 'A' }

In that case, ‘R’, ‘red’ and ‘r’ will be treated as being equivalent. This dictionary can be customized to fit your specific needs.

Then, we can create a dictionary of layers based on the channel names of the first frame. This code snippet assumes that all images in the sequence have the same layers as the first frame but this assumption is not mandatory. You can also create a dictionary of layers for each frame by moving its creation in the main loop.

Once the dictionary of layers is created, we are ready to extract the layers from each frame, saving each one as a separate image. For each layer, we get the corresponding channelList from the dictionary of layers and we use it to extract the layer in the following lines of code:

imgLayer = Draft.Image.CreateImage( frame.width, frame.height, channelList )
imgLayer.Copy( frame, channels = channelList )

Note that the method Copy() only copy over to imgLayer the channels in channelList. This is where the actual layer extraction takes place.

Then, the imgLayer‘s channel names are renamed to RGB(A) and the output filename is renamed to include the current layer’s name. Finally, imgLayer is written to file.

To summarize, a set of images, one image per EXR layer, is written to file for each frame. For instance, let’s suppose the list of channel names in your multi-layer EXR image is:

{Reflect.R, Reflect.G, Reflect.B, Refract.R, Refract.G, Refract.B, Shadow.R, Shadow.G, Shadow.B, R, G, B}

Then, this code snippet will create the following EXR image files associated with the sequence’s first frame:

output_Reflect_0001.exr, output_Refract_0001.exr, output_Shadow_0001.exr, output_0001.exr

where output_0001.exr contains the basic RGB(A) layer. A similar set of images will be written to file for each frame in the sequence.