micro-reader 0.0.1__tar.gz → 0.0.3__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.
- {micro_reader-0.0.1 → micro_reader-0.0.3}/PKG-INFO +66 -2
- {micro_reader-0.0.1 → micro_reader-0.0.3}/README.md +65 -1
- {micro_reader-0.0.1 → micro_reader-0.0.3}/pyproject.toml +1 -1
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/__init__.py +10 -5
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_base.py +104 -12
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_errors.py +11 -0
- micro_reader-0.0.3/src/micro_reader/_fileset.py +358 -0
- micro_reader-0.0.3/src/micro_reader/_io.py +159 -0
- micro_reader-0.0.3/src/micro_reader/_outline.py +71 -0
- micro_reader-0.0.3/src/micro_reader/_plate.py +150 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_registry.py +23 -9
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/_czi_metadata.py +45 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/_tiff_metadata.py +78 -26
- micro_reader-0.0.3/src/micro_reader/backends/companion.py +624 -0
- micro_reader-0.0.3/src/micro_reader/backends/cv7000.py +265 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/czi.py +69 -7
- micro_reader-0.0.3/src/micro_reader/backends/harmony.py +206 -0
- micro_reader-0.0.3/src/micro_reader/backends/incell.py +173 -0
- micro_reader-0.0.3/src/micro_reader/backends/metaxpress.py +208 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/mrc.py +32 -1
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/nd2.py +5 -1
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/tiff.py +94 -6
- micro_reader-0.0.1/src/micro_reader/_io.py +0 -83
- {micro_reader-0.0.1 → micro_reader-0.0.3}/.gitignore +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/LICENSE +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_axes.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_codecs.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_index.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_jpeg_restarts.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_memory.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_metadata.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_pool.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/_stream.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/__init__.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/_lif_metadata.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/_nd2_legacy.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/_nd2_metadata.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/_nd2_structure.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/ims.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/lif.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/oir.py +0 -0
- {micro_reader-0.0.1 → micro_reader-0.0.3}/src/micro_reader/backends/raster.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: micro-reader
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: Low-level, memory-bounded pixel reading for microscopy formats: read exactly the rectangle you ask for.
|
|
5
5
|
Project-URL: Homepage, https://github.com/bugraoezdemir/micro-reader
|
|
6
6
|
Project-URL: Source, https://github.com/bugraoezdemir/micro-reader
|
|
@@ -192,6 +192,10 @@ This returns the images matching the requested indices. For example, if a file c
|
|
|
192
192
|
`micro-reader` uses these index names when a format provides the corresponding dimensions:
|
|
193
193
|
|
|
194
194
|
```text
|
|
195
|
+
plate
|
|
196
|
+
acquisition
|
|
197
|
+
well
|
|
198
|
+
field
|
|
195
199
|
scene
|
|
196
200
|
tile
|
|
197
201
|
rotation
|
|
@@ -203,6 +207,7 @@ emission
|
|
|
203
207
|
excitation
|
|
204
208
|
lifetime
|
|
205
209
|
sequence
|
|
210
|
+
part
|
|
206
211
|
resolution
|
|
207
212
|
```
|
|
208
213
|
|
|
@@ -425,6 +430,37 @@ The tiles are composed according to the format's stitching information.
|
|
|
425
430
|
|
|
426
431
|
Currently, stitching is supported for **CZI only**.
|
|
427
432
|
|
|
433
|
+
## Plates
|
|
434
|
+
|
|
435
|
+
High-content screening plates are opened through their index file, for example `Index.idx.xml` for Harmony. Each field is one image, indexed by `plate`, `acquisition`, `well` and `field`.
|
|
436
|
+
|
|
437
|
+
The plate layout is available through:
|
|
438
|
+
|
|
439
|
+
```python
|
|
440
|
+
plate = f.plates[0]
|
|
441
|
+
plate.wells[0].name
|
|
442
|
+
# "B2"
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Images can be reached through the wells, or selected by well:
|
|
446
|
+
|
|
447
|
+
```python
|
|
448
|
+
image = plate.wells[0].fields[0].image
|
|
449
|
+
f.select(well=plate.well_index("B2"))
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
`f.outline()` shows the plates, wells and fields of a file.
|
|
453
|
+
|
|
454
|
+
Zeiss CZI plates are read as plates with `plates=True`, and as scenes otherwise:
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
f = micro_reader.open("plate.czi", plates=True)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Missing plane files are read as zeros, with a `MissingImagesWarning`. A field without any of its files is left out.
|
|
461
|
+
|
|
462
|
+
When the channels of a field have different z sampling, `image.parts` gives one image per group of channels that share it.
|
|
463
|
+
|
|
428
464
|
## Metadata
|
|
429
465
|
|
|
430
466
|
Pixel and channel metadata are available through:
|
|
@@ -489,12 +525,14 @@ The position is omitted when it cannot be determined reliably.
|
|
|
489
525
|
| Format | Notes |
|
|
490
526
|
| -------------------- | ----------------------------------------------------- |
|
|
491
527
|
| **TIFF / BigTIFF** | TIFF, OME-TIFF, ImageJ, LSM, STK and TIFF pyramids |
|
|
528
|
+
| **OME companion** | `.companion.ome` sets of OME-TIFF files |
|
|
492
529
|
| **CZI** | Zeiss CZI, including mosaic tiles and stitched scenes |
|
|
493
530
|
| **ND2** | Nikon ND2, including legacy JPEG 2000 ND2 |
|
|
494
531
|
| **Leica** | LIF, LOF, LIFEXT, XLIF, XLEF, XLCF |
|
|
495
532
|
| **OIR** | Olympus / Evident FluoView |
|
|
496
533
|
| **Imaris** | `.ims`, with the `[ims]` extra |
|
|
497
534
|
| **MRC** | Common MRC modes and extended headers |
|
|
535
|
+
| **Plates (HCS)** | Harmony, MetaXpress, IN Cell and CellVoyager plates |
|
|
498
536
|
| **JPEG / PNG / BMP** | Common raster image formats |
|
|
499
537
|
|
|
500
538
|
All formats use the same `File`, `Image`, axes, indexing, metadata, and reading interface wherever possible.
|
|
@@ -534,6 +572,16 @@ Additional codecs such as JPEG XR and LERC are available with:
|
|
|
534
572
|
pip install "micro-reader[codecs]"
|
|
535
573
|
```
|
|
536
574
|
|
|
575
|
+
### OME companion files
|
|
576
|
+
|
|
577
|
+
An OME companion file (`.companion.ome`) describes images stored in separate TIFF files. Opening the companion reads all those images and groups them together as one `t c z y x` image:
|
|
578
|
+
|
|
579
|
+
```python
|
|
580
|
+
f = micro_reader.open("data.companion.ome")
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Opening one of the TIFF files on its own reads only that file, with its pixel sizes, channels and position taken from the companion. If the companion is missing, the file is read without pixel sizes, with a `CompanionWarning`.
|
|
584
|
+
|
|
537
585
|
### CZI
|
|
538
586
|
|
|
539
587
|
CZI files support:
|
|
@@ -677,7 +725,23 @@ You can also set it through the environment:
|
|
|
677
725
|
export MICRO_READER_MEMORY_LIMIT=8GB
|
|
678
726
|
```
|
|
679
727
|
|
|
680
|
-
|
|
728
|
+
The limit covers the blocks being decoded at once. Decoded blocks are also kept for reuse,
|
|
729
|
+
so a block shared by neighbouring reads is not decoded twice. All open files share one
|
|
730
|
+
cache limit, 256 MB by default:
|
|
731
|
+
|
|
732
|
+
```python
|
|
733
|
+
micro_reader.set_cache_limit("512MB")
|
|
734
|
+
micro_reader.cache_limit()
|
|
735
|
+
micro_reader.cached_bytes() # what the caches hold now
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
or `export MICRO_READER_CACHE_LIMIT=512MB`. The process then uses at most the memory
|
|
739
|
+
limit plus the cache limit for decoding, however many files are open. One exception: a
|
|
740
|
+
single picture (PNG, JPEG, ... up to 256 MB) is kept decoded by its own open file.
|
|
741
|
+
|
|
742
|
+
For uncompressed data, only the requested rows are read. When you ask for part of
|
|
743
|
+
each row, the rest of the row is read along with it if that is faster than reading the
|
|
744
|
+
rows one by one.
|
|
681
745
|
|
|
682
746
|
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
747
|
|
|
@@ -140,6 +140,10 @@ This returns the images matching the requested indices. For example, if a file c
|
|
|
140
140
|
`micro-reader` uses these index names when a format provides the corresponding dimensions:
|
|
141
141
|
|
|
142
142
|
```text
|
|
143
|
+
plate
|
|
144
|
+
acquisition
|
|
145
|
+
well
|
|
146
|
+
field
|
|
143
147
|
scene
|
|
144
148
|
tile
|
|
145
149
|
rotation
|
|
@@ -151,6 +155,7 @@ emission
|
|
|
151
155
|
excitation
|
|
152
156
|
lifetime
|
|
153
157
|
sequence
|
|
158
|
+
part
|
|
154
159
|
resolution
|
|
155
160
|
```
|
|
156
161
|
|
|
@@ -373,6 +378,37 @@ The tiles are composed according to the format's stitching information.
|
|
|
373
378
|
|
|
374
379
|
Currently, stitching is supported for **CZI only**.
|
|
375
380
|
|
|
381
|
+
## Plates
|
|
382
|
+
|
|
383
|
+
High-content screening plates are opened through their index file, for example `Index.idx.xml` for Harmony. Each field is one image, indexed by `plate`, `acquisition`, `well` and `field`.
|
|
384
|
+
|
|
385
|
+
The plate layout is available through:
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
plate = f.plates[0]
|
|
389
|
+
plate.wells[0].name
|
|
390
|
+
# "B2"
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Images can be reached through the wells, or selected by well:
|
|
394
|
+
|
|
395
|
+
```python
|
|
396
|
+
image = plate.wells[0].fields[0].image
|
|
397
|
+
f.select(well=plate.well_index("B2"))
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`f.outline()` shows the plates, wells and fields of a file.
|
|
401
|
+
|
|
402
|
+
Zeiss CZI plates are read as plates with `plates=True`, and as scenes otherwise:
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
f = micro_reader.open("plate.czi", plates=True)
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Missing plane files are read as zeros, with a `MissingImagesWarning`. A field without any of its files is left out.
|
|
409
|
+
|
|
410
|
+
When the channels of a field have different z sampling, `image.parts` gives one image per group of channels that share it.
|
|
411
|
+
|
|
376
412
|
## Metadata
|
|
377
413
|
|
|
378
414
|
Pixel and channel metadata are available through:
|
|
@@ -437,12 +473,14 @@ The position is omitted when it cannot be determined reliably.
|
|
|
437
473
|
| Format | Notes |
|
|
438
474
|
| -------------------- | ----------------------------------------------------- |
|
|
439
475
|
| **TIFF / BigTIFF** | TIFF, OME-TIFF, ImageJ, LSM, STK and TIFF pyramids |
|
|
476
|
+
| **OME companion** | `.companion.ome` sets of OME-TIFF files |
|
|
440
477
|
| **CZI** | Zeiss CZI, including mosaic tiles and stitched scenes |
|
|
441
478
|
| **ND2** | Nikon ND2, including legacy JPEG 2000 ND2 |
|
|
442
479
|
| **Leica** | LIF, LOF, LIFEXT, XLIF, XLEF, XLCF |
|
|
443
480
|
| **OIR** | Olympus / Evident FluoView |
|
|
444
481
|
| **Imaris** | `.ims`, with the `[ims]` extra |
|
|
445
482
|
| **MRC** | Common MRC modes and extended headers |
|
|
483
|
+
| **Plates (HCS)** | Harmony, MetaXpress, IN Cell and CellVoyager plates |
|
|
446
484
|
| **JPEG / PNG / BMP** | Common raster image formats |
|
|
447
485
|
|
|
448
486
|
All formats use the same `File`, `Image`, axes, indexing, metadata, and reading interface wherever possible.
|
|
@@ -482,6 +520,16 @@ Additional codecs such as JPEG XR and LERC are available with:
|
|
|
482
520
|
pip install "micro-reader[codecs]"
|
|
483
521
|
```
|
|
484
522
|
|
|
523
|
+
### OME companion files
|
|
524
|
+
|
|
525
|
+
An OME companion file (`.companion.ome`) describes images stored in separate TIFF files. Opening the companion reads all those images and groups them together as one `t c z y x` image:
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
f = micro_reader.open("data.companion.ome")
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Opening one of the TIFF files on its own reads only that file, with its pixel sizes, channels and position taken from the companion. If the companion is missing, the file is read without pixel sizes, with a `CompanionWarning`.
|
|
532
|
+
|
|
485
533
|
### CZI
|
|
486
534
|
|
|
487
535
|
CZI files support:
|
|
@@ -625,7 +673,23 @@ You can also set it through the environment:
|
|
|
625
673
|
export MICRO_READER_MEMORY_LIMIT=8GB
|
|
626
674
|
```
|
|
627
675
|
|
|
628
|
-
|
|
676
|
+
The limit covers the blocks being decoded at once. Decoded blocks are also kept for reuse,
|
|
677
|
+
so a block shared by neighbouring reads is not decoded twice. All open files share one
|
|
678
|
+
cache limit, 256 MB by default:
|
|
679
|
+
|
|
680
|
+
```python
|
|
681
|
+
micro_reader.set_cache_limit("512MB")
|
|
682
|
+
micro_reader.cache_limit()
|
|
683
|
+
micro_reader.cached_bytes() # what the caches hold now
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
or `export MICRO_READER_CACHE_LIMIT=512MB`. The process then uses at most the memory
|
|
687
|
+
limit plus the cache limit for decoding, however many files are open. One exception: a
|
|
688
|
+
single picture (PNG, JPEG, ... up to 256 MB) is kept decoded by its own open file.
|
|
689
|
+
|
|
690
|
+
For uncompressed data, only the requested rows are read. When you ask for part of
|
|
691
|
+
each row, the rest of the row is read along with it if that is faster than reading the
|
|
692
|
+
rows one by one.
|
|
629
693
|
|
|
630
694
|
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.
|
|
631
695
|
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "micro-reader"
|
|
9
|
-
version = "0.0.
|
|
9
|
+
version = "0.0.3"
|
|
10
10
|
description = "Low-level, memory-bounded pixel reading for microscopy formats: read exactly the rectangle you ask for."
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.11"
|
|
@@ -4,20 +4,25 @@
|
|
|
4
4
|
>>> with micro_reader.open("image.mrc") as f:
|
|
5
5
|
... block = f.images[0].read(z=0, y=slice(0, 512))
|
|
6
6
|
"""
|
|
7
|
-
from ._base import INDEX_NAMES, SAMPLED_INDICES, File, Image, ReadStats
|
|
7
|
+
from ._base import INDEX_NAMES, PLATE_INDICES, SAMPLED_INDICES, File, Image, ReadStats
|
|
8
8
|
from ._metadata import Channel, Metadata, Quantity
|
|
9
|
-
from .
|
|
9
|
+
from ._plate import Acquisition, Field, Plate, Well
|
|
10
|
+
from ._errors import (CompanionWarning, CorruptFileError, MicroReaderError,
|
|
11
|
+
MissingImagesWarning, UnsupportedFormatError)
|
|
10
12
|
from ._memory import MemoryLimitError, memory_limit, set_memory_limit
|
|
13
|
+
from ._io import cache_limit, cached_bytes, set_cache_limit
|
|
11
14
|
from ._pool import decode_threads, set_decode_threads
|
|
12
15
|
from ._registry import BACKENDS, open # noqa: A004 - the public entry point
|
|
13
16
|
from .backends.czi import DamagedFileWarning
|
|
14
17
|
|
|
15
|
-
__version__ = "0.0.
|
|
18
|
+
__version__ = "0.0.3"
|
|
16
19
|
|
|
17
20
|
__all__ = [
|
|
18
|
-
"open", "File", "Image", "INDEX_NAMES", "
|
|
19
|
-
"Metadata", "Channel", "Quantity",
|
|
21
|
+
"open", "File", "Image", "INDEX_NAMES", "PLATE_INDICES", "SAMPLED_INDICES", "ReadStats",
|
|
22
|
+
"BACKENDS", "Metadata", "Channel", "Quantity", "Plate", "Well", "Field", "Acquisition",
|
|
20
23
|
"decode_threads", "set_decode_threads", "memory_limit", "set_memory_limit",
|
|
24
|
+
"cache_limit", "set_cache_limit", "cached_bytes",
|
|
21
25
|
"MicroReaderError", "UnsupportedFormatError", "CorruptFileError", "MemoryLimitError", "DamagedFileWarning",
|
|
26
|
+
"MissingImagesWarning", "CompanionWarning",
|
|
22
27
|
"__version__",
|
|
23
28
|
]
|
|
@@ -65,9 +65,12 @@ class Image:
|
|
|
65
65
|
self.name = name if name is not None else f"image{position}"
|
|
66
66
|
#: which image of the file: {"scene": .., "tile": .., "rotation": ..,
|
|
67
67
|
#: "view": .., "resolution": ..} -- only the indices the file has
|
|
68
|
-
#: ("
|
|
68
|
+
#: (plates: "well", "field", ... instead of "scene"; see INDEX_NAMES)
|
|
69
69
|
self.index = dict(index) if index is not None else {"scene": self._position}
|
|
70
70
|
self._resolutions: Optional[list] = None
|
|
71
|
+
#: a ragged image's rectangular parts / a part's whole (see ``parts``)
|
|
72
|
+
self._parts: Optional[list] = None
|
|
73
|
+
self._whole: Optional["Image"] = None
|
|
71
74
|
self._metadata = None # backends set a _metadata.Metadata
|
|
72
75
|
#: None: any rectangle is read exactly. Otherwise the smallest block
|
|
73
76
|
#: the backend reads at once (e.g. a compressed tile or strip).
|
|
@@ -108,6 +111,55 @@ class Image:
|
|
|
108
111
|
(``resolutions`` holds more than the image itself)."""
|
|
109
112
|
return len(self.resolutions) > 1
|
|
110
113
|
|
|
114
|
+
# An image is either a pyramid (the logical image: level 0, also when the
|
|
115
|
+
# file stores no lower levels) or a lower resolution level of one.
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def is_pyramid(self) -> bool:
|
|
119
|
+
"""Whether this image is a pyramid, the image itself at full
|
|
120
|
+
resolution, rather than a lower resolution level of one. True for
|
|
121
|
+
every image without lower levels too."""
|
|
122
|
+
return self.pyramid is self
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def level(self) -> int:
|
|
126
|
+
"""This image's resolution level in its pyramid: 0 for the pyramid
|
|
127
|
+
itself (full resolution), 1, 2, ... for lower levels."""
|
|
128
|
+
for n, member in enumerate(self.resolutions):
|
|
129
|
+
if member is self:
|
|
130
|
+
return n
|
|
131
|
+
raise RuntimeError(f"{self.name!r} is not in its own pyramid")
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def pyramid(self) -> "Image":
|
|
135
|
+
"""The pyramid this image belongs to: itself at level 0, the
|
|
136
|
+
full-resolution image for a lower level."""
|
|
137
|
+
return self.resolutions[0]
|
|
138
|
+
|
|
139
|
+
# A ragged image: its channels do not share one sampling (e.g. one channel
|
|
140
|
+
# a z-stack, the others single planes; docs/AXES.md). It is a whole made of
|
|
141
|
+
# rectangular parts, each an ordinary lazy Image with its own z / t and
|
|
142
|
+
# metadata. The whole reads as one array, padded with zeros, when the
|
|
143
|
+
# parts lie on a common grid; otherwise reading it raises.
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def parts(self) -> list:
|
|
147
|
+
"""This image's rectangular parts, channel groups that share one
|
|
148
|
+
sampling: several for a ragged image, just this image otherwise."""
|
|
149
|
+
return list(self._parts) if self._parts else [self]
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def is_ragged(self) -> bool:
|
|
153
|
+
"""Whether this image's channels do not share one sampling
|
|
154
|
+
(``parts`` holds more than one part)."""
|
|
155
|
+
return len(self.parts) > 1
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def whole(self) -> "Image":
|
|
159
|
+
"""The image this part belongs to: itself for a whole (or an image
|
|
160
|
+
that is not ragged)."""
|
|
161
|
+
return self._whole if self._whole is not None else self
|
|
162
|
+
|
|
111
163
|
@property
|
|
112
164
|
def resolutions(self) -> list:
|
|
113
165
|
"""This image's pyramid, full resolution first: one image per
|
|
@@ -177,12 +229,18 @@ class Image:
|
|
|
177
229
|
|
|
178
230
|
#: the indices that say which image of a file an image is, outermost first.
|
|
179
231
|
#: Arrays have only t c z y x (+ s); every other dimension is an index:
|
|
180
|
-
#:
|
|
181
|
-
#:
|
|
182
|
-
#:
|
|
232
|
+
#: plate, acquisition (imaging run), well and field for plates (docs/HCS.md;
|
|
233
|
+
#: on a plate, well and field take the place of scene), illumination
|
|
234
|
+
#: (light-sheet sides), phase, block (CZI), emission / excitation (spectral
|
|
235
|
+
#: scans; the wavelength in the image's metadata), lifetime (FLIM bins) and
|
|
236
|
+
#: sequence (a dimension the file does not name; open(...,
|
|
183
237
|
#: rename={"sequence": "z"}) makes it an axis)
|
|
184
|
-
INDEX_NAMES = ("
|
|
185
|
-
"
|
|
238
|
+
INDEX_NAMES = ("plate", "acquisition", "well", "field",
|
|
239
|
+
"scene", "tile", "rotation", "view", "illumination", "phase", "block",
|
|
240
|
+
"emission", "excitation", "lifetime", "sequence", "part", "resolution")
|
|
241
|
+
#: the indices of plates (docs/HCS.md), on every image of a plate;
|
|
242
|
+
#: ``plate`` and ``acquisition`` are 0 when a file holds one of each
|
|
243
|
+
PLATE_INDICES = ("plate", "acquisition", "well", "field")
|
|
186
244
|
#: the indices that are not frames of the image but dimensions sampled at the
|
|
187
245
|
#: same points (what other readers keep as array axes)
|
|
188
246
|
SAMPLED_INDICES = ("illumination", "phase", "block", "emission", "excitation", "lifetime",
|
|
@@ -198,13 +256,24 @@ class File:
|
|
|
198
256
|
|
|
199
257
|
backend: str = "base"
|
|
200
258
|
|
|
201
|
-
def __init__(self, path: str, images: Sequence[Image]):
|
|
259
|
+
def __init__(self, path: str, images: Sequence[Image], plates: Sequence = ()):
|
|
202
260
|
self.path = str(path)
|
|
203
261
|
self.images = list(images)
|
|
262
|
+
#: the plates the images belong to (``_plate.Plate``), like ``images``
|
|
263
|
+
#: a list; empty for a file that is not a plate (docs/HCS.md)
|
|
264
|
+
self.plates = list(plates)
|
|
204
265
|
if not getattr(self, "format", None):
|
|
205
266
|
#: the format variant; the backend's name unless it reads several
|
|
206
267
|
self.format = self.backend
|
|
207
268
|
self._index_resolutions()
|
|
269
|
+
self._bind_plates()
|
|
270
|
+
|
|
271
|
+
def _bind_plates(self) -> None:
|
|
272
|
+
"""Fill in each plate's wells -> fields -> images from ``images``
|
|
273
|
+
(again whenever ``images`` is replaced, e.g. by ``open(axes=...)``)."""
|
|
274
|
+
if self.plates:
|
|
275
|
+
from ._plate import navigate
|
|
276
|
+
self.plates = navigate(self.plates, self.images)
|
|
208
277
|
|
|
209
278
|
def _index_resolutions(self) -> None:
|
|
210
279
|
"""``resolution`` is in every index when the file stores a pyramid of
|
|
@@ -235,9 +304,10 @@ class File:
|
|
|
235
304
|
values it takes, e.g. ``{"scene": 2, "tile": 5}``. Not a total and
|
|
236
305
|
not a shape: with 3 tiles in scene 0 and 5 in scene 1, tile indices
|
|
237
306
|
run 0-2 and 0-4, so the range is 5 (not 8, and scene 0 has only 3).
|
|
238
|
-
Tile, rotation and
|
|
239
|
-
``range(n)``; scene indices are the file's own (a CZI's
|
|
240
|
-
numbers), so for scenes it is the number of distinct
|
|
307
|
+
Tile, rotation, view, well and field indices count from 0, so the
|
|
308
|
+
values are ``range(n)``; scene indices are the file's own (a CZI's
|
|
309
|
+
may skip numbers), so for scenes it is the number of distinct
|
|
310
|
+
values. A well index is a place in ``plates[p].wells``."""
|
|
241
311
|
counts = {}
|
|
242
312
|
for name in INDEX_NAMES:
|
|
243
313
|
values = {image.index[name] for image in self.images if name in image.index}
|
|
@@ -263,6 +333,28 @@ class File:
|
|
|
263
333
|
def __exit__(self, *exc) -> None:
|
|
264
334
|
self.close()
|
|
265
335
|
|
|
266
|
-
def
|
|
336
|
+
def outline(self) -> str:
|
|
337
|
+
"""The file's structure (docs/AXES.md): its plates, wells and fields,
|
|
338
|
+
or its scenes, each with its tiles, the orthogonal indices that vary
|
|
339
|
+
in it (``x view 2``), the image shape and the pyramid levels."""
|
|
340
|
+
from ._outline import outline_lines
|
|
341
|
+
return "\n".join([self._header()] + [" " + line for line in outline_lines(self)])
|
|
342
|
+
|
|
343
|
+
def _header(self) -> str:
|
|
344
|
+
plates = f" plates={len(self.plates)}" if self.plates else ""
|
|
267
345
|
return (f"<micro_reader.File {self.path!r} backend={self.backend!r} "
|
|
268
|
-
f"format={self.format!r} images={len(self.images)}>")
|
|
346
|
+
f"format={self.format!r} images={len(self.images)}{plates}>")
|
|
347
|
+
|
|
348
|
+
#: outline lines shown by ``repr``; ``outline()`` shows all
|
|
349
|
+
REPR_LINES = 6
|
|
350
|
+
|
|
351
|
+
def __repr__(self) -> str:
|
|
352
|
+
from ._outline import outline_lines
|
|
353
|
+
try:
|
|
354
|
+
lines = outline_lines(self)
|
|
355
|
+
except Exception: # a repr never fails
|
|
356
|
+
return self._header()
|
|
357
|
+
shown = lines[:self.REPR_LINES]
|
|
358
|
+
if len(lines) > len(shown):
|
|
359
|
+
shown.append(f"... {len(lines) - len(shown)} more lines: .outline()")
|
|
360
|
+
return "\n".join([self._header()] + [" " + line for line in shown])
|
|
@@ -12,3 +12,14 @@ class UnsupportedFormatError(MicroReaderError):
|
|
|
12
12
|
|
|
13
13
|
class CorruptFileError(MicroReaderError):
|
|
14
14
|
"""The file is recognised but inconsistent, e.g. truncated."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MissingImagesWarning(UserWarning):
|
|
18
|
+
"""A file set lists images whose files are missing; they are left out
|
|
19
|
+
(docs/HCS.md)."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CompanionWarning(UserWarning):
|
|
23
|
+
"""An OME companion set's metadata came from somewhere other than the file
|
|
24
|
+
opened, or a member path had to be resolved differently than written
|
|
25
|
+
(docs/COMPANION.md)."""
|