Professional-grade EXR image format library for high-dynamic-range scene-linear image data with multi-part and deep compositing support
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Extensible attribute system for storing comprehensive image metadata, including standard attributes for color management, camera data, rendering parameters, and custom application-specific information.
Core metadata attributes required or commonly used in EXR files.
# Required header attributes (automatically managed)
header = {
"compression": int, # Compression method constant
"type": int, # Image type (scanline/tiled/deep)
"channels": dict, # Channel definitions (auto-generated)
"dataWindow": tuple, # Data bounds (minX, minY, maxX, maxY)
"displayWindow": tuple, # Display bounds (minX, minY, maxX, maxY)
"lineOrder": int, # Scanline order (INCREASING_Y/DECREASING_Y/RANDOM_Y)
"pixelAspectRatio": float, # Pixel aspect ratio (width/height)
"screenWindowCenter": tuple, # Screen window center (x, y)
"screenWindowWidth": float, # Screen window width
}
# Optional standard attributes
header.update({
"owner": str, # Image creator/owner
"comments": str, # Descriptive comments
"capDate": str, # Capture date (YYYY:MM:DD HH:MM:SS)
"utcOffset": float, # UTC offset in seconds
"software": str, # Creating software name/version
"artist": str, # Artist/creator name
"copyright": str, # Copyright notice
"contact": str, # Contact information
"jobName": str, # Production job name
"project": str, # Project name
})Attributes for tiled images and multi-resolution formats.
# Tiled image attributes
header.update({
"tiles": dict, # Tile description
# {
# "xSize": int, # Tile width
# "ySize": int, # Tile height
# "mode": int, # Level mode (ONE_LEVEL/MIPMAP_LEVELS/RIPMAP_LEVELS)
# "roundingMode": int # Level rounding mode (ROUND_DOWN/ROUND_UP)
# }
})
# Multi-resolution level modes
from OpenEXR import Imath
Imath.LevelMode.ONE_LEVEL: int # Single resolution level
Imath.LevelMode.MIPMAP_LEVELS: int # Mipmapped levels (power of 2)
Imath.LevelMode.RIPMAP_LEVELS: int # Ripmapped levels (independent X/Y)
Imath.LevelRoundingMode.ROUND_DOWN: int # Round dimensions down
Imath.LevelRoundingMode.ROUND_UP: int # Round dimensions upColor management and display-related metadata.
# Color space and display attributes
header.update({
"chromaticities": tuple, # Color primaries (8 floats: rx,ry,gx,gy,bx,by,wx,wy)
"whiteLuminance": float, # White point luminance (cd/m²)
"adoptedNeutral": tuple, # Adopted neutral (x, y) chromaticity
# Display and viewing
"displayWindow": tuple, # Display bounds (minX, minY, maxX, maxY)
"screenWindowCenter": tuple, # Screen window center (x, y)
"screenWindowWidth": float, # Screen window width
"pixelAspectRatio": float, # Pixel aspect ratio
# Gamma and transfer
"gamma": float, # Display gamma (if applicable)
"exposure": float, # Exposure compensation
})
# Standard chromaticities
from OpenEXR import Imath
rec709_chromaticities = Imath.Chromaticities(
# Red, Green, Blue, White points (x, y coordinates)
Imath.V2f(0.64, 0.33), # Red primary
Imath.V2f(0.30, 0.60), # Green primary
Imath.V2f(0.15, 0.06), # Blue primary
Imath.V2f(0.3127, 0.3290) # White point (D65)
)Camera-specific metadata for visual effects and photography workflows.
# Camera and lens attributes
header.update({
"camera": str, # Camera model/name
"lens": str, # Lens model/name
"focalLength": float, # Focal length (mm)
"aperture": float, # F-stop/aperture value
"focus": float, # Focus distance (m)
"iso": int, # ISO sensitivity
"shutterSpeed": float, # Shutter speed (seconds)
# Camera position and orientation
"cameraPosition": tuple, # World position (x, y, z)
"cameraOrientation": tuple, # Rotation quaternion (x, y, z, w)
"cameraTransform": tuple, # 4x4 transformation matrix (16 floats)
# Projection parameters
"nearClip": float, # Near clipping plane
"farClip": float, # Far clipping plane
"fieldOfView": float, # Field of view (degrees)
"projection": str, # Projection type ("perspective", "orthographic")
})Metadata specific to 3D rendering and visual effects workflows.
# Rendering attributes
header.update({
"renderTime": float, # Render time (seconds)
"samples": int, # Sample count per pixel
"renderer": str, # Rendering software
"renderLayer": str, # Render layer name
"renderPass": str, # Render pass type
# Scene information
"scene": str, # Scene file name
"shot": str, # Shot identifier
"frame": int, # Frame number
"frameRate": float, # Frames per second
# Quality settings
"quality": str, # Quality preset ("draft", "preview", "final")
"motionBlur": bool, # Motion blur enabled
"depthOfField": bool, # Depth of field enabled
"globalIllumination": bool, # Global illumination enabled
})
# Multi-part type identification
header.update({
"type": int, # Part type constant
"name": str, # Part name (for multi-part files)
"view": str, # Stereo view ("left", "right", "center")
})Application-specific metadata using the extensible attribute system.
# Custom application attributes (examples)
header.update({
# Pipeline-specific
"pipeline.version": str, # Pipeline version
"pipeline.jobId": str, # Job identifier
"pipeline.userId": str, # User identifier
"pipeline.department": str, # Creating department
# Workflow metadata
"workflow.status": str, # Approval status
"workflow.reviewNotes": str, # Review comments
"workflow.version": int, # Version number
# Technical metadata
"tech.hostName": str, # Rendering machine
"tech.renderEngine": str, # Render engine version
"tech.memoryUsage": float, # Peak memory usage (GB)
"tech.renderNodes": int, # Distributed render nodes
# Color pipeline
"color.inputSpace": str, # Input color space
"color.workingSpace": str, # Working color space
"color.outputSpace": str, # Output color space
"color.lut": str, # Color LUT file path
})Complex geometric types for advanced metadata.
from OpenEXR import Imath
# Vector types
vector2i = Imath.V2i(x, y) # 2D integer vector
vector2f = Imath.V2f(x, y) # 2D float vector
vector3i = Imath.V3i(x, y, z) # 3D integer vector
vector3f = Imath.V3f(x, y, z) # 3D float vector
# Matrix types
matrix33f = Imath.M33f() # 3x3 float matrix
matrix44f = Imath.M44f() # 4x4 float matrix
# Specialized types
timecode = Imath.TimeCode( # SMPTE timecode
hours, minutes, seconds, frame,
dropFrame=False, colorFrame=False,
fieldPhase=False, bgf0=False, bgf1=False, bgf2=False
)
keycode = Imath.KeyCode( # Film keycode
filmMfcCode, filmType, prefix,
count, perfOffset, perfsPerFrame, perfsPerCount
)
rational = Imath.Rational(numerator, denominator) # Rational number
preview = Imath.PreviewImage(width, height, pixels) # Preview thumbnailimport OpenEXR
import numpy as np
# Create image with comprehensive metadata
height, width = 1080, 1920
rgb_data = np.random.rand(height, width, 3).astype('f')
# Build complete header
header = {
# Required
"compression": OpenEXR.ZIP_COMPRESSION,
"type": OpenEXR.scanlineimage,
# Display and quality
"pixelAspectRatio": 1.0,
# Production metadata
"software": "MyRenderer v2.1.0",
"artist": "John Doe",
"project": "Feature Film XYZ",
"shot": "SEQ010_SH020",
"frame": 1001,
"comments": "Hero beauty pass with atmospheric effects",
# Camera information
"camera": "RED Dragon 6K",
"lens": "Zeiss Master Prime 50mm T1.3",
"focalLength": 50.0,
"aperture": 2.8,
"iso": 800,
# Rendering details
"renderer": "Arnold 7.2.1.1",
"renderTime": 145.7,
"samples": 256,
"quality": "final"
}
channels = {"RGB": rgb_data}
with OpenEXR.File(header, channels) as outfile:
outfile.write("metadata_example.exr")
# Read back and examine metadata
with OpenEXR.File("metadata_example.exr") as infile:
header = infile.header()
print("Production Info:")
print(f" Project: {header.get('project', 'Unknown')}")
print(f" Shot: {header.get('shot', 'Unknown')}")
print(f" Frame: {header.get('frame', 'Unknown')}")
print(f" Artist: {header.get('artist', 'Unknown')}")
print("\nTechnical Info:")
print(f" Software: {header.get('software', 'Unknown')}")
print(f" Renderer: {header.get('renderer', 'Unknown')}")
print(f" Render Time: {header.get('renderTime', 0):.1f}s")
print(f" Samples: {header.get('samples', 'Unknown')}")import OpenEXR
from OpenEXR import Imath
import numpy as np
# Define color space with chromaticities
rec2020_chromaticities = Imath.Chromaticities(
Imath.V2f(0.708, 0.292), # Red primary
Imath.V2f(0.170, 0.797), # Green primary
Imath.V2f(0.131, 0.046), # Blue primary
Imath.V2f(0.3127, 0.3290) # White point D65
)
# HDR metadata
height, width = 2160, 3840 # 4K
hdr_data = np.random.exponential(2.0, (height, width, 3)).astype('f')
hdr_header = {
"compression": OpenEXR.DWAA_COMPRESSION,
"type": OpenEXR.scanlineimage,
# Color management
"chromaticities": (
# Extract values from Chromaticities object
rec2020_chromaticities.red.x, rec2020_chromaticities.red.y,
rec2020_chromaticities.green.x, rec2020_chromaticities.green.y,
rec2020_chromaticities.blue.x, rec2020_chromaticities.blue.y,
rec2020_chromaticities.white.x, rec2020_chromaticities.white.y
),
"whiteLuminance": 10000.0, # 10,000 cd/m² for HDR
"adoptedNeutral": (0.3127, 0.3290), # D65 white point
# HDR workflow
"color.inputSpace": "scene-linear Rec.2020",
"color.workingSpace": "ACES AP0",
"color.outputSpace": "Rec.2020 PQ",
"exposure": 0.0, # No exposure compensation
# Technical
"software": "HDR Pipeline v1.0",
"comments": "HDR content for wide gamut displays"
}
channels = {"RGB": hdr_data}
with OpenEXR.File(hdr_header, channels) as outfile:
outfile.write("hdr_color_managed.exr")import OpenEXR
import numpy as np
from datetime import datetime
def create_vfx_metadata(shot_info, render_settings, technical_info):
"""Create comprehensive VFX pipeline metadata."""
# Get current timestamp
now = datetime.now()
timestamp = now.strftime("%Y:%m:%d %H:%M:%S")
utc_offset = now.utcoffset().total_seconds() if now.utcoffset() else 0
header = {
# Core format
"compression": OpenEXR.DWAA_COMPRESSION,
"type": OpenEXR.scanlineimage,
# Basic metadata
"capDate": timestamp,
"utcOffset": utc_offset,
"software": f"VFX Pipeline {technical_info['pipeline_version']}",
# Shot identification
"project": shot_info["project"],
"shot": shot_info["shot"],
"sequence": shot_info["sequence"],
"frame": shot_info["frame"],
"frameRate": shot_info["fps"],
"renderLayer": shot_info["layer"],
"renderPass": shot_info["pass"],
# Artist and approval workflow
"artist": shot_info["artist"],
"department": shot_info["department"],
"workflow.version": shot_info["version"],
"workflow.status": shot_info.get("status", "pending"),
# Render settings
"renderer": render_settings["engine"],
"samples": render_settings["samples"],
"renderTime": render_settings.get("time", 0),
"quality": render_settings["quality"],
"motionBlur": render_settings.get("motion_blur", False),
"depthOfField": render_settings.get("dof", False),
# Technical environment
"tech.hostName": technical_info["hostname"],
"tech.renderEngine": technical_info["engine_version"],
"tech.memoryUsage": technical_info.get("memory_gb", 0),
"tech.renderNodes": technical_info.get("nodes", 1),
# Pipeline specific
"pipeline.jobId": technical_info["job_id"],
"pipeline.buildNumber": technical_info["build"],
"pipeline.configHash": technical_info.get("config_hash", ""),
# Custom tracking
"tracking.submitTime": technical_info.get("submit_time", timestamp),
"tracking.queueTime": technical_info.get("queue_time", 0),
"tracking.renderCost": technical_info.get("cost", 0.0)
}
return header
# Example usage
shot_data = {
"project": "FeatureFilm2024",
"sequence": "SEQ_100",
"shot": "SH_010",
"frame": 1001,
"fps": 24.0,
"layer": "beauty",
"pass": "diffuse",
"artist": "jane.smith",
"department": "lighting",
"version": 3
}
render_data = {
"engine": "Arnold",
"engine_version": "7.2.1.1",
"samples": 512,
"quality": "final",
"motion_blur": True,
"dof": True,
"time": 342.5
}
tech_data = {
"pipeline_version": "2.1.0",
"hostname": "render-node-042",
"job_id": "JOB_20240910_001234",
"build": "2.1.0-stable-abc123",
"memory_gb": 64.2,
"nodes": 8
}
# Create image with VFX metadata
height, width = 2048, 2048
beauty_data = np.random.rand(height, width, 3).astype('f')
vfx_header = create_vfx_metadata(shot_data, render_data, tech_data)
channels = {"RGB": beauty_data}
with OpenEXR.File(vfx_header, channels) as outfile:
outfile.write("shot_with_pipeline_metadata.exr")
# Verify metadata was stored
with OpenEXR.File("shot_with_pipeline_metadata.exr") as infile:
header = infile.header()
print("Shot Information:")
print(f" {header['project']}/{header['sequence']}/{header['shot']} v{header['workflow.version']}")
print(f" Frame {header['frame']} - {header['renderLayer']}.{header['renderPass']}")
print(f" Artist: {header['artist']} ({header['department']})")
print("\nRender Information:")
print(f" Engine: {header['renderer']}")
print(f" Samples: {header['samples']}")
print(f" Time: {header['renderTime']:.1f}s")
print(f" Quality: {header['quality']}")
print("\nTechnical:")
print(f" Host: {header['tech.hostName']}")
print(f" Job: {header['pipeline.jobId']}")
print(f" Memory: {header['tech.memoryUsage']:.1f} GB")import OpenEXR
from OpenEXR import Imath
import numpy as np
# Create image with complex custom attributes
height, width = 1080, 1920
image_data = np.random.rand(height, width, 3).astype('f')
# Camera transformation matrix (4x4)
camera_matrix = Imath.M44f(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 10.0, # 10 units back from origin
0.0, 0.0, 0.0, 1.0
)
# SMPTE timecode
frame_timecode = Imath.TimeCode(
hours=1, minutes=23, seconds=45, frame=12,
dropFrame=False, colorFrame=False
)
# Film keycode
film_keycode = Imath.KeyCode(
filmMfcCode=12345,
filmType=6789,
prefix=123,
count=456789,
perfOffset=12,
perfsPerFrame=4,
perfsPerCount=64
)
# Build header with complex types
header = {
"compression": OpenEXR.ZIP_COMPRESSION,
"type": OpenEXR.scanlineimage,
# Standard metadata
"software": "Advanced Pipeline v3.0",
"comments": "Complex metadata example",
# Geometric attributes (stored as tuples/arrays)
"cameraTransform": tuple(camera_matrix.getValue()), # 16 floats
"cameraPosition": (0.0, 0.0, 10.0), # World position
"cameraTarget": (0.0, 0.0, 0.0), # Look-at target
# Timecode information
"timeCode": str(frame_timecode), # Convert to string representation
"keyCode": str(film_keycode), # Convert to string representation
# Rational numbers (as tuples)
"frameRate": (24000, 1001), # 23.976 fps as rational
"shutterAngle": (180, 1), # 180 degree shutter
# Custom workflow data
"workflow.checkpoints": [ # List as string
"modeling_approved",
"rigging_complete",
"animation_final",
"lighting_review"
],
# Bounding box information
"geometry.boundingBox": (-10.0, -5.0, -2.0, 10.0, 5.0, 8.0), # min/max XYZ
# Color correction parameters
"cc.lift": (0.0, 0.0, 0.0), # Lift adjustment
"cc.gamma": (1.0, 1.0, 1.0), # Gamma adjustment
"cc.gain": (1.0, 1.0, 1.0), # Gain adjustment
"cc.saturation": 1.0, # Saturation multiplier
}
channels = {"RGB": image_data}
with OpenEXR.File(header, channels) as outfile:
outfile.write("complex_metadata.exr")
# Read and parse complex metadata
with OpenEXR.File("complex_metadata.exr") as infile:
header = infile.header()
# Extract camera transform
if "cameraTransform" in header:
matrix_values = header["cameraTransform"]
print("Camera Transform Matrix:")
for i in range(4):
row = matrix_values[i*4:(i+1)*4]
print(f" {row[0]:8.3f} {row[1]:8.3f} {row[2]:8.3f} {row[3]:8.3f}")
# Extract workflow checkpoints
if "workflow.checkpoints" in header:
checkpoints = header["workflow.checkpoints"]
print(f"\nWorkflow Checkpoints: {checkpoints}")
# Extract color correction
cc_attrs = {k: v for k, v in header.items() if k.startswith("cc.")}
if cc_attrs:
print("\nColor Correction:")
for attr, value in cc_attrs.items():
print(f" {attr}: {value}")Install with Tessl CLI
npx tessl i tessl/pypi-openexr