0
# Array Integration
1
2
Seamless integration with Python arrays and NumPy for data exchange, enabling easy interoperability with the scientific Python ecosystem. PyVips provides efficient conversion between images and arrays without unnecessary data copying.
3
4
## Capabilities
5
6
### Array Export
7
8
Convert PyVips images to Python arrays and NumPy arrays for analysis and processing with other libraries.
9
10
```python { .api }
11
def tolist(self) -> list:
12
"""
13
Convert single-band image to Python list of lists.
14
15
IMPORTANT: Only works for single-band images. Multi-band images will raise NotImplementedError.
16
17
Returns:
18
List of lists representing image rows: [[row1_pixels], [row2_pixels], ...]
19
- Each inner list contains pixel values for one row
20
- Complex format images return complex numbers (a + bj)
21
22
Raises:
23
NotImplementedError: If image has more than one band
24
"""
25
26
def numpy(self, dtype=None):
27
"""
28
Convert image to NumPy array (convenience method for method chaining).
29
30
This mimics PyTorch behavior: image.op1().op2().numpy()
31
32
Parameters:
33
- dtype: str or numpy dtype, target data type (optional, uses image format if None)
34
35
Returns:
36
NumPy array with shape:
37
- (height, width) for single-band images (channel axis removed)
38
- (height, width, bands) for multi-band images
39
- Single-pixel single-band images become 0D arrays
40
41
Requires numpy as runtime dependency.
42
"""
43
44
def __array__(self, dtype=None):
45
"""
46
NumPy array interface implementation for np.array() compatibility.
47
48
Parameters:
49
- dtype: str or numpy dtype, target data type (optional, uses image format if None)
50
51
Returns:
52
NumPy array with same shape rules as numpy() method
53
54
Requires numpy as runtime dependency.
55
"""
56
```
57
58
Example usage:
59
60
```python
61
import numpy as np
62
63
# Convert single-band image to Python list
64
gray_image = image.colourspace('b-w') # Convert to single-band first
65
gray_list = gray_image.tolist() # Returns [[row1_pixels], [row2_pixels], ...]
66
print(f"List type: {type(gray_list)}")
67
print(f"Number of rows: {len(gray_list)}")
68
print(f"Pixels in first row: {len(gray_list[0])}")
69
70
# tolist() only works for single-band images
71
try:
72
# This will fail for multi-band images
73
rgb_list = image.tolist() # NotImplementedError for multi-band
74
except NotImplementedError:
75
print("tolist() only works for single-band images")
76
# Use numpy() instead for multi-band images
77
array = image.numpy()
78
pixel_list = array.tolist() # Convert numpy array to list
79
80
# Convert to NumPy array
81
array = image.numpy()
82
print(f"Array shape: {array.shape}")
83
print(f"Array dtype: {array.dtype}")
84
85
# Specify target dtype
86
float_array = image.numpy(dtype=np.float32)
87
uint16_array = image.numpy(dtype=np.uint16)
88
89
# Use NumPy array interface
90
array = np.array(image) # Equivalent to image.numpy()
91
array = np.asarray(image, dtype=np.float64)
92
93
# Access array properties
94
height, width = array.shape[:2]
95
if len(array.shape) == 3:
96
bands = array.shape[2]
97
print(f"Shape: {height}x{width}x{bands}")
98
else:
99
print(f"Shape: {height}x{width} (grayscale)")
100
```
101
102
### Array Import
103
104
Create PyVips images from Python arrays and NumPy arrays for processing with PyVips operations.
105
106
```python { .api }
107
@classmethod
108
def new_from_list(cls, array: list, scale: float = 1.0,
109
offset: float = 0.0) -> 'Image':
110
"""
111
Create image from Python list.
112
113
Parameters:
114
- array: 2D list of pixel values [[row1], [row2], ...]
115
- scale: float, scale factor applied to pixel values
116
- offset: float, offset added to pixel values
117
118
Returns:
119
Image object created from list data
120
"""
121
122
@classmethod
123
def new_from_array(cls, array, scale: float = 1.0, offset: float = 0.0,
124
interpretation: str = None) -> 'Image':
125
"""
126
Create image from array (list or NumPy array).
127
128
Parameters:
129
- array: 2D array of pixel values
130
- scale: float, scale factor
131
- offset: float, offset value
132
- interpretation: str, color space interpretation
133
134
Returns:
135
Image object created from array data
136
"""
137
```
138
139
Example usage:
140
141
```python
142
# Create from Python list
143
pixel_data = [
144
[255, 128, 0], # Row 1: Red, Half-intensity, Black
145
[128, 255, 128], # Row 2: Half-intensity, Green, Half-intensity
146
[0, 128, 255] # Row 3: Black, Half-intensity, Blue
147
]
148
image_from_list = pyvips.Image.new_from_list(pixel_data)
149
150
# Create from NumPy array
151
import numpy as np
152
153
# RGB image from array
154
rgb_array = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
155
image_from_array = pyvips.Image.new_from_array(rgb_array)
156
157
# Grayscale image
158
gray_array = np.random.randint(0, 256, (100, 100), dtype=np.uint8)
159
gray_image = pyvips.Image.new_from_array(gray_array)
160
161
# Float array with scaling
162
float_array = np.random.random((50, 50)).astype(np.float32)
163
float_image = pyvips.Image.new_from_array(float_array, scale=255.0)
164
165
# Specify color space interpretation
166
srgb_array = np.zeros((100, 100, 3), dtype=np.uint8)
167
srgb_array[:, :, 0] = 255 # Red channel
168
srgb_image = pyvips.Image.new_from_array(srgb_array, interpretation='srgb')
169
170
# Complex data
171
complex_array = np.random.random((50, 50)) + 1j * np.random.random((50, 50))
172
# Note: Complex arrays need special handling - convert to real representation
173
real_part = complex_array.real
174
imag_part = complex_array.imag
175
combined = np.stack([real_part, imag_part], axis=-1)
176
complex_image = pyvips.Image.new_from_array(combined, interpretation='complex')
177
```
178
179
### NumPy Interoperability
180
181
Seamless integration with NumPy ecosystem for scientific computing workflows.
182
183
```python
184
import numpy as np
185
import matplotlib.pyplot as plt
186
import scipy.ndimage
187
188
# PyVips to NumPy workflow
189
image = pyvips.Image.new_from_file('photo.jpg')
190
array = image.numpy()
191
192
# NumPy operations
193
blurred_array = scipy.ndimage.gaussian_filter(array, sigma=2.0)
194
edges_array = scipy.ndimage.sobel(array.mean(axis=2)) # Edge detection on grayscale
195
196
# Visualization with matplotlib
197
plt.figure(figsize=(15, 5))
198
199
plt.subplot(131)
200
plt.imshow(array)
201
plt.title('Original')
202
plt.axis('off')
203
204
plt.subplot(132)
205
plt.imshow(blurred_array)
206
plt.title('Blurred (SciPy)')
207
plt.axis('off')
208
209
plt.subplot(133)
210
plt.imshow(edges_array, cmap='gray')
211
plt.title('Edges (SciPy)')
212
plt.axis('off')
213
214
plt.tight_layout()
215
plt.show()
216
217
# Convert back to PyVips
218
blurred_image = pyvips.Image.new_from_array(blurred_array)
219
edges_image = pyvips.Image.new_from_array(edges_array)
220
221
# Continue processing with PyVips
222
result = (blurred_image
223
.resize(0.5)
224
.sharpen()
225
.colourspace('srgb'))
226
227
# Combine PyVips and NumPy processing
228
def hybrid_processing(pyvips_image):
229
# PyVips preprocessing
230
preprocessed = (pyvips_image
231
.thumbnail_image(800)
232
.gaussblur(0.5))
233
234
# Convert to NumPy for custom algorithm
235
array = preprocessed.numpy()
236
237
# Custom NumPy processing
238
# Apply custom filter
239
kernel = np.array([[-1, -1, -1],
240
[-1, 9, -1],
241
[-1, -1, -1]])
242
if len(array.shape) == 3:
243
filtered = np.zeros_like(array)
244
for i in range(array.shape[2]):
245
filtered[:, :, i] = scipy.ndimage.convolve(array[:, :, i], kernel)
246
else:
247
filtered = scipy.ndimage.convolve(array, kernel)
248
249
# Convert back to PyVips
250
result_image = pyvips.Image.new_from_array(filtered)
251
252
# PyVips postprocessing
253
final = (result_image
254
.linear([1.1, 1.1, 1.1], [0, 0, 0]) # Slight contrast boost
255
.colourspace('srgb'))
256
257
return final
258
259
# Use hybrid processing
260
processed = hybrid_processing(image)
261
```
262
263
### Data Type Handling
264
265
Proper handling of different data types between PyVips and NumPy.
266
267
```python
268
import numpy as np
269
270
# PyVips format to NumPy dtype mapping
271
format_to_dtype = {
272
'uchar': np.uint8,
273
'char': np.int8,
274
'ushort': np.uint16,
275
'short': np.int16,
276
'uint': np.uint32,
277
'int': np.int32,
278
'float': np.float32,
279
'double': np.float64,
280
'complex': np.complex64,
281
'dpcomplex': np.complex128
282
}
283
284
def get_numpy_dtype(image):
285
"""Get appropriate NumPy dtype for PyVips image."""
286
return format_to_dtype.get(image.format, np.float32)
287
288
# Convert with appropriate dtype
289
image = pyvips.Image.new_from_file('photo.jpg')
290
appropriate_dtype = get_numpy_dtype(image)
291
array = image.numpy(dtype=appropriate_dtype)
292
293
# Handle different bit depths
294
def normalize_to_float(image):
295
"""Normalize image to 0-1 float range regardless of input format."""
296
array = image.numpy()
297
298
if image.format == 'uchar':
299
return array.astype(np.float32) / 255.0
300
elif image.format == 'ushort':
301
return array.astype(np.float32) / 65535.0
302
elif image.format in ['float', 'double']:
303
return array.astype(np.float32)
304
else:
305
# For other formats, use image statistics
306
min_val = array.min()
307
max_val = array.max()
308
if max_val > min_val:
309
return (array.astype(np.float32) - min_val) / (max_val - min_val)
310
else:
311
return array.astype(np.float32)
312
313
# Convert back with proper scaling
314
def array_to_image_format(array, target_format='uchar'):
315
"""Convert NumPy array to specific PyVips format with proper scaling."""
316
if target_format == 'uchar':
317
# Scale to 0-255
318
scaled = np.clip(array * 255.0, 0, 255).astype(np.uint8)
319
elif target_format == 'ushort':
320
# Scale to 0-65535
321
scaled = np.clip(array * 65535.0, 0, 65535).astype(np.uint16)
322
elif target_format == 'float':
323
scaled = array.astype(np.float32)
324
else:
325
scaled = array
326
327
return pyvips.Image.new_from_array(scaled)
328
329
# Example usage
330
normalized = normalize_to_float(image)
331
# Process in float space
332
processed_array = normalized * 1.2 # Brighten
333
# Convert back to original format
334
result = array_to_image_format(processed_array, target_format=image.format)
335
```
336
337
### Batch Processing with Arrays
338
339
Efficient batch processing combining PyVips and NumPy for handling multiple images.
340
341
```python
342
import numpy as np
343
from pathlib import Path
344
345
def process_image_batch(image_paths, output_dir):
346
"""Process multiple images with combined PyVips/NumPy operations."""
347
results = []
348
349
for path in image_paths:
350
# Load with PyVips
351
image = pyvips.Image.new_from_file(str(path))
352
353
# Standardize size with PyVips
354
standard = image.thumbnail_image(512)
355
356
# Convert to NumPy for batch operations
357
array = standard.numpy()
358
359
# Collect for batch processing
360
results.append({
361
'path': path,
362
'array': array,
363
'original': image
364
})
365
366
# Batch NumPy operations
367
all_arrays = np.array([r['array'] for r in results])
368
369
# Compute batch statistics
370
batch_mean = np.mean(all_arrays, axis=0)
371
batch_std = np.std(all_arrays, axis=0)
372
373
# Apply batch normalization
374
normalized_arrays = []
375
for i, array in enumerate(all_arrays):
376
normalized = (array - batch_mean) / (batch_std + 1e-8)
377
normalized = np.clip(normalized * 50 + 128, 0, 255).astype(np.uint8)
378
normalized_arrays.append(normalized)
379
380
# Convert back to PyVips and save
381
for i, result in enumerate(results):
382
normalized_image = pyvips.Image.new_from_array(normalized_arrays[i])
383
384
# Final PyVips processing
385
final = (normalized_image
386
.colourspace('srgb')
387
.sharpen())
388
389
# Save with original filename
390
output_path = Path(output_dir) / f"processed_{result['path'].name}"
391
final.write_to_file(str(output_path))
392
393
# Machine learning data preparation
394
def prepare_ml_dataset(image_dir, target_size=(224, 224)):
395
"""Prepare images for machine learning with PyVips and NumPy."""
396
image_paths = list(Path(image_dir).glob('*.jpg'))
397
398
dataset = []
399
labels = []
400
401
for path in image_paths:
402
# Load and preprocess with PyVips
403
image = pyvips.Image.new_from_file(str(path))
404
405
# Standardize with smart cropping
406
processed = (image
407
.thumbnail_image(target_size[0], crop='attention')
408
.colourspace('srgb'))
409
410
# Ensure exact size (pad if necessary)
411
if processed.width != target_size[0] or processed.height != target_size[1]:
412
# Center crop or pad
413
left = (processed.width - target_size[0]) // 2
414
top = (processed.height - target_size[1]) // 2
415
416
if left >= 0 and top >= 0:
417
processed = processed.crop(left, top, target_size[0], target_size[1])
418
else:
419
# Pad with black
420
processed = processed.gravity('centre', target_size[0], target_size[1])
421
422
# Convert to NumPy for ML framework
423
array = processed.numpy().astype(np.float32) / 255.0
424
425
dataset.append(array)
426
427
# Extract label from filename or directory
428
label = path.stem.split('_')[0] # Assuming format: "class_image.jpg"
429
labels.append(label)
430
431
return np.array(dataset), labels
432
433
# Use ML preparation
434
X, y = prepare_ml_dataset('training_images/')
435
print(f"Dataset shape: {X.shape}")
436
print(f"Labels: {set(y)}")
437
```
438
439
## Performance Considerations
440
441
### Memory Efficiency
442
443
```python
444
# Efficient array conversion for large images
445
def efficient_array_processing(large_image):
446
"""Process large images efficiently with arrays."""
447
448
# For very large images, process in tiles
449
if large_image.width * large_image.height > 50_000_000: # 50MP threshold
450
# Tile-based processing
451
tile_size = 2048
452
result_tiles = []
453
454
for y in range(0, large_image.height, tile_size):
455
for x in range(0, large_image.width, tile_size):
456
# Extract tile
457
w = min(tile_size, large_image.width - x)
458
h = min(tile_size, large_image.height - y)
459
tile = large_image.crop(x, y, w, h)
460
461
# Process tile with NumPy
462
tile_array = tile.numpy()
463
processed_array = your_numpy_function(tile_array)
464
processed_tile = pyvips.Image.new_from_array(processed_array)
465
466
result_tiles.append((x, y, processed_tile))
467
468
# Reconstruct image from tiles
469
# (Implementation depends on specific needs)
470
471
else:
472
# Direct processing for smaller images
473
array = large_image.numpy()
474
processed_array = your_numpy_function(array)
475
return pyvips.Image.new_from_array(processed_array)
476
477
# Avoid unnecessary copies
478
def zero_copy_when_possible(image):
479
"""Minimize memory copies during array operations."""
480
481
# Check if array conversion is really needed
482
if can_use_pyvips_directly():
483
return image.some_pyvips_operation()
484
485
# Convert to array only when necessary
486
array = image.numpy()
487
488
# Modify array in-place when possible
489
array *= 1.1 # In-place multiplication
490
array += 10 # In-place addition
491
492
return pyvips.Image.new_from_array(array)
493
```
494
495
### Type Optimization
496
497
```python
498
# Choose appropriate data types
499
def optimize_array_types(image):
500
"""Use appropriate data types for different operations."""
501
502
if image.format == 'uchar' and needs_precision():
503
# Convert to float for high-precision operations
504
float_array = image.numpy(dtype=np.float32)
505
processed = complex_float_operation(float_array)
506
# Convert back to uint8 with proper scaling
507
result_array = np.clip(processed, 0, 1) * 255
508
return pyvips.Image.new_from_array(result_array.astype(np.uint8))
509
else:
510
# Keep original type for simple operations
511
array = image.numpy()
512
processed = simple_operation(array)
513
return pyvips.Image.new_from_array(processed)
514
```