OpenEXR Python Bindings
The `openexr` library provides official Python bindings for the OpenEXR image file format, a high-dynamic-range (HDR) image file format used in computer graphics and visual effects. It allows Python applications to read, write, and manipulate EXR files, including multi-part, multi-channel, and deep images. The library is actively maintained with frequent patch releases addressing security vulnerabilities and bug fixes, typically following the major C++ OpenEXR library releases.
Warnings
- breaking The `openexr` package is the official Python 3 binding for OpenEXR 3.x, replacing the older `PyOpenEXR` and `PyImath` packages which were tied to OpenEXR 2.x. Users migrating from older setups will find significant API differences and different import paths. Do not mix `openexr` with `PyOpenEXR`.
- gotcha Installing `openexr` from source can be complex due to its underlying C++ dependencies (OpenEXR, Imath, zlib, etc.). Pre-built wheels are typically available on PyPI for common platforms, but if a wheel isn't available for your specific Python version/OS, a robust C++ build environment (including `cmake`) is required.
- gotcha While `openexr` provides EXR file I/O, `Imath` (vectors, matrices, boxes) is typically accessed via the separate `imath` Python package. Though `openexr`'s internal C++ libraries use Imath, its Python bindings do not fully expose all Imath types directly under the `openexr` namespace.
- gotcha Pixel data read from `openexr` channels are returned as raw `bytes` objects. These must be converted to `numpy.ndarray` for practical manipulation, requiring careful handling of `dtype` (e.g., `np.float16` for `HALF`, `np.float32` for `FLOAT`) and `reshape` to the correct image dimensions.
- gotcha Multiple recent patch releases (e.g., 3.4.9, 3.3.9, 3.2.7) have addressed significant security vulnerabilities, including heap out-of-bounds writes and integer overflows, which could lead to crashes or arbitrary code execution when processing malformed EXR files.
Install
-
pip install openexr
Imports
- openexr
import openexr
- Imath
import Imath
Quickstart
import openexr
import Imath
import numpy as np
# Define image properties
width = 256
height = 256
half_channels = {'R': Imath.ChannelList().add('R', Imath.PixelType(Imath.PixelType.HALF)),
'G': Imath.ChannelList().add('G', Imath.PixelType(Imath.PixelType.HALF)),
'B': Imath.ChannelList().add('B', Imath.PixelType(Imath.PixelType.HALF))}
# Create some dummy data (e.g., a simple gradient)
x = np.linspace(0, 1, width)
y = np.linspace(0, 1, height)
R = np.outer(y, x).astype(np.float16)
G = np.outer(y, 1-x).astype(np.float16)
B = np.outer(1-y, x).astype(np.float16)
# Write an EXR file
output_filename = 'example.exr'
header = openexr.Header(width, height)
# Ensure correct channel types and data window if writing half data
# header['channels'] = half_channels
# The above is for explicit channel list. For simplicity, let's use default half for RGB.
file_out = openexr.OutputFile(output_filename, header)
file_out.writePixels({'R': R.tobytes(), 'G': G.tobytes(), 'B': B.tobytes()})
file_out.close()
print(f"Written {output_filename}")
# Read the EXR file
file_in = openexr.InputFile(output_filename)
header_in = file_in.header()
data_window = header_in['dataWindow']
dw = Imath.Box2i(data_window)
size = (dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1)
# Read 'R' channel data as bytes and convert to numpy array
r_channel_bytes = file_in.channel('R', Imath.PixelType(Imath.PixelType.HALF))
r_data = np.frombuffer(r_channel_bytes, dtype=np.float16).reshape(size[1], size[0])
print(f"Read R channel data shape: {r_data.shape}")
print(f"First few R values: {r_data.flatten()[:5]}")
file_in.close()