The OpenEXR Python Module¶
The OpenEXR python module provides basic access to data in EXR imagefiles. The read and write methods use python dictionaries for headermetadata and numpy arrays for pixel data.
To install the OpenEXR module:
%pipinstallOpenEXR
The module is specifically designed for maximum simplicity and ease ofuse, not for high performance. If your application deals withespecially large data files, is particular about memory management, orneeds low level operations like reading specific scanlines or tiles,this may not be the module for you. But if your application iscomfortable reading entire files into memory and can deal with pixeldata in numpy arrays, the OpenEXR module is a suitable option.
A Note about Backwards Compatibility¶
The v3.3 release of the OpenEXR module provides an entirely new API inthe form of theOpenEXR.File object. This API is full-featured andfully supported going forward.
The original implementation of the OpenEXR python bindings prior tothe v3.3 release used theInputFile andOutputFileobjects. This API is limited in scope, and is now deprecated. It isstill distributed as is for backwards compatibility, but usage isdiscouraged.
Example Images¶
SeeTest Images for example images to experiment with.
Reading and Writing in a Nutshell¶
Generate random RGB data and write it to an EXR file:
importOpenEXRheight,width=(1080,1920)RGB=np.random.rand(height,width,3).astype('float32')channels={"RGB":RGB}header={"compression":OpenEXR.ZIP_COMPRESSION,"type":OpenEXR.scanlineimage}withOpenEXR.File(header,channels)asoutfile:outfile.write("image.exr")
This creates a scanline image of size 10x20 pixels with R, G, and Bchannels of type float, initialized to random values, and writes it tothe filetest.exr, compressed with ZIP compression.
Correspondingly, to read an image and print its pixel data:
importOpenEXRwithOpenEXR.File("image.exr")asinfile:header=infile.header()print(f"type={header['type']}")print(f"compression={header['compression']}")RGB=infile.channels()["RGB"].pixelsheight,width=RGB.shape[0:2]foryinrange(height):forxinrange(width):pixel=(RGB[y,x,0],RGB[y,x,1],RGB[y,x,2])print(f"pixel[{y}][{x}]={pixel}")
Reading EXR Files with OpenEXR.File¶
The basic construct of the OpenEXR module is theFile object.Construct aFile object with a filename as the parameter and itreads the image data into the object:
>>>exrfile=OpenEXR.File("StillLife.exr")
OpenEXR.Part¶
An EXR file consists of a list calledparts of one or more parts,which the OpenEXR python module represents with thePart object. Apart consists of a dictionary calledheader that holds theattribute metadata, and a dictionary calledchannels that hold thepixel data.
>>>exrfile=OpenEXR.File("StillLife.exr")>>>part=exrfile.parts[0]>>>part.height()846>>>part.width()1240>>>forname,valueinpart.header.items():...print(name,value)...capDate 2002:06:23 21:30:10channels [Channel("A", xSampling=1, ySampling=1), Channel("B", xSampling=1, ySampling=1), Channel("G", xSampling=1, ySampling=1), Channel("R", xSampling=1, ySampling=1)]compression Compression.PIZ_COMPRESSIONdataWindow (array([0, 0], dtype=int32), array([1239, 845], dtype=int32))displayWindow (array([0, 0], dtype=int32), array([1239, 845], dtype=int32))lineOrder LineOrder.INCREASING_Yowner Copyright 2002 Industrial Light & MagicpixelAspectRatio 1.0preview PreviewImage(100, 68)screenWindowCenter [0. 0.]screenWindowWidth 0.44999998807907104type Storage.scanlineimageutcOffset 25200.0>>>forname,channelinpart.channels.items():...print(name,channel,channel.pixels.shape,channel.pixels.dtype)...RGBA Channel("RGBA", xSampling=1, ySampling=1) (846, 1240, 4) float16
Since many common EXR files have only a single part, for convenience,theFile object hasheader() andchannels() methods that
Header Metadata¶
TheFile object’sheader() method returns a dictionary holdingthe file’s metadata. The dictionary key is the metadata attributename, and the dictionary value is an object holding the attribute value.
An EXR file header can store metadata attributes with any name, but seeStandard Attributes for a complete description of the standardattributes in an EXR file, both required and optional, which havestrictly enforced types.
Supported types of metadata are:
string
list of strings
integer
float
list of floats
V2i, V2f, V2d, V3i, V3f, V3d - 2D and 3D vectors, represented as 2x1or 3x1 numpy arrays with a
dtypeofint32,float32, orfloat64.M33f, M33d, M44f, M44d - 3x3 or 4x4 matrices, represented as 3x3 or4x4 numpy arrays with a
dtypeoffloat32orfloat64.Box2i, Box2f - bounding boxes, represented as tuples of numpy arrays (
minandmax) with adtypeofint32orfloat32.
The OpenEXR module has enumerated types for certain attributes:
ThedataWindow Attribute and Image Size¶
ThedataWindow attribute is especially important. Its size matchesthe shape of the channel pixel arrays. Themin of thedataWindow attribute specifies the row/column coordinate of thepixel at the origin of the image. However, the numpy arrays holdingthe pixel data arenot offset by this value.
>>>min,max=exrfile.header()["dataWindow"]>>>height=max[1]-min[1]+1>>>width=max[0]-min[0]+1>>>height,width(846, 1240)
OpenEXR.Channel¶
Thechannels() method of theFile object returns a dictionaryholding pixel data. The key is the channel name and the value is aChannel object.
TheChannel object has apixels field that is a 2D numpy arrayholding the pixel data. Supported types areuint32,float16,andfloat32.
For parts that contain RGB data, where the file contains separateR,G,B, and optionallyA channels, the channelsdictionary holds a single channel namedRGB and with a numpy arrayof shape(height,width,3), orRGBA and a numpy array ofshape(height,width,4) if there is alpha.
All channels within a part have the same width and height, and thusthe same pixel array shape.
For single-part files,channels() returns the image channels for thefile. For multi-part files,channels() takes a part number asargument and returns the channels for that part:
>>>forpinrange(len(exrfile.parts)):...forname,channelinexrfile.channels(p).items():...print(name,channel.pixels.shape,channel.pixels.dtype)...RGBA (846, 1240, 4) float16
The channel object also hasxSampling,ySampling, andpLinear fields that hold the channel’s subsampling values andplinear setting used for DWA compression. The default sampling valuesare 1 and are only used for luminance subsampling.
Pixel Arrays¶
The first dimension of a channel’spixels array is the imageheight. The second dimension is the image width. All channels of apart must have the same width and height. It’s an error to create orwrite a File object with channels of different shapes. ThePartobject hasheight() andwidth() methods that return the imagedimension. You can, of course, query the dimension of a channel viathe pixel array itself.
>>>part=exrfile.parts[0]>>>dw=part.header['dataWindow']>>>height=part.height()>>>width=part.width()>>>forname,channelinpart.channels.items():...print(name,channel.pixels.shape,height,width,dw)...(846, 1240, 4) 846 1240 (array([0, 0], dtype=int32), array([1239, 845], dtype=int32))
TheFile object allocates space for the pixel arrays uponread. There is no mechanism to provide memory addresses for the pixelarrays.
Pixel Array Data Layout¶
Although the OpenEXR file format supports channels of arbitrary nameand number of typeuint32,float16, andfloat32, mostprograms working with OpenEXR files expect this data to representpixels, so it’s more convenient to groupR,G,B, andA channels together. By default, theFile object does this andreturns a channel with nameRGB and a numpy array of shape(height,width,3), orRGBA and a numpy array of shape(height,width,4) if there is alpha.
TheFile() object constructor takes an optionalseparate_channels argument,False by default, but ifTrue,it skips the channel grouping and returns each channel as a separate2D numpy array.
Tiled Images¶
TheFile object reads tiled EXR images into pixel arrays just thesame as scanline images.
Although the EXR format supports multiple tile levels, currently, theAPI provides no access to these levels.
Deep Images¶
Deep EXR files store an arbitrary number of data values per pixel. Fordeep parts, theChannel object’spixels array has adtypeofobject, which is in turn a 1D numpy array holding the deep samplesfor that pixel. Supported types for the deep sample array areuint32,float16, andfloat32.
If the deep sample array object for a given pixel isNone, thereare no samples for that pixel.
RGB=infile.channels()["RGB"].pixelsheight,width=R.shapeforyinrange(height):forxinrange(width):ifR[y,x].dtype==None:print(f"No samples for pixel{y},{x}")else:foriinrange(RGB[y,x].shape(0)):print(f"pixel{y},{x} sample[{i}]:{RGB[y,x]}")
All channel within a given deep part must have the same number ofsamples, so the deep sample arrays for all channels have the same sizeand shape.
Writing EXR Files with OpenEXR.File¶
To write an EXR file, construct aFile object and call thewrite() method.
For single-part files, theFile object constructor takes adictionary for the header and a dictionary for the channels.
Construct the channels dict with values that are either numpy arraysorChannel objects if you need to specify thexSampling,ySampling, orpLinear values.
The channel pixel arrays must have adtype ofuint32,float16, orfloat32.
All channel pixel arrays within a given part must have the samedimensions. Thewrite method will throw an exception if they arenot.
height,width=(20,10)RGB=np.random.rand(height,width,3).astype('f')channels={"RGB":RGB}header={"compression":OpenEXR.ZIP_COMPRESSION,"type":OpenEXR.scanlineimage}withOpenEXR.File(header,channels)asoutfile:outfile.write("test.exr")
Writing Multi-Part EXR Files¶
For multi-part images, pass theFile constructor a list ofPart objects, each of which holds the header and channels dicts.
height,width=(20,10)Z0=np.zeros((height,width),dtype='float32')Z1=np.ones((height,width),dtype='ffloat32')P0=OpenEXR.Part({},{"Z":Z0})P1=OpenEXR.Part({},{"Z":Z1})f=OpenEXR.File([P0,P1])f.write("readme_2part.exr")withOpenEXR.File("multipart.exr")aso:asserto.parts[0].name()=="Part0"asserto.parts[0].width()==10asserto.parts[0].height()==20asserto.parts[1].name()=="Part1"asserto.parts[1].width()==10asserto.parts[1].height()==20
Writing Tiled EXR Files¶
To write a tiled image, set thetype header attribute toOpenEXR.tiledimage and thetiles header attribute to an objectof typeOpenEXR.TileDescription with the appropriate settings.
height,width=(20,10)Z=np.zeros((height,width),dtype='f')channels={"Z":Z}header={"type":OpenEXR.tiledimage,"tiles":OpenEXR.TileDescription(),"compression":OpenEXR.ZIPSCOMPRESSION}withOpenEXR.File(channels,header)asexrfile:exrfile.write("tiled.exr")
Writing Deep EXR Files¶
For deep images, the channel pixel arrays must have adtype ofobject, orNone for pixels with no samples. The object mustbe a numpy array with adtype ofuint32,float16, orfloat32.
height,width=(20,10)Z=np.empty((height,width),dtype=object)foryinrange(height):forxinrange(width):Z[y,x]=np.array([y*width+x],dtype='float32')channels={"Z":Z}header={"compression":OpenEXR.ZIPS_COMPRESSION,"type":OpenEXR.deepscanline}withOpenEXR.File(header,channels)asoutfile:outfile.write("deep.exr")
All deep pixel arrays within a given part must have the same number ofsamples, so the pixel arrays must have the same size and shape. Thewrite method will throw an exception if they are not.
