0
# Image Data Structures
1
2
Channel-based image representation supporting arbitrary pixel types, subsampling patterns, and deep compositing data structures for professional VFX and animation workflows.
3
4
## Capabilities
5
6
### Pixel Type System
7
8
OpenEXR supports multiple pixel data types optimized for different content and precision requirements.
9
10
```python { .api }
11
# Pixel type constants
12
OpenEXR.UINT: int # 32-bit unsigned integer (0 to 4,294,967,295)
13
OpenEXR.HALF: int # 16-bit IEEE 754 floating point (half precision)
14
OpenEXR.FLOAT: int # 32-bit IEEE 754 floating point (single precision)
15
```
16
17
### Channel Architecture
18
19
Individual image channels with flexible configuration for subsampling, pixel types, and linear encoding.
20
21
```python { .api }
22
class Channel:
23
def __init__(self, name: str = None, pixels = None, xSampling: int = 1, ySampling: int = 1, pLinear: bool = False):
24
"""
25
Create image channel with pixel data.
26
27
Args:
28
name: Channel identifier (e.g., "R", "G", "B", "A", "Z", "motion.X")
29
pixels: Pixel data as numpy array
30
xSampling: Horizontal subsampling factor (1 = full resolution)
31
ySampling: Vertical subsampling factor (1 = full resolution)
32
pLinear: True if pixels are perceptually linear encoded
33
"""
34
35
def pixelType(self):
36
"""
37
Get pixel data type based on numpy array dtype.
38
39
Returns:
40
OpenEXR pixel type constant (UINT/HALF/FLOAT)
41
"""
42
43
name: str # Channel name/identifier
44
xSampling: int # Horizontal subsampling (1, 2, 4, ...)
45
ySampling: int # Vertical subsampling (1, 2, 4, ...)
46
pLinear: bool # Perceptual linearity flag
47
pixels: numpy.ndarray # Pixel data array
48
```
49
50
### Array Data Layout
51
52
OpenEXR uses specific array layouts and data type mappings for efficient processing.
53
54
```python { .api }
55
# Numpy dtype mapping to OpenEXR pixel types
56
numpy.uint32 -> OpenEXR.UINT # 32-bit unsigned integer
57
numpy.float16 -> OpenEXR.HALF # 16-bit floating point (half)
58
numpy.float32 -> OpenEXR.FLOAT # 32-bit floating point (float)
59
60
# Array shapes for different channel configurations
61
single_channel: (height, width) # Grayscale, alpha, depth
62
rgb_packed: (height, width, 3) # RGB as single array
63
rgba_packed: (height, width, 4) # RGBA as single array
64
separate_channels: dict[str, (height, width)] # Individual channel arrays
65
```
66
67
### Geometric Types
68
69
Mathematical types for image bounds, vectors, and coordinate systems from the Imath module.
70
71
```python { .api }
72
# 2D Vector types
73
class V2i:
74
def __init__(self, x: int = 0, y: int = 0): ...
75
x: int
76
y: int
77
78
class V2f:
79
def __init__(self, x: float = 0.0, y: float = 0.0): ...
80
x: float
81
y: float
82
83
# 2D Box types for image bounds
84
class Box2i:
85
def __init__(self, min: V2i = None, max: V2i = None): ...
86
def __init__(self, minX: int, minY: int, maxX: int, maxY: int): ...
87
88
min: V2i # Minimum corner
89
max: V2i # Maximum corner
90
91
def width(self) -> int: ...
92
def height(self) -> int: ...
93
def isEmpty(self) -> bool: ...
94
def hasVolume(self) -> bool: ...
95
96
class Box2f:
97
def __init__(self, min: V2f = None, max: V2f = None): ...
98
def __init__(self, minX: float, minY: float, maxX: float, maxY: float): ...
99
100
min: V2f # Minimum corner
101
max: V2f # Maximum corner
102
```
103
104
### Deep Image Data
105
106
Multi-sample per pixel data structures for advanced compositing workflows.
107
108
```python { .api }
109
# Deep image support (conceptual - implementation varies)
110
class DeepChannel:
111
"""
112
Channel supporting variable samples per pixel.
113
Used for deep compositing workflows.
114
"""
115
116
def __init__(self, name: str, sampleCounts, sampleData):
117
"""
118
Create deep channel.
119
120
Args:
121
name: Channel identifier
122
sampleCounts: Array of sample counts per pixel
123
sampleData: Flattened array of all sample values
124
"""
125
126
name: str # Channel name
127
sampleCounts: numpy.ndarray # Samples per pixel (height, width)
128
sampleData: numpy.ndarray # All sample values (flattened)
129
totalSamples: int # Total number of samples
130
```
131
132
## Usage Examples
133
134
### Working with Pixel Types
135
136
```python
137
import OpenEXR
138
import numpy as np
139
140
# Create data with different pixel types
141
height, width = 1080, 1920
142
143
# HALF precision (16-bit float) - common for HDR images
144
half_data = np.random.rand(height, width, 3).astype(np.float16)
145
half_channels = {"RGB": half_data}
146
147
# FLOAT precision (32-bit float) - maximum precision
148
float_data = np.random.rand(height, width, 3).astype(np.float32)
149
float_channels = {"RGB": float_data}
150
151
# UINT (32-bit unsigned int) - for ID mattes, masks
152
uint_data = np.random.randint(0, 255, (height, width), dtype=np.uint32)
153
uint_channels = {"ID": uint_data}
154
155
# Check pixel types
156
print(f"Half pixel type: {OpenEXR.Channel(pixels=half_data).pixelType()}") # OpenEXR.HALF
157
print(f"Float pixel type: {OpenEXR.Channel(pixels=float_data).pixelType()}") # OpenEXR.FLOAT
158
print(f"UINT pixel type: {OpenEXR.Channel(pixels=uint_data).pixelType()}") # OpenEXR.UINT
159
```
160
161
### Channel Configurations
162
163
```python
164
import OpenEXR
165
import numpy as np
166
167
height, width = 1080, 1920
168
169
# RGB channels as single packed array
170
rgb_packed = np.random.rand(height, width, 3).astype('f')
171
channels_packed = {"RGB": rgb_packed}
172
173
# RGB channels as separate arrays
174
r_data = np.random.rand(height, width).astype('f')
175
g_data = np.random.rand(height, width).astype('f')
176
b_data = np.random.rand(height, width).astype('f')
177
channels_separate = {"R": r_data, "G": g_data, "B": b_data}
178
179
# RGBA with alpha channel
180
rgba_data = np.random.rand(height, width, 4).astype('f')
181
rgba_data[:, :, 3] = 1.0 # Set alpha to 1.0
182
channels_rgba = {"RGBA": rgba_data}
183
184
# Mixed precision channels
185
beauty_rgb = np.random.rand(height, width, 3).astype(np.float16) # HALF for beauty
186
depth_z = np.random.rand(height, width).astype(np.float32) # FLOAT for depth
187
mask_id = np.random.randint(0, 10, (height, width), dtype=np.uint32) # UINT for ID
188
189
mixed_channels = {
190
"RGB": beauty_rgb,
191
"Z": depth_z,
192
"ID": mask_id
193
}
194
```
195
196
### Subsampled Channels
197
198
```python
199
import OpenEXR
200
import numpy as np
201
202
height, width = 1080, 1920
203
204
# Full resolution luminance
205
y_data = np.random.rand(height, width).astype('f')
206
207
# Subsampled chrominance (half resolution)
208
# Create data at full resolution then subsample
209
chroma_full = np.random.rand(height, width, 2).astype('f')
210
chroma_subsampled = chroma_full[::2, ::2, :] # Every other pixel
211
212
# Create channels with subsampling
213
channels = {
214
"Y": OpenEXR.Channel("Y", y_data, xSampling=1, ySampling=1),
215
"RY": OpenEXR.Channel("RY", chroma_subsampled[:,:,0], xSampling=2, ySampling=2),
216
"BY": OpenEXR.Channel("BY", chroma_subsampled[:,:,1], xSampling=2, ySampling=2)
217
}
218
219
# Write YC format image
220
header = {
221
"compression": OpenEXR.ZIP_COMPRESSION,
222
"type": OpenEXR.scanlineimage
223
}
224
225
with OpenEXR.File(header, channels) as outfile:
226
outfile.write("yc_subsampled.exr")
227
```
228
229
### Working with Image Bounds
230
231
```python
232
from OpenEXR import Imath
233
import OpenEXR
234
import numpy as np
235
236
# Define image windows using Box2i
237
display_window = Imath.Box2i(
238
Imath.V2i(0, 0), # min corner
239
Imath.V2i(1919, 1079) # max corner (1920x1080)
240
)
241
242
# Data window can be subset of display window
243
data_window = Imath.Box2i(
244
Imath.V2i(100, 100), # Cropped region
245
Imath.V2i(1819, 979) # 1720x880 actual data
246
)
247
248
# Calculate dimensions
249
data_width = data_window.width() # 1720
250
data_height = data_window.height() # 880
251
display_width = display_window.width() # 1920
252
display_height = display_window.height() # 1080
253
254
print(f"Display: {display_width}x{display_height}")
255
print(f"Data: {data_width}x{data_height}")
256
257
# Create image data for actual data window size
258
rgb_data = np.random.rand(data_height, data_width, 3).astype('f')
259
260
# Header with window information
261
header = {
262
"compression": OpenEXR.ZIP_COMPRESSION,
263
"type": OpenEXR.scanlineimage,
264
"displayWindow": (display_window.min.x, display_window.min.y,
265
display_window.max.x, display_window.max.y),
266
"dataWindow": (data_window.min.x, data_window.min.y,
267
data_window.max.x, data_window.max.y)
268
}
269
270
channels = {"RGB": rgb_data}
271
272
with OpenEXR.File(header, channels) as outfile:
273
outfile.write("windowed_image.exr")
274
```
275
276
### Multi-Channel VFX Data
277
278
```python
279
import OpenEXR
280
import numpy as np
281
282
height, width = 1080, 1920
283
284
# Beauty pass (RGB)
285
beauty = np.random.rand(height, width, 3).astype(np.float16)
286
287
# Depth pass (Z)
288
depth = np.random.exponential(10.0, (height, width)).astype(np.float32)
289
290
# Motion vectors (2D)
291
motion_x = np.random.normal(0, 2, (height, width)).astype(np.float32)
292
motion_y = np.random.normal(0, 2, (height, width)).astype(np.float32)
293
294
# Normal vectors (3D)
295
normals = np.random.normal(0, 1, (height, width, 3)).astype(np.float32)
296
# Normalize to unit vectors
297
norm_length = np.sqrt(np.sum(normals**2, axis=2, keepdims=True))
298
normals = normals / norm_length
299
300
# Object ID (integer)
301
object_ids = np.random.randint(0, 100, (height, width)).astype(np.uint32)
302
303
# Material ID (integer)
304
material_ids = np.random.randint(0, 50, (height, width)).astype(np.uint32)
305
306
# Coverage/alpha (float)
307
coverage = np.random.rand(height, width).astype(np.float32)
308
309
# Create comprehensive channel set
310
vfx_channels = {
311
# Beauty
312
"RGB": beauty,
313
314
# Geometry
315
"Z": depth,
316
"N": normals, # Normals as packed RGB
317
"N.X": normals[:,:,0], # Or separate components
318
"N.Y": normals[:,:,1],
319
"N.Z": normals[:,:,2],
320
321
# Motion
322
"motion": np.stack([motion_x, motion_y], axis=2), # Packed 2D
323
"motion.X": motion_x, # Or separate components
324
"motion.Y": motion_y,
325
326
# IDs
327
"objectID": object_ids,
328
"materialID": material_ids,
329
330
# Coverage
331
"A": coverage
332
}
333
334
# VFX-optimized header
335
vfx_header = {
336
"compression": OpenEXR.DWAA_COMPRESSION, # Good for mixed content
337
"type": OpenEXR.scanlineimage,
338
"pixelAspectRatio": 1.0,
339
340
# Custom attributes
341
"software": "VFX Pipeline v1.0",
342
"comments": "Multi-pass render with motion vectors"
343
}
344
345
with OpenEXR.File(vfx_header, vfx_channels) as outfile:
346
outfile.write("vfx_multipass.exr")
347
348
# Read back and verify channel types
349
with OpenEXR.File("vfx_multipass.exr") as infile:
350
channels = infile.channels()
351
352
for name, channel in channels.items():
353
pixel_type = channel.pixelType()
354
shape = channel.pixels.shape
355
dtype = channel.pixels.dtype
356
357
print(f"{name}: {shape} {dtype} -> OpenEXR.{pixel_type}")
358
```
359
360
### Memory Optimization
361
362
```python
363
import OpenEXR
364
import numpy as np
365
366
def create_memory_efficient_channels(height, width):
367
"""Create channels with memory-conscious data types."""
368
369
# Use HALF precision for beauty (saves 50% memory vs FLOAT)
370
beauty_data = np.random.rand(height, width, 3).astype(np.float16)
371
372
# Use FLOAT only when precision is critical (depth, motion)
373
depth_data = np.random.rand(height, width).astype(np.float32)
374
motion_data = np.random.rand(height, width, 2).astype(np.float32)
375
376
# Use UINT for discrete data (IDs, masks)
377
id_data = np.random.randint(0, 1000, (height, width), dtype=np.uint32)
378
379
# Calculate memory usage
380
beauty_mb = beauty_data.nbytes / (1024 * 1024)
381
depth_mb = depth_data.nbytes / (1024 * 1024)
382
motion_mb = motion_data.nbytes / (1024 * 1024)
383
id_mb = id_data.nbytes / (1024 * 1024)
384
385
total_mb = beauty_mb + depth_mb + motion_mb + id_mb
386
387
print(f"Memory usage:")
388
print(f" Beauty (HALF): {beauty_mb:.1f} MB")
389
print(f" Depth (FLOAT): {depth_mb:.1f} MB")
390
print(f" Motion (FLOAT): {motion_mb:.1f} MB")
391
print(f" ID (UINT): {id_mb:.1f} MB")
392
print(f" Total: {total_mb:.1f} MB")
393
394
return {
395
"RGB": beauty_data,
396
"Z": depth_data,
397
"motion": motion_data,
398
"ID": id_data
399
}
400
401
# Create 4K image with memory-efficient types
402
channels = create_memory_efficient_channels(2160, 3840) # 4K resolution
403
404
header = {
405
"compression": OpenEXR.DWAA_COMPRESSION,
406
"type": OpenEXR.scanlineimage
407
}
408
409
with OpenEXR.File(header, channels) as outfile:
410
outfile.write("4k_memory_efficient.exr")
411
```