wsi-tile-processor 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wsi_tile_processor-0.1.1/LICENSE +21 -0
- wsi_tile_processor-0.1.1/PKG-INFO +439 -0
- wsi_tile_processor-0.1.1/README.md +405 -0
- wsi_tile_processor-0.1.1/pyproject.toml +46 -0
- wsi_tile_processor-0.1.1/setup.cfg +4 -0
- wsi_tile_processor-0.1.1/src/wsi_tile_processor.egg-info/PKG-INFO +439 -0
- wsi_tile_processor-0.1.1/src/wsi_tile_processor.egg-info/SOURCES.txt +9 -0
- wsi_tile_processor-0.1.1/src/wsi_tile_processor.egg-info/dependency_links.txt +1 -0
- wsi_tile_processor-0.1.1/src/wsi_tile_processor.egg-info/requires.txt +11 -0
- wsi_tile_processor-0.1.1/src/wsi_tile_processor.egg-info/top_level.txt +1 -0
- wsi_tile_processor-0.1.1/tests/test_smoke.py +333 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Federico Carollo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wsi-tile-processor
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Tile-by-tile inference on Whole Slide Images (WSI) with pyramidal OME-TIFF output
|
|
5
|
+
Author: Federico Carollo
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FedeCarollo/wsi-tile-processor
|
|
8
|
+
Project-URL: Repository, https://github.com/FedeCarollo/wsi-tile-processor
|
|
9
|
+
Project-URL: Issues, https://github.com/FedeCarollo/wsi-tile-processor/issues
|
|
10
|
+
Keywords: WSI,whole slide image,pathology,OME-TIFF,tiling,inference,histology
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy
|
|
24
|
+
Requires-Dist: tifffile
|
|
25
|
+
Requires-Dist: imagecodecs
|
|
26
|
+
Requires-Dist: openslide-python
|
|
27
|
+
Requires-Dist: scipy
|
|
28
|
+
Requires-Dist: Pillow
|
|
29
|
+
Requires-Dist: pyvips
|
|
30
|
+
Provides-Extra: full
|
|
31
|
+
Requires-Dist: zarr; extra == "full"
|
|
32
|
+
Requires-Dist: scikit-image; extra == "full"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# wsi-tile-processor
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/wsi-tile-processor/)
|
|
38
|
+
[](https://www.python.org/downloads/)
|
|
39
|
+
[](https://opensource.org/licenses/MIT)
|
|
40
|
+
|
|
41
|
+
> Tile-by-tile inference on Whole Slide Images (WSI) with pyramidal OME-TIFF output.
|
|
42
|
+
|
|
43
|
+
`wsi-tile-processor` is a Python library for running any deep learning or image-processing model on whole slide images (WSI) in a memory-efficient, tiled manner, and saving the result as a multi-resolution pyramidal OME-TIFF ready for viewing in QuPath, OMERO, or any TIFF-compatible viewer.
|
|
44
|
+
|
|
45
|
+
**Key features:**
|
|
46
|
+
|
|
47
|
+
- **Two processors** — `FastWSIProcessor` (non-overlapping, fast) and `GaussianWSIProcessor` (overlapping tiles with smooth Gaussian blending, no seam artifacts)
|
|
48
|
+
- **Pluggable background filters** — swap or subclass `BackgroundFilter` to implement custom tissue/background detection
|
|
49
|
+
- **Pluggable tissue mask detectors** — coarse-level background skipping with `TissueMaskDetector`
|
|
50
|
+
- **Multi-backend slide reading** — `openslide`, `tifffile` (for OME-TIFF sub-IFD pyramids), or `auto`
|
|
51
|
+
- **Batch inference** — feed multiple tiles per forward pass
|
|
52
|
+
- **BigTIFF + JPEG pyramid output** via pyvips
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
**From PyPI (Recommended):**
|
|
59
|
+
```bash
|
|
60
|
+
pip install wsi-tile-processor
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For full support (zarr-backed tifffile reads + scikit-image tissue detection):
|
|
64
|
+
```bash
|
|
65
|
+
pip install wsi-tile-processor[full]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**From GitHub (Latest development version):**
|
|
69
|
+
```bash
|
|
70
|
+
pip install git+https://github.com/FedeCarollo/wsi-tile-processor.git
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### System dependencies
|
|
74
|
+
|
|
75
|
+
#### OpenSlide
|
|
76
|
+
|
|
77
|
+
[OpenSlide](https://openslide.org/download/) must be installed on your system:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Ubuntu / Debian
|
|
81
|
+
sudo apt install openslide-tools
|
|
82
|
+
|
|
83
|
+
# macOS
|
|
84
|
+
brew install openslide
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### libvips (for pyramid generation)
|
|
88
|
+
|
|
89
|
+
`pyvips` is a Python binding for [libvips](https://www.libvips.org/) and **does not bundle it** by default.
|
|
90
|
+
You have two options:
|
|
91
|
+
|
|
92
|
+
**Option A — system package (recommended for servers/HPC):**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Ubuntu / Debian
|
|
96
|
+
sudo apt install libvips
|
|
97
|
+
|
|
98
|
+
# macOS
|
|
99
|
+
brew install vips
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Option B — bundled binary via pip (no system install needed):**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install "pyvips[binary]"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
This pulls in `pyvips-binary`, which ships pre-compiled libvips binaries and runs in faster CFFI API mode. Useful for Docker containers or environments where you can't install system packages.
|
|
109
|
+
|
|
110
|
+
> **ℹ️ Note:** Without libvips installed (system or binary), `pyvips` falls back to ABI mode which may be ~20% slower. Pyramidization will still work correctly.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Quick Start
|
|
115
|
+
|
|
116
|
+
### Non-overlapping tiles — `FastWSIProcessor`
|
|
117
|
+
|
|
118
|
+
Use this for most use cases: virtual staining, classification heatmaps, segmentation masks.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import numpy as np
|
|
122
|
+
from wsi_tile_processor import FastWSIProcessor
|
|
123
|
+
|
|
124
|
+
# 'generate' maps an HxWx3 uint8 tile to an HxWx3 output.
|
|
125
|
+
# Here we use an identity function as a placeholder.
|
|
126
|
+
def my_model(tile: np.ndarray) -> np.ndarray:
|
|
127
|
+
return tile # replace with your model
|
|
128
|
+
|
|
129
|
+
processor = FastWSIProcessor(
|
|
130
|
+
wsi_path="input.svs",
|
|
131
|
+
tiff_path="output.ome.tiff",
|
|
132
|
+
level=1, # pyramid level to process (0 = full res)
|
|
133
|
+
tile_size=512, # tile side length in pixels
|
|
134
|
+
generate=my_model,
|
|
135
|
+
output_channels=3,
|
|
136
|
+
verbose=True,
|
|
137
|
+
)
|
|
138
|
+
processor.process()
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Overlapping tiles with blending — `GaussianWSIProcessor`
|
|
142
|
+
|
|
143
|
+
Use this when your model produces boundary artifacts. Overlapping tiles are averaged with a Gaussian weight kernel, eliminating grid patterns.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from wsi_tile_processor import GaussianWSIProcessor
|
|
147
|
+
|
|
148
|
+
processor = GaussianWSIProcessor(
|
|
149
|
+
wsi_path="input.svs",
|
|
150
|
+
tiff_path="output.ome.tiff",
|
|
151
|
+
level=1,
|
|
152
|
+
tile_size=512,
|
|
153
|
+
stride=256, # 50% overlap
|
|
154
|
+
blur_sigma=64.0, # Gaussian kernel sigma in pixels
|
|
155
|
+
generate=my_model,
|
|
156
|
+
output_channels=3,
|
|
157
|
+
verbose=True,
|
|
158
|
+
)
|
|
159
|
+
processor.process()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
### Batched inference
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
import numpy as np
|
|
167
|
+
|
|
168
|
+
def batched_model(batch: np.ndarray) -> np.ndarray:
|
|
169
|
+
# batch shape: (B, H, W, 3) uint8
|
|
170
|
+
# return shape: (B, H, W, 3)
|
|
171
|
+
return batch
|
|
172
|
+
|
|
173
|
+
processor = FastWSIProcessor(
|
|
174
|
+
wsi_path="input.svs",
|
|
175
|
+
tiff_path="output.ome.tiff",
|
|
176
|
+
level=1,
|
|
177
|
+
tile_size=512,
|
|
178
|
+
generate=batched_model,
|
|
179
|
+
batch_size=8, # tiles per forward pass
|
|
180
|
+
verbose=True,
|
|
181
|
+
)
|
|
182
|
+
processor.process()
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Architecture
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
WSIProcessor (abstract)
|
|
191
|
+
├── FastWSIProcessor — stride == tile_size, uint8 memmap
|
|
192
|
+
└── GaussianWSIProcessor — stride < tile_size, float16 acc + wgt memmaps
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### `FastWSIProcessor`
|
|
196
|
+
|
|
197
|
+
- Iterates tiles in a regular grid (no overlap).
|
|
198
|
+
- Each non-background tile is passed to `generate`, and the uint8 result is written directly into a memory-mapped binary file.
|
|
199
|
+
- After all tiles, writes a BigTIFF and pyramidizes in-place with pyvips.
|
|
200
|
+
- **Disk usage:** 1× the output image size.
|
|
201
|
+
- **Best for:** virtual staining, classification maps, any task where tile alignment is fine.
|
|
202
|
+
|
|
203
|
+
### `GaussianWSIProcessor`
|
|
204
|
+
|
|
205
|
+
- Iterates tiles with a configurable stride (overlap).
|
|
206
|
+
- Each tile's output is multiplied by a Gaussian weight kernel and accumulated into `float16` memmaps (`acc` and `wgt`).
|
|
207
|
+
- After all tiles, every pixel is normalized by its total accumulated weight, then converted to uint8.
|
|
208
|
+
- **Disk usage:** ~5–6× the output image size during processing.
|
|
209
|
+
- **Best for:** models that produce boundary artifacts, or when smooth spatial continuity is critical.
|
|
210
|
+
|
|
211
|
+
### Custom Processor
|
|
212
|
+
|
|
213
|
+
Both processors share the same abstract contract defined by `WSIProcessor`.
|
|
214
|
+
You can subclass it to implement a completely different accumulation strategy — for example, writing tiles to HDF5, streaming to a remote store, or applying multi-scale blending.
|
|
215
|
+
|
|
216
|
+
The only method you must implement is `process()`:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import numpy as np
|
|
220
|
+
from wsi_tile_processor import WSIProcessor, _open_slide, _get_mpp
|
|
221
|
+
|
|
222
|
+
class MyProcessor(WSIProcessor):
|
|
223
|
+
def process(self) -> None:
|
|
224
|
+
slide = _open_slide(self.wsi_path, self.backend)
|
|
225
|
+
level_w, level_h = slide.level_dimensions[self.level]
|
|
226
|
+
downsample = slide.level_downsamples[self.level]
|
|
227
|
+
|
|
228
|
+
for ty in range(0, level_h, self.tile_size):
|
|
229
|
+
for tx in range(0, level_w, self.tile_size):
|
|
230
|
+
read_w = min(self.tile_size, level_w - tx)
|
|
231
|
+
read_h = min(self.tile_size, level_h - ty)
|
|
232
|
+
x0, y0 = int(tx * downsample), int(ty * downsample)
|
|
233
|
+
|
|
234
|
+
pil_tile = slide.read_region((x0, y0), self.level, (read_w, read_h))
|
|
235
|
+
tile = np.array(pil_tile.convert("RGB"), dtype=np.uint8)
|
|
236
|
+
|
|
237
|
+
if self._is_tile_background(tile):
|
|
238
|
+
continue # uses whatever BackgroundFilter was configured
|
|
239
|
+
|
|
240
|
+
result = self.generate(tile)
|
|
241
|
+
# ... write result to your custom output format ...
|
|
242
|
+
|
|
243
|
+
slide.close()
|
|
244
|
+
|
|
245
|
+
# Use it like any other processor
|
|
246
|
+
proc = MyProcessor(
|
|
247
|
+
wsi_path="input.svs",
|
|
248
|
+
tiff_path="output.ome.tiff",
|
|
249
|
+
level=1,
|
|
250
|
+
tile_size=512,
|
|
251
|
+
generate=my_model,
|
|
252
|
+
)
|
|
253
|
+
proc.process()
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
> **💡 Tip:** Your custom processor automatically inherits `_is_tile_background()`, `_compute_tissue_mask()`, and `_build_pyramid_pyvips()` from the base class, so you can reuse all the built-in filtering and pyramidization logic.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Background Filters
|
|
261
|
+
|
|
262
|
+
Background tiles are skipped by default: `generate` is never called on them,
|
|
263
|
+
and they are filled with `background_value` (default: white, 1.0).
|
|
264
|
+
|
|
265
|
+
### Built-in filters
|
|
266
|
+
|
|
267
|
+
#### `BrightnessBackgroundFilter` *(default)*
|
|
268
|
+
|
|
269
|
+
Brightness + optional saturation heuristic. Tuned for H&E and IHC slides.
|
|
270
|
+
A tile is background when more than `threshold` (default 70%) of its pixels
|
|
271
|
+
have all three channels > `threshold_intensity` (default 215).
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
from wsi_tile_processor import BrightnessBackgroundFilter, FastWSIProcessor
|
|
275
|
+
|
|
276
|
+
bg_filter = BrightnessBackgroundFilter(
|
|
277
|
+
threshold=0.75, # stricter: 75% bright pixels
|
|
278
|
+
threshold_intensity=220,
|
|
279
|
+
min_variance=100.0, # also skip low-variance tiles
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
processor = FastWSIProcessor(
|
|
283
|
+
wsi_path="input.svs",
|
|
284
|
+
tiff_path="output.ome.tiff",
|
|
285
|
+
level=1,
|
|
286
|
+
tile_size=512,
|
|
287
|
+
generate=my_model,
|
|
288
|
+
background_filter=bg_filter,
|
|
289
|
+
)
|
|
290
|
+
processor.process()
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### `OtsuBackgroundFilter`
|
|
294
|
+
|
|
295
|
+
Uses scikit-image Otsu thresholding on the grayscale image.
|
|
296
|
+
Requires `pip install scikit-image` (or `pip install wsi-tile-processor[full]`).
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
from wsi_tile_processor import OtsuBackgroundFilter
|
|
300
|
+
|
|
301
|
+
bg_filter = OtsuBackgroundFilter(background_fraction=0.65)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### `SaturationBackgroundFilter`
|
|
305
|
+
|
|
306
|
+
Classifies a tile as background when its mean HSV saturation is below a threshold.
|
|
307
|
+
Fast and robust for highly stained tissue.
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from wsi_tile_processor import SaturationBackgroundFilter
|
|
311
|
+
|
|
312
|
+
bg_filter = SaturationBackgroundFilter(min_mean_saturation=20.0)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Custom background filter
|
|
316
|
+
|
|
317
|
+
Subclass `BackgroundFilter` and implement `__call__`:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
import numpy as np
|
|
321
|
+
from wsi_tile_processor import BackgroundFilter, FastWSIProcessor
|
|
322
|
+
|
|
323
|
+
class MyPenMarkFilter(BackgroundFilter):
|
|
324
|
+
"""Skip tiles dominated by blue pen marks."""
|
|
325
|
+
|
|
326
|
+
def __call__(self, tile_rgb: np.ndarray) -> bool:
|
|
327
|
+
r, g, b = tile_rgb[..., 0], tile_rgb[..., 1], tile_rgb[..., 2]
|
|
328
|
+
# Blue pen: b >> r and b >> g
|
|
329
|
+
blue_mask = (b.astype(int) - r.astype(int) > 50) & \
|
|
330
|
+
(b.astype(int) - g.astype(int) > 50)
|
|
331
|
+
return float(blue_mask.mean()) > 0.30 # >30% blue → skip
|
|
332
|
+
|
|
333
|
+
processor = FastWSIProcessor(
|
|
334
|
+
wsi_path="input.svs",
|
|
335
|
+
tiff_path="output.ome.tiff",
|
|
336
|
+
level=1,
|
|
337
|
+
tile_size=512,
|
|
338
|
+
generate=my_model,
|
|
339
|
+
background_filter=MyPenMarkFilter(),
|
|
340
|
+
)
|
|
341
|
+
processor.process()
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
You can also pass any plain callable with signature `(tile_rgb: np.ndarray) -> bool`:
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
processor = FastWSIProcessor(
|
|
348
|
+
...,
|
|
349
|
+
background_filter=lambda t: t.mean() > 230,
|
|
350
|
+
)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## Tissue Mask Detection
|
|
356
|
+
|
|
357
|
+
For very large slides, computing the background filter on every tile can be
|
|
358
|
+
slow. A `TissueMaskDetector` provides a coarse, one-time mask computed on a
|
|
359
|
+
low-resolution thumbnail. Tiles that fall entirely outside the tissue mask are
|
|
360
|
+
skipped *without* reading the full-resolution data.
|
|
361
|
+
|
|
362
|
+
### Built-in detector
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from wsi_tile_processor import BrightnessTissueMaskDetector, FastWSIProcessor
|
|
366
|
+
|
|
367
|
+
detector = BrightnessTissueMaskDetector(
|
|
368
|
+
intensity_threshold=215, # pixels below this are tissue
|
|
369
|
+
max_side=4000, # thumbnail max dimension
|
|
370
|
+
open_radius=2,
|
|
371
|
+
close_radius=4,
|
|
372
|
+
verbose=True,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
processor = FastWSIProcessor(
|
|
376
|
+
wsi_path="input.svs",
|
|
377
|
+
tiff_path="output.ome.tiff",
|
|
378
|
+
level=1,
|
|
379
|
+
tile_size=512,
|
|
380
|
+
generate=my_model,
|
|
381
|
+
tissue_mask_detector=detector, # supersedes use_tissue_mask=True
|
|
382
|
+
)
|
|
383
|
+
processor.process()
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Requires `scikit-image` (`pip install wsi-tile-processor[full]`).
|
|
387
|
+
|
|
388
|
+
### Custom tissue mask detector
|
|
389
|
+
|
|
390
|
+
```python
|
|
391
|
+
import numpy as np
|
|
392
|
+
from wsi_tile_processor import TissueMaskDetector
|
|
393
|
+
|
|
394
|
+
class MyDLTissueMask(TissueMaskDetector):
|
|
395
|
+
def __init__(self, model):
|
|
396
|
+
self.model = model
|
|
397
|
+
|
|
398
|
+
def __call__(self, slide) -> tuple[np.ndarray, float]:
|
|
399
|
+
# Use the smallest level for speed
|
|
400
|
+
level = slide.level_count - 1
|
|
401
|
+
dims = slide.level_dimensions[level]
|
|
402
|
+
downsample = slide.level_downsamples[level]
|
|
403
|
+
thumb = np.array(slide.read_region((0, 0), level, dims).convert("RGB"))
|
|
404
|
+
mask = self.model.predict(thumb) # → bool H×W
|
|
405
|
+
return mask, downsample
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Slide Backends
|
|
411
|
+
|
|
412
|
+
| Backend | When to use |
|
|
413
|
+
|---------|-------------|
|
|
414
|
+
| `"auto"` *(default)* | Tries openslide first; falls back to tifffile if tifffile exposes more pyramid levels. Best for mixed environments. |
|
|
415
|
+
| `"openslide"` | SVS, NDPI, SCN, and other formats natively supported by OpenSlide. |
|
|
416
|
+
| `"tifffile"` | OME-TIFF files with sub-IFD pyramid levels (e.g. output of [VALIS](https://github.com/MathOnco/valis)). OpenSlide may only see the base level in these files. |
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
processor = FastWSIProcessor(
|
|
420
|
+
...,
|
|
421
|
+
backend="tifffile",
|
|
422
|
+
)
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Output Format
|
|
428
|
+
|
|
429
|
+
The output is a **BigTIFF OME-TIFF** with:
|
|
430
|
+
|
|
431
|
+
- A full JPEG-compressed pyramid (sub-IFD layout, readable by QuPath, OMERO, pyvips, tifffile, …)
|
|
432
|
+
- Physical resolution metadata (`XResolution` / `YResolution` in pixels/cm, derived from the input slide's `mpp-x`)
|
|
433
|
+
- Configurable JPEG quality (default 95) and internal tile size (default 512 px)
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## License
|
|
438
|
+
|
|
439
|
+
MIT © 2026 Federico Carollo — see [LICENSE](LICENSE) for details.
|