CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-openexr

Professional-grade EXR image format library for high-dynamic-range scene-linear image data with multi-part and deep compositing support

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

metadata.mddocs/

Metadata and Headers

Extensible attribute system for storing comprehensive image metadata, including standard attributes for color management, camera data, rendering parameters, and custom application-specific information.

Capabilities

Standard Header Attributes

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
})

Tiling and Multi-Resolution

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 up

Color and Display Attributes

Color 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 and Lens Metadata

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")
})

Rendering and VFX Metadata

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")
})

Custom Attributes

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
})

Geometric and Math Types

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 thumbnail

Usage Examples

Basic Header Management

import 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')}")

Color Management Metadata

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")

VFX Pipeline Metadata

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")

Custom Attribute Types

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

docs

advanced-features.md

cpp-api.md

file-io.md

image-data.md

index.md

metadata.md

tile.json