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.
@@ -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