micro-reader 0.0.1__py3-none-any.whl
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.
- micro_reader/__init__.py +23 -0
- micro_reader/_axes.py +196 -0
- micro_reader/_base.py +268 -0
- micro_reader/_codecs.py +439 -0
- micro_reader/_errors.py +14 -0
- micro_reader/_index.py +53 -0
- micro_reader/_io.py +83 -0
- micro_reader/_jpeg_restarts.py +174 -0
- micro_reader/_memory.py +161 -0
- micro_reader/_metadata.py +163 -0
- micro_reader/_pool.py +85 -0
- micro_reader/_registry.py +104 -0
- micro_reader/_stream.py +317 -0
- micro_reader/backends/__init__.py +1 -0
- micro_reader/backends/_czi_metadata.py +188 -0
- micro_reader/backends/_lif_metadata.py +125 -0
- micro_reader/backends/_nd2_legacy.py +370 -0
- micro_reader/backends/_nd2_metadata.py +224 -0
- micro_reader/backends/_nd2_structure.py +280 -0
- micro_reader/backends/_tiff_metadata.py +287 -0
- micro_reader/backends/czi.py +948 -0
- micro_reader/backends/ims.py +409 -0
- micro_reader/backends/lif.py +713 -0
- micro_reader/backends/mrc.py +184 -0
- micro_reader/backends/nd2.py +264 -0
- micro_reader/backends/oir.py +439 -0
- micro_reader/backends/raster.py +568 -0
- micro_reader/backends/tiff.py +858 -0
- micro_reader-0.0.1.dist-info/METADATA +741 -0
- micro_reader-0.0.1.dist-info/RECORD +32 -0
- micro_reader-0.0.1.dist-info/WHEEL +4 -0
- micro_reader-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: micro-reader
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Low-level, memory-bounded pixel reading for microscopy formats: read exactly the rectangle you ask for.
|
|
5
|
+
Project-URL: Homepage, https://github.com/bugraoezdemir/micro-reader
|
|
6
|
+
Project-URL: Source, https://github.com/bugraoezdemir/micro-reader
|
|
7
|
+
Project-URL: Issues, https://github.com/bugraoezdemir/micro-reader/issues
|
|
8
|
+
Author-email: Bugra Oezdemir <bugraa.ozdemir@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bioimage,cryo-em,czi,eer,image-io,imaris,ims,leica,lif,lsm,memory-bounded,microscopy,mrc,nd2,oir,ome-tiff,tiff
|
|
12
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: micro-reader-codecs<0.1,>=0.0.1
|
|
22
|
+
Requires-Dist: numpy>=1.24
|
|
23
|
+
Requires-Dist: tifffile>=2025.5.21
|
|
24
|
+
Provides-Extra: all
|
|
25
|
+
Requires-Dist: h5py>=3.11; extra == 'all'
|
|
26
|
+
Requires-Dist: imagecodecs; extra == 'all'
|
|
27
|
+
Provides-Extra: codecs
|
|
28
|
+
Requires-Dist: imagecodecs; extra == 'codecs'
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: h5py>=3.11; extra == 'dev'
|
|
31
|
+
Requires-Dist: imagecodecs; extra == 'dev'
|
|
32
|
+
Requires-Dist: liffile; extra == 'dev'
|
|
33
|
+
Requires-Dist: nd2; extra == 'dev'
|
|
34
|
+
Requires-Dist: pillow; extra == 'dev'
|
|
35
|
+
Requires-Dist: pylibczirw; (python_version < '3.14') and extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
37
|
+
Requires-Dist: readlif; extra == 'dev'
|
|
38
|
+
Requires-Dist: zstandard; extra == 'dev'
|
|
39
|
+
Provides-Extra: ims
|
|
40
|
+
Requires-Dist: h5py>=3.11; extra == 'ims'
|
|
41
|
+
Provides-Extra: test
|
|
42
|
+
Requires-Dist: h5py>=3.11; extra == 'test'
|
|
43
|
+
Requires-Dist: imagecodecs; extra == 'test'
|
|
44
|
+
Requires-Dist: liffile; extra == 'test'
|
|
45
|
+
Requires-Dist: nd2; extra == 'test'
|
|
46
|
+
Requires-Dist: pillow; extra == 'test'
|
|
47
|
+
Requires-Dist: pylibczirw; (python_version < '3.14') and extra == 'test'
|
|
48
|
+
Requires-Dist: pytest; extra == 'test'
|
|
49
|
+
Requires-Dist: readlif; extra == 'test'
|
|
50
|
+
Requires-Dist: zstandard; extra == 'test'
|
|
51
|
+
Description-Content-Type: text/markdown
|
|
52
|
+
|
|
53
|
+
# micro-reader
|
|
54
|
+
|
|
55
|
+
**Memory-bounded pixel reader for microscopy images.**
|
|
56
|
+
|
|
57
|
+
`micro-reader` reads regions of large microscopy files without loading the entire image into memory. It provides a common interface across TIFF, CZI, ND2, Leica, OIR, Imaris, MRC, and common raster formats.
|
|
58
|
+
|
|
59
|
+
> **Status: pre-alpha (0.0.x)**
|
|
60
|
+
>
|
|
61
|
+
> The API may still change.
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
For the main formats:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install micro-reader
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Optional support:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install "micro-reader[ims]" # Imaris (.ims)
|
|
75
|
+
pip install "micro-reader[codecs]" # additional codecs
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import micro_reader
|
|
82
|
+
|
|
83
|
+
with micro_reader.open("image.czi") as f:
|
|
84
|
+
image = f.images[0]
|
|
85
|
+
|
|
86
|
+
print(image.axes)
|
|
87
|
+
print(image.shape)
|
|
88
|
+
print(image.dtype)
|
|
89
|
+
|
|
90
|
+
# Read only the region you need
|
|
91
|
+
block = image.read(
|
|
92
|
+
c=1,
|
|
93
|
+
z=10,
|
|
94
|
+
y=slice(0, 2048),
|
|
95
|
+
x=slice(1000, 3000),
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
You can also use NumPy-style indexing:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
block = image[0, 1, 10, 0:2048, 1000:3000]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Reads return native-endian NumPy arrays.
|
|
106
|
+
|
|
107
|
+
For each image, you can inspect how much data had to be read internally:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
print(image.stats.over_read)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## How it works
|
|
114
|
+
|
|
115
|
+
`micro-reader` uses three concepts:
|
|
116
|
+
|
|
117
|
+
* **File**: the opened microscopy file
|
|
118
|
+
* **Image**: one image or frame that can be read as an array
|
|
119
|
+
* **Index**: identifies images that are separated by dimensions such as scene, tile, view, or emission wavelength
|
|
120
|
+
|
|
121
|
+
The key distinction is:
|
|
122
|
+
|
|
123
|
+
> **`t`, `c`, `z`, `y`, and `x` are array axes. Other dimensions are represented as image indices.**
|
|
124
|
+
|
|
125
|
+
This lets different microscopy formats use the same interface even when their internal structures are very different.
|
|
126
|
+
|
|
127
|
+
The basic model is:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
File
|
|
131
|
+
└── Image
|
|
132
|
+
├── axes
|
|
133
|
+
├── shape
|
|
134
|
+
├── dtype
|
|
135
|
+
├── index
|
|
136
|
+
├── metadata
|
|
137
|
+
└── read(...)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Files and images
|
|
141
|
+
|
|
142
|
+
Every file is opened as a `micro_reader.File`:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
f = micro_reader.open("image.czi")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
You can inspect which reader and format variant were detected:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
f.backend
|
|
152
|
+
f.format
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For example:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
f.backend -> "czi"
|
|
159
|
+
f.format -> "czi"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
A file contains one or more `Image` objects:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
image = f.images[0]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Each image has an index describing where it came from:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
image.index
|
|
172
|
+
# {"scene": 0, "tile": 3, "view": 0}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
You can inspect all indices in the file:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
f.indices
|
|
179
|
+
f.index_range
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
And select images by index:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
f.select(scene=1, view=0)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
This returns the images matching the requested indices. For example, if a file contains several scenes, tiles, and views, selecting `scene=1, view=0` returns the tiles belonging to that scene and view.
|
|
189
|
+
|
|
190
|
+
### Available index dimensions
|
|
191
|
+
|
|
192
|
+
`micro-reader` uses these index names when a format provides the corresponding dimensions:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
scene
|
|
196
|
+
tile
|
|
197
|
+
rotation
|
|
198
|
+
view
|
|
199
|
+
illumination
|
|
200
|
+
phase
|
|
201
|
+
block
|
|
202
|
+
emission
|
|
203
|
+
excitation
|
|
204
|
+
lifetime
|
|
205
|
+
sequence
|
|
206
|
+
resolution
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Only dimensions actually present in a file appear in an image's index.
|
|
210
|
+
|
|
211
|
+
## Axes
|
|
212
|
+
|
|
213
|
+
An image array uses these axes:
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
t c z y x
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
and optionally:
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
s
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
for RGB samples.
|
|
226
|
+
|
|
227
|
+
For example:
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
image.axes
|
|
231
|
+
# "tczyx"
|
|
232
|
+
|
|
233
|
+
image.shape
|
|
234
|
+
# (1, 3, 40, 5120, 6144)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Other dimensions, such as illumination, emission, tile, etc., are represented as image indices rather than array axes, as discussed above.
|
|
238
|
+
|
|
239
|
+
### Changing axis order
|
|
240
|
+
|
|
241
|
+
You can request a specific axis order:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
image = f.images[0].as_axes("tczyx")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
or when opening the file:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
f = micro_reader.open("image.tif", axes="tczyx")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
You can also request the canonical order:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
f = micro_reader.open("image.tif", axes="canonical")
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Canonical order is:
|
|
260
|
+
|
|
261
|
+
```text
|
|
262
|
+
t c z y x
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
followed by `s` when RGB samples are present.
|
|
266
|
+
|
|
267
|
+
Missing axes are added with size 1 and existing axes are reordered.
|
|
268
|
+
|
|
269
|
+
### Removing singleton axes
|
|
270
|
+
|
|
271
|
+
You can remove singleton axes with:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
plane = image.squeeze()
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
or:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
plane = image.squeeze("tz")
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Only axes with size 1 can be removed. `y` and `x` are never removed.
|
|
284
|
+
|
|
285
|
+
### RGB samples
|
|
286
|
+
|
|
287
|
+
By default, RGB samples are represented by the `s` axis.
|
|
288
|
+
|
|
289
|
+
You can instead represent RGB as channels:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
f = micro_reader.open(
|
|
293
|
+
"image.tif",
|
|
294
|
+
samples="channels",
|
|
295
|
+
)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
If there are existing channels already, RGB samples will fold with them, leading to a total of c × s channels.
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
## Ambiguous dimensions
|
|
302
|
+
|
|
303
|
+
Some formats do not say what a dimension represents.
|
|
304
|
+
|
|
305
|
+
For example, a plain multi-page TIFF may contain 80 pages without saying whether those pages are `z`, `t`, or something else.
|
|
306
|
+
|
|
307
|
+
`micro-reader` represents such pages using the `sequence` index:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
f = micro_reader.open("stack.tif")
|
|
311
|
+
|
|
312
|
+
print(f.index_range)
|
|
313
|
+
# {"scene": 1, "sequence": 80}
|
|
314
|
+
|
|
315
|
+
print(len(f.images))
|
|
316
|
+
# 80
|
|
317
|
+
|
|
318
|
+
print(f.images[0].index)
|
|
319
|
+
# {"scene": 0, "sequence": 0}
|
|
320
|
+
|
|
321
|
+
print(f.images[0].axes)
|
|
322
|
+
# yx
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
If you know that the pages represent `z`, you can tell the reader:
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
f = micro_reader.open(
|
|
329
|
+
"stack.tif",
|
|
330
|
+
rename={"sequence": "z"},
|
|
331
|
+
)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
The result is a single image with `zyx` axes.
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
print(f.images[0].axes)
|
|
338
|
+
# zyx
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
## Resolution levels
|
|
343
|
+
|
|
344
|
+
Pyramidal files can contain multiple resolution levels:
|
|
345
|
+
|
|
346
|
+
```text
|
|
347
|
+
resolution 0 → full resolution
|
|
348
|
+
resolution 1 → lower resolution
|
|
349
|
+
resolution 2 → even lower resolution
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
By default, `f.images` contains only the full-resolution images:
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
f = micro_reader.open(
|
|
356
|
+
path,
|
|
357
|
+
resolutions="base", # the default, can be left out
|
|
358
|
+
)
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Each image of a pyramidal file carries its resolution level in its index. In this mode, every image in `f.images` is level 0:
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
print(f.index_range)
|
|
365
|
+
# {"scene": 4, "resolution": 1}
|
|
366
|
+
|
|
367
|
+
print(f.images[0].index)
|
|
368
|
+
# {"scene": 0, "resolution": 0}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`index_range` shows `"resolution": 1` because only the full-resolution level is in `f.images`.
|
|
372
|
+
|
|
373
|
+
Lower-resolution versions are available through the image via `image.resolutions`:
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
image_scene0 = f.images[0]
|
|
377
|
+
|
|
378
|
+
image_scene0.is_pyramidal
|
|
379
|
+
# True
|
|
380
|
+
|
|
381
|
+
img_scene0_level2 = image_scene0.resolutions[2] # a lower-resolution image
|
|
382
|
+
|
|
383
|
+
img_scene0_level2.index
|
|
384
|
+
# {"scene": 0, "resolution": 2}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
If you prefer to expose every resolution level as an image directly under the `File` object:
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
f = micro_reader.open(
|
|
391
|
+
path,
|
|
392
|
+
resolutions="all", # instead of "base"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
print(f.index_range)
|
|
396
|
+
# {"scene": 4, "resolution": 6}
|
|
397
|
+
|
|
398
|
+
img_scene0_level2 = f.select(scene=0, resolution=2)[0]
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Supported pyramids include:
|
|
402
|
+
|
|
403
|
+
* **Imaris**: all stored levels
|
|
404
|
+
* **TIFF**: OME-TIFF and other TIFF pyramids such as SVS and NDPI
|
|
405
|
+
* **CZI**: stored pyramids of whole scenes
|
|
406
|
+
|
|
407
|
+
Other formats expose a single resolution level.
|
|
408
|
+
|
|
409
|
+
CZI pyramids belong to whole scenes. Separately opened CZI mosaic tiles therefore do not have their own resolution levels.
|
|
410
|
+
|
|
411
|
+
## Mosaic tiles
|
|
412
|
+
|
|
413
|
+
Mosaic tiles are separate images by default.
|
|
414
|
+
|
|
415
|
+
For CZI files, you can instead request stitched scenes:
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
f = micro_reader.open(
|
|
419
|
+
path,
|
|
420
|
+
tiles="stitched",
|
|
421
|
+
)
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
The tiles are composed according to the format's stitching information.
|
|
425
|
+
|
|
426
|
+
Currently, stitching is supported for **CZI only**.
|
|
427
|
+
|
|
428
|
+
## Metadata
|
|
429
|
+
|
|
430
|
+
Pixel and channel metadata are available through:
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
meta = image.metadata
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
For example:
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
meta.pixel_sizes
|
|
440
|
+
meta.position
|
|
441
|
+
meta.channels
|
|
442
|
+
meta.index_values
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
A typical result might look like:
|
|
446
|
+
|
|
447
|
+
```python
|
|
448
|
+
meta.pixel_sizes
|
|
449
|
+
# {
|
|
450
|
+
# "x": Quantity(0.065, "micrometer"),
|
|
451
|
+
# "z": Quantity(...),
|
|
452
|
+
# "t": Quantity(2.5, "second"),
|
|
453
|
+
# }
|
|
454
|
+
|
|
455
|
+
meta.position
|
|
456
|
+
# {
|
|
457
|
+
# "x": Quantity(1250.0, "micrometer"),
|
|
458
|
+
# "y": ...
|
|
459
|
+
# }
|
|
460
|
+
|
|
461
|
+
meta.channels[0]
|
|
462
|
+
# Channel(
|
|
463
|
+
# name="DAPI",
|
|
464
|
+
# color="#00A0FF",
|
|
465
|
+
# emission_wavelength=Quantity(461.0, "nanometer"),
|
|
466
|
+
# )
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Metadata principles
|
|
470
|
+
|
|
471
|
+
`micro-reader` follows a few important rules:
|
|
472
|
+
|
|
473
|
+
* Units use OME-Zarr-compatible names such as `micrometer`, `nanometer`, and `millisecond`.
|
|
474
|
+
* Missing metadata is left as `None` or absent.
|
|
475
|
+
* Pixel sizes are never invented as `1.0`.
|
|
476
|
+
* Colours are not invented when the file does not provide them.
|
|
477
|
+
* Each resolution level has its own pixel sizes.
|
|
478
|
+
|
|
479
|
+
### Image position
|
|
480
|
+
|
|
481
|
+
`image.metadata.position` gives the position of the centre of the first pixel in stage coordinates.
|
|
482
|
+
|
|
483
|
+
Where possible, format-specific coordinate conventions are converted to this common representation.
|
|
484
|
+
|
|
485
|
+
The position is omitted when it cannot be determined reliably.
|
|
486
|
+
|
|
487
|
+
## Supported formats
|
|
488
|
+
|
|
489
|
+
| Format | Notes |
|
|
490
|
+
| -------------------- | ----------------------------------------------------- |
|
|
491
|
+
| **TIFF / BigTIFF** | TIFF, OME-TIFF, ImageJ, LSM, STK and TIFF pyramids |
|
|
492
|
+
| **CZI** | Zeiss CZI, including mosaic tiles and stitched scenes |
|
|
493
|
+
| **ND2** | Nikon ND2, including legacy JPEG 2000 ND2 |
|
|
494
|
+
| **Leica** | LIF, LOF, LIFEXT, XLIF, XLEF, XLCF |
|
|
495
|
+
| **OIR** | Olympus / Evident FluoView |
|
|
496
|
+
| **Imaris** | `.ims`, with the `[ims]` extra |
|
|
497
|
+
| **MRC** | Common MRC modes and extended headers |
|
|
498
|
+
| **JPEG / PNG / BMP** | Common raster image formats |
|
|
499
|
+
|
|
500
|
+
All formats use the same `File`, `Image`, axes, indexing, metadata, and reading interface wherever possible.
|
|
501
|
+
|
|
502
|
+
Format-specific behaviour is described below.
|
|
503
|
+
|
|
504
|
+
### TIFF
|
|
505
|
+
|
|
506
|
+
Supported TIFF formats include:
|
|
507
|
+
|
|
508
|
+
* TIFF
|
|
509
|
+
* BigTIFF
|
|
510
|
+
* OME-TIFF
|
|
511
|
+
* ImageJ
|
|
512
|
+
* Zeiss LSM
|
|
513
|
+
* MetaMorph STK
|
|
514
|
+
|
|
515
|
+
Compressed TIFF data is decoded only for the strips or tiles needed by the requested region.
|
|
516
|
+
|
|
517
|
+
Supported codecs include:
|
|
518
|
+
|
|
519
|
+
* LZW
|
|
520
|
+
* Deflate
|
|
521
|
+
* PackBits
|
|
522
|
+
* LZMA
|
|
523
|
+
* zstd
|
|
524
|
+
* JPEG
|
|
525
|
+
* JPEG 2000
|
|
526
|
+
* WebP
|
|
527
|
+
* JPEG XL
|
|
528
|
+
* PNG
|
|
529
|
+
* EER
|
|
530
|
+
|
|
531
|
+
Additional codecs such as JPEG XR and LERC are available with:
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
pip install "micro-reader[codecs]"
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### CZI
|
|
538
|
+
|
|
539
|
+
CZI files support:
|
|
540
|
+
|
|
541
|
+
* mosaic tiles
|
|
542
|
+
* scenes
|
|
543
|
+
* views
|
|
544
|
+
* rotations
|
|
545
|
+
* RGB
|
|
546
|
+
* large files
|
|
547
|
+
* stored scene pyramids
|
|
548
|
+
|
|
549
|
+
CZI-specific operations include:
|
|
550
|
+
|
|
551
|
+
```python
|
|
552
|
+
image.read_region(x, y, w, h)
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
and:
|
|
556
|
+
|
|
557
|
+
```python
|
|
558
|
+
f.canvas()
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
`image.origin` gives the image corner in the file's pixel coordinates.
|
|
562
|
+
|
|
563
|
+
Damaged subblock directories can be recovered with a `DamagedFileWarning`. Details are available through:
|
|
564
|
+
|
|
565
|
+
```python
|
|
566
|
+
f.damage
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### ND2
|
|
570
|
+
|
|
571
|
+
Nikon ND2 support includes:
|
|
572
|
+
|
|
573
|
+
* ND2 version 3+
|
|
574
|
+
* legacy JPEG 2000 ND2 versions 1 and 2
|
|
575
|
+
* one image per XY position
|
|
576
|
+
* `t c z y x` axes
|
|
577
|
+
* RGB samples
|
|
578
|
+
|
|
579
|
+
### Leica
|
|
580
|
+
|
|
581
|
+
Supported Leica formats include:
|
|
582
|
+
|
|
583
|
+
* LIF
|
|
584
|
+
* LOF
|
|
585
|
+
* LIFEXT
|
|
586
|
+
* XLIF
|
|
587
|
+
* XLEF
|
|
588
|
+
* XLCF
|
|
589
|
+
|
|
590
|
+
Leica files can contain nested analysis results, tiles, rotations, and emission/excitation scans.
|
|
591
|
+
|
|
592
|
+
These are represented using the common image/index model.
|
|
593
|
+
|
|
594
|
+
### OIR
|
|
595
|
+
|
|
596
|
+
Olympus / Evident FluoView OIR files are supported, including companion files and spectral scans.
|
|
597
|
+
|
|
598
|
+
### Imaris
|
|
599
|
+
|
|
600
|
+
Install Imaris support with:
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
pip install "micro-reader[ims]"
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
Imaris resolution level 0 is exposed at the actual image size, with Imaris chunk padding removed.
|
|
607
|
+
|
|
608
|
+
Chunked compression is decoded in parallel.
|
|
609
|
+
|
|
610
|
+
### MRC
|
|
611
|
+
|
|
612
|
+
Supported MRC modes include:
|
|
613
|
+
|
|
614
|
+
```text
|
|
615
|
+
0, 1, 2, 4, 6, 12
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Both byte orders and extended headers are supported.
|
|
619
|
+
|
|
620
|
+
### JPEG, PNG and BMP
|
|
621
|
+
|
|
622
|
+
Supported features include:
|
|
623
|
+
|
|
624
|
+
* PNG colour types and bit depths
|
|
625
|
+
* interlaced PNG
|
|
626
|
+
* BMP palettes, RLE and bit fields
|
|
627
|
+
* OS/2 BMP headers
|
|
628
|
+
* baseline, progressive and lossless JPEG
|
|
629
|
+
|
|
630
|
+
Raster images are normally decoded as a whole on the first read.
|
|
631
|
+
|
|
632
|
+
For images larger than 256 MB:
|
|
633
|
+
|
|
634
|
+
* non-interlaced PNGs are read by rows
|
|
635
|
+
* JPEGs with restart markers are read in bands of rows
|
|
636
|
+
|
|
637
|
+
For paletted images, pixel values remain indices and the palette is available through:
|
|
638
|
+
|
|
639
|
+
```python
|
|
640
|
+
image.palette
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
## Performance
|
|
644
|
+
|
|
645
|
+
Compressed data is decoded using a shared thread pool.
|
|
646
|
+
|
|
647
|
+
By default, the decoder uses one thread per CPU.
|
|
648
|
+
|
|
649
|
+
You can change this with:
|
|
650
|
+
|
|
651
|
+
```python
|
|
652
|
+
micro_reader.set_decode_threads(4)
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
or:
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
export MICRO_READER_DECODE_THREADS=4
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
The native codec implementations are provided by the separate Rust package `micro-reader-codecs`, which is installed automatically.
|
|
662
|
+
|
|
663
|
+
## Memory usage
|
|
664
|
+
|
|
665
|
+
Memory usage is controlled by a configurable limit:
|
|
666
|
+
|
|
667
|
+
```python
|
|
668
|
+
micro_reader.set_memory_limit("8GB")
|
|
669
|
+
micro_reader.memory_limit()
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
The default limit is one quarter of the computer's memory.
|
|
673
|
+
|
|
674
|
+
You can also set it through the environment:
|
|
675
|
+
|
|
676
|
+
```bash
|
|
677
|
+
export MICRO_READER_MEMORY_LIMIT=8GB
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
For uncompressed data, only the requested rows and columns are read.
|
|
681
|
+
|
|
682
|
+
Compressed data is stored in blocks such as TIFF strips or tiles, CZI subblocks, and ND2 frames. Small blocks are decoded as a whole. Blocks larger than 16 MB are read in parts where the codec allows it.
|
|
683
|
+
|
|
684
|
+
### Streamed decoding
|
|
685
|
+
|
|
686
|
+
Deflate, LZW, zstd, PackBits, and LZMA blocks are decoded from their start while keeping only the rows needed by the requested region.
|
|
687
|
+
|
|
688
|
+
This covers, among others:
|
|
689
|
+
|
|
690
|
+
* TIFF strips and tiles
|
|
691
|
+
* ND2 lossless frames
|
|
692
|
+
* CZI subblocks
|
|
693
|
+
* non-interlaced PNG
|
|
694
|
+
|
|
695
|
+
Reading a block from top to bottom stays a single pass: each read continues where the last one stopped.
|
|
696
|
+
|
|
697
|
+
Jumping back in a large block, as a viewer does, resumes from a checkpoint for deflate. For LZW and zstd, the block is decoded again from its start.
|
|
698
|
+
|
|
699
|
+
### Region decoding
|
|
700
|
+
|
|
701
|
+
JPEG 2000 and JPEG XL can decode only the requested region.
|
|
702
|
+
|
|
703
|
+
This is used for TIFF, CZI, and legacy ND2 where supported by the format.
|
|
704
|
+
|
|
705
|
+
### JPEG restart markers
|
|
706
|
+
|
|
707
|
+
JPEG blocks with restart markers can be decoded only for the part covering the requested rows.
|
|
708
|
+
|
|
709
|
+
### Codecs that require whole-block decoding
|
|
710
|
+
|
|
711
|
+
Some blocks can only be decoded as a whole, including:
|
|
712
|
+
|
|
713
|
+
* progressive and lossless JPEG
|
|
714
|
+
* JPEG without restart markers
|
|
715
|
+
* interlaced PNG
|
|
716
|
+
* JPEG XR
|
|
717
|
+
* CZI zstd with hi-lo packing
|
|
718
|
+
|
|
719
|
+
### How the limit is enforced
|
|
720
|
+
|
|
721
|
+
Every decode, whole or in parts, counts against the memory limit.
|
|
722
|
+
|
|
723
|
+
If a block that must be decoded whole is larger than the configured limit, `MemoryLimitError` is raised instead of allowing the process to run out of memory.
|
|
724
|
+
|
|
725
|
+
Blocks decoded concurrently from multiple threads are accounted for together. If a decode does not fit within the current memory budget, it waits until sufficient memory is available.
|
|
726
|
+
|
|
727
|
+
## Design principles
|
|
728
|
+
|
|
729
|
+
`micro-reader` aims to keep the application-facing interface small even when the underlying file formats are complex.
|
|
730
|
+
|
|
731
|
+
Applications work with the same `File` and `Image` model described in [How it works](#how-it-works). Format-specific complexity stays inside the reader.
|
|
732
|
+
|
|
733
|
+
The goal is that an application can open a microscopy file, identify the image it needs, read only the required pixels, and obtain standardized metadata without needing to understand the vendor-specific file structure.
|
|
734
|
+
|
|
735
|
+
## License
|
|
736
|
+
|
|
737
|
+
MIT
|
|
738
|
+
|
|
739
|
+
Source code, tests, and development documentation:
|
|
740
|
+
|
|
741
|
+
https://github.com/bugraoezdemir/micro-reader
|