or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

array-integration.mdenumerations.mdimage-creation.mdimage-operations.mdimage-output.mdindex.mdio-connections.mdproperties-metadata.mdsystem-control.md

array-integration.mddocs/

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

```