Skip to content

Commit 9ca6e62

Browse files
Copilotadamjtaylor
andcommitted
Use pyvips for robust H&E thumbnail generation
Replace custom tifffile/PIL implementation with pyvips (libvips): - Works with both OME-TIFF (convert=true) and original formats like SVS (convert=false) - Handles RGB chunky format from raw2ometiff --rgb flag - Production-ready library designed for whole slide imaging - Efficiently handles pyramidal images automatically - Eliminates complex array shape manipulation code - More maintainable and reliable Co-authored-by: adamjtaylor <14945787+adamjtaylor@users.noreply.github.com>
1 parent 19d92a8 commit 9ca6e62

File tree

1 file changed

+8
-56
lines changed

1 file changed

+8
-56
lines changed

modules/make_miniature.nf

Lines changed: 8 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,65 +17,17 @@ process make_miniature {
1717
"""
1818
#!/usr/bin/env python
1919
20-
import tifffile
21-
from PIL import Image
22-
import numpy as np
20+
import pyvips
2321
24-
# Open the OME-TIFF file and read the base (full resolution) image
25-
# Let PIL handle the resizing via thumbnail() - it's more robust
26-
with tifffile.TiffFile('$image') as tif:
27-
# Always read from the base series (full resolution)
28-
# Don't try to use pyramid levels as they may be too small or have odd shapes
29-
if len(tif.series) > 0:
30-
img_array = tif.series[0].asarray()
31-
else:
32-
img_array = tif.asarray()
22+
# Use pyvips for robust thumbnail generation from any whole slide format
23+
# Supports SVS, OME-TIFF, and other formats efficiently
24+
image = pyvips.Image.new_from_file('$image', access='sequential')
3325
34-
# Handle array shape - squeeze out singleton dimensions
35-
img_array = np.squeeze(img_array)
26+
# Create thumbnail with max dimension of 512px
27+
thumbnail = image.thumbnail_image(512)
3628
37-
# Ensure we have at least 2D array
38-
if img_array.ndim < 2:
39-
# After squeezing, if we end up with 1D, reshape to 2D
40-
img_array = img_array.reshape(1, -1)
41-
42-
# Handle different array shapes
43-
# Expected shapes: (Y, X), (Y, X, 3), (Y, X, 4) for grayscale, RGB, or RGBA
44-
if img_array.ndim > 3:
45-
# If more than 3 dimensions after squeeze, take first slice
46-
# This handles cases like (C, Y, X, S) -> take first channel
47-
img_array = img_array[0]
48-
img_array = np.squeeze(img_array)
49-
# Check again after squeezing
50-
if img_array.ndim < 2:
51-
img_array = img_array.reshape(1, -1)
52-
53-
# Ensure proper data type for PIL (uint8)
54-
if img_array.dtype != np.uint8:
55-
# Scale to uint8 range if needed
56-
img_min = img_array.min()
57-
img_max = img_array.max()
58-
if img_max > 255:
59-
# Avoid division by zero
60-
if img_max > img_min:
61-
img_array = ((img_array - img_min) / (img_max - img_min) * 255).astype(np.uint8)
62-
else:
63-
img_array = np.zeros_like(img_array, dtype=np.uint8)
64-
else:
65-
img_array = img_array.astype(np.uint8)
66-
67-
# Convert numpy array to PIL Image
68-
thumb = Image.fromarray(img_array)
69-
70-
# Create thumbnail (maintains aspect ratio)
71-
# PIL's thumbnail is efficient and handles large images well
72-
thumb.thumbnail((512, 512))
73-
74-
# Ensure RGB mode
75-
if thumb.mode != "RGB":
76-
thumb = thumb.convert("RGB")
77-
78-
thumb.save('miniature.jpg')
29+
# Save as JPEG
30+
thumbnail.write_to_file('miniature.jpg')
7931
"""
8032
} else {
8133
"""

0 commit comments

Comments
 (0)