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
Comprehensive file reading and writing operations for EXR images, supporting scanline and tiled formats, multi-part files, and streaming operations with configurable threading.
The main interface for EXR file operations, providing context management and flexible construction for different use cases.
class File:
def __init__(self, filename: str, separate_channels: bool = False, header_only: bool = False):
"""
Open existing EXR file for reading.
Args:
filename: Path to EXR file
separate_channels: Read channels as separate arrays
header_only: Read only header information
"""
def __init__(self, header: dict, channels: dict):
"""
Create EXR file for writing with single part.
Args:
header: Image metadata and format settings
channels: Channel data dictionary
"""
def __init__(self, parts: list):
"""
Create multi-part EXR file for writing.
Args:
parts: List of Part objects
"""
def __enter__(self):
"""Context manager entry."""
def __exit__(self, *args):
"""Context manager exit with cleanup."""
def header(self, part_index: int = 0) -> dict:
"""
Get header information for specified part.
Args:
part_index: Part index for multi-part files
Returns:
Header dictionary with metadata
"""
def channels(self, part_index: int = 0) -> dict:
"""
Get channel data for specified part.
Args:
part_index: Part index for multi-part files
Returns:
Dictionary mapping channel names to Channel objects
"""
def write(self, filename: str) -> None:
"""
Write EXR data to file.
Args:
filename: Output file path
"""
def width(self) -> int:
"""Get image width in pixels."""
def height(self) -> int:
"""Get image height in pixels."""Individual parts within multi-part EXR files, enabling complex compositing workflows with multiple image layers.
class Part:
def __init__(self, header: dict, channels: dict, name: str):
"""
Create image part for multi-part files.
Args:
header: Part-specific header information
channels: Channel data for this part
name: Human-readable part name
"""
def name(self) -> str:
"""Get part name."""
def shape(self) -> tuple:
"""Get image dimensions as (height, width)."""
def width(self) -> int:
"""Get image width in pixels."""
def height(self) -> int:
"""Get image height in pixels."""
def compression(self):
"""Get compression method for this part."""
def type(self):
"""Get image type (scanline/tiled/deep)."""
def typeString(self) -> str:
"""Get human-readable image type string."""
header: dict # Header attributes
channels: dict # Channel data mapping
part_index: int # Zero-based part indexIndividual image channels with flexible pixel types and sampling parameters.
class Channel:
def __init__(self, name: str = None, pixels = None, xSampling: int = 1, ySampling: int = 1, pLinear: bool = False):
"""
Create image channel.
Args:
name: Channel identifier
pixels: Pixel data as numpy array
xSampling: Horizontal subsampling factor
ySampling: Vertical subsampling factor
pLinear: Whether pixels are perceptually linear
"""
def pixelType(self):
"""Get pixel data type (UINT/HALF/FLOAT)."""
name: str # Channel name
xSampling: int # Horizontal subsampling
ySampling: int # Vertical subsampling
pLinear: bool # Perceptual linearity flag
pixels: numpy.ndarray # Pixel data arrayimport OpenEXR
import numpy as np
# Read complete EXR file
with OpenEXR.File("input.exr") as infile:
header = infile.header()
channels = infile.channels()
# Access specific channels
rgb_data = channels["RGB"].pixels
alpha_data = channels["A"].pixels if "A" in channels else None
print(f"Image: {infile.width()}x{infile.height()}")
print(f"Channels: {list(channels.keys())}")
print(f"Compression: {header['compression']}")
# Read header only for fast metadata access
with OpenEXR.File("input.exr", header_only=True) as infile:
header = infile.header()
print(f"Image type: {header['type']}")
print(f"Data window: {header['dataWindow']}")
# Read with separate channel arrays
with OpenEXR.File("input.exr", separate_channels=True) as infile:
channels = infile.channels()
r_channel = channels["R"].pixels
g_channel = channels["G"].pixels
b_channel = channels["B"].pixelsimport OpenEXR
import numpy as np
# Create RGB image data
height, width = 1080, 1920
rgb_data = np.random.rand(height, width, 3).astype('f')
# Write single-part file
header = {
"compression": OpenEXR.ZIP_COMPRESSION,
"type": OpenEXR.scanlineimage,
"pixelAspectRatio": 1.0
}
channels = {"RGB": rgb_data}
with OpenEXR.File(header, channels) as outfile:
outfile.write("output.exr")
# Write separate channels
r_data = np.random.rand(height, width).astype('f')
g_data = np.random.rand(height, width).astype('f')
b_data = np.random.rand(height, width).astype('f')
a_data = np.ones((height, width), dtype='f')
channels = {
"R": r_data,
"G": g_data,
"B": b_data,
"A": a_data
}
with OpenEXR.File(header, channels) as outfile:
outfile.write("rgba_output.exr")import OpenEXR
import numpy as np
# Create multiple image parts
height, width = 1080, 1920
# Beauty pass
beauty_data = np.random.rand(height, width, 3).astype('f')
beauty_header = {
"compression": OpenEXR.DWAA_COMPRESSION,
"type": OpenEXR.scanlineimage
}
beauty_channels = {"RGB": beauty_data}
# Depth pass
depth_data = np.random.rand(height, width).astype('f')
depth_header = {
"compression": OpenEXR.ZIP_COMPRESSION,
"type": OpenEXR.scanlineimage
}
depth_channels = {"Z": depth_data}
# Motion vector pass
motion_data = np.random.rand(height, width, 2).astype('f')
motion_header = {
"compression": OpenEXR.ZIP_COMPRESSION,
"type": OpenEXR.scanlineimage
}
motion_channels = {"motion": motion_data}
# Create multi-part file
parts = [
OpenEXR.Part(beauty_header, beauty_channels, "beauty"),
OpenEXR.Part(depth_header, depth_channels, "depth"),
OpenEXR.Part(motion_header, motion_channels, "motion")
]
with OpenEXR.File(parts) as outfile:
outfile.write("multipart_output.exr")
# Read multi-part file
with OpenEXR.File("multipart_output.exr") as infile:
# Access different parts
beauty_channels = infile.channels(0) # Beauty pass
depth_channels = infile.channels(1) # Depth pass
motion_channels = infile.channels(2) # Motion pass
# Get part-specific headers
beauty_header = infile.header(0)
depth_header = infile.header(1)
motion_header = infile.header(2)import OpenEXR
import numpy as np
def process_large_exr(input_path, output_path):
"""Process large EXR files with memory efficiency."""
with OpenEXR.File(input_path) as infile:
header = infile.header()
# Modify header for output
output_header = header.copy()
output_header["compression"] = OpenEXR.DWAA_COMPRESSION
# Process in chunks for memory efficiency
channels = infile.channels()
# Apply processing to channel data
processed_channels = {}
for name, channel in channels.items():
# Example: gamma correction
processed_pixels = np.power(channel.pixels, 1.0/2.2)
processed_channels[name] = processed_pixels
# Write processed result
with OpenEXR.File(output_header, processed_channels) as outfile:
outfile.write(output_path)
# Process file
process_large_exr("large_input.exr", "processed_output.exr")import OpenEXR
def safe_exr_read(filename):
"""Safely read EXR file with proper error handling."""
try:
with OpenEXR.File(filename) as infile:
header = infile.header()
channels = infile.channels()
return header, channels
except IOError as e:
print(f"File I/O error: {e}")
return None, None
except Exception as e:
print(f"EXR format error: {e}")
return None, None
def safe_exr_write(filename, header, channels):
"""Safely write EXR file with validation."""
# Validate header
required_keys = ["compression", "type"]
for key in required_keys:
if key not in header:
raise ValueError(f"Missing required header key: {key}")
# Validate channels
if not channels:
raise ValueError("No channels provided")
try:
with OpenEXR.File(header, channels) as outfile:
outfile.write(filename)
print(f"Successfully wrote: {filename}")
except Exception as e:
print(f"Write error: {e}")
raise
# Usage
header, channels = safe_exr_read("input.exr")
if header and channels:
safe_exr_write("output.exr", header, channels)Install with Tessl CLI
npx tessl i tessl/pypi-openexr