micro-reader 0.3.0__tar.gz → 0.4.0__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.3.0 → micro_reader-0.4.0}/.gitignore +2 -1
- {micro_reader-0.3.0 → micro_reader-0.4.0}/PKG-INFO +69 -40
- {micro_reader-0.3.0 → micro_reader-0.4.0}/README.md +67 -38
- {micro_reader-0.3.0 → micro_reader-0.4.0}/pyproject.toml +2 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_axes.py +21 -8
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_base.py +75 -15
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_groups.py +17 -4
- micro_reader-0.4.0/src/micro_reader/_lambda.py +147 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_memory.py +7 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_metadata.py +30 -3
- micro_reader-0.4.0/src/micro_reader/_naming.py +113 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_outline.py +3 -3
- micro_reader-0.4.0/src/micro_reader/_parts.py +366 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_plate.py +83 -10
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_pool.py +6 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_registry.py +111 -20
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_spatial.py +2 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_stack.py +5 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_version.py +1 -1
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_czi_metadata.py +18 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_hdf5.py +17 -1
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_lif_metadata.py +3 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_nd2_legacy.py +11 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_nd2_metadata.py +45 -11
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_nd2_structure.py +47 -1
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_tiff_metadata.py +110 -8
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/bdv.py +21 -3
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/columbus.py +6 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/companion.py +58 -8
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/cv7000.py +16 -5
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/czi.py +22 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/dv.py +4 -1
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/harmony.py +20 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/hdf5.py +6 -3
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/ims.py +47 -11
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/incell.py +14 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/jdce.py +3 -0
- micro_reader-0.4.0/src/micro_reader/backends/klb.py +175 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/lif.py +67 -2
- micro_reader-0.4.0/src/micro_reader/backends/luxendo.py +718 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/metamorph.py +36 -1
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/metaxpress.py +10 -6
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/micromanager.py +41 -7
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/nd2.py +44 -6
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/oir.py +28 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/raster.py +7 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/tiff.py +84 -2
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/vsi.py +6 -2
- micro_reader-0.3.0/src/micro_reader/_parts.py +0 -188
- {micro_reader-0.3.0 → micro_reader-0.4.0}/LICENSE +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/__init__.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_cfb.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_codecs.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_errors.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_eubi_guard.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_fileset.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_index.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_io.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_jpeg_restarts.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_mosaic.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/_stream.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/__init__.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_ets.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_nd2_run.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_tiff_variants.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/_vsi_tree.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/cellomics.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/flex.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/ics.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/ilastik.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/mrc.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/ndpis.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/oif.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/omp2info.py +0 -0
- {micro_reader-0.3.0 → micro_reader-0.4.0}/src/micro_reader/backends/scanr.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: micro-reader
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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
|
|
@@ -9,7 +9,7 @@ Author-email: Bugra Oezdemir <bugraa.ozdemir@gmail.com>
|
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
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 ::
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Science/Research
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -56,9 +56,9 @@ Description-Content-Type: text/markdown
|
|
|
56
56
|
|
|
57
57
|
**Memory-bounded pixel reader for microscopy images.**
|
|
58
58
|
|
|
59
|
-
`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, OIB / OIF, Olympus VSI, Imaris, DeltaVision, MRC, common raster formats, OME companion file sets, and high-content screening plates (Harmony, Columbus, MetaXpress, ImageXpress HCS.ai, IN Cell, CellVoyager, CQ1, Opera Flex, scanR, Cellomics).
|
|
59
|
+
`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, OIB / OIF, Olympus VSI, Imaris, DeltaVision, MRC, KLB, common raster formats, OME companion file sets, and high-content screening plates (Harmony, Columbus, MetaXpress, ImageXpress HCS.ai, IN Cell, CellVoyager, CQ1, Opera Flex, scanR, Cellomics).
|
|
60
60
|
|
|
61
|
-
> **Status:
|
|
61
|
+
> **Status: alpha (0.x)**
|
|
62
62
|
>
|
|
63
63
|
> The API may still change.
|
|
64
64
|
|
|
@@ -77,14 +77,6 @@ pip install "micro-reader[ims]" # Imaris (.ims)
|
|
|
77
77
|
pip install "micro-reader[codecs]" # additional codecs
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
## Compatibility with eubi-bridge
|
|
81
|
-
|
|
82
|
-
micro-reader 0.1.0 renamed the image index `scene` to `series`. eubi-bridge 0.1.3 to 0.2.x and eubi-bridge-lite below 0.3.0 still read `scene`, so `import micro_reader` refuses to load next to them. It raises an `ImportError` that names the installed eubi version and the two ways out: upgrade eubi (`pip install -U 'eubi-bridge>=0.3.0'`, or the same for `eubi-bridge-lite`), or keep that eubi with the micro-reader it was built for (`pip install 'micro-reader<0.1'`). The check reads the versions of the installed packages; it never imports eubi. An editable install of eubi is checked like any other: its recorded version can be out of date, so the message then says to reinstall it.
|
|
83
|
-
|
|
84
|
-
To skip the check, set the environment variable `MICRO_READER_SKIP_EUBI_CHECK=1`. Only the value `1` skips it; any other value keeps the check. micro-reader then warns once that the check was skipped. This is meant for work on an eubi checkout, not for normal use.
|
|
85
|
-
|
|
86
|
-
The check will be removed when every eubi-bridge / eubi-bridge-lite line below 0.3.0 has left the support window, checked by the release version matrix; not before.
|
|
87
|
-
|
|
88
80
|
## Quick start
|
|
89
81
|
|
|
90
82
|
```python
|
|
@@ -197,6 +189,17 @@ f.select(series=1, view=0)
|
|
|
197
189
|
|
|
198
190
|
This returns the images matching the requested indices. For example, if a file contains several series, tiles, and views, selecting `series=1, view=0` returns the tiles belonging to that series and view.
|
|
199
191
|
|
|
192
|
+
### Image names
|
|
193
|
+
|
|
194
|
+
`image.name` follows one rule for every format, and is the same whichever file of a dataset you open:
|
|
195
|
+
|
|
196
|
+
1. A plate's image is named by its well and field: `"well B2 field 0"` (the field counted from 0, as in `select(field=0)`; the plate's name in front when the file holds several plates).
|
|
197
|
+
2. Otherwise the name the format gives the image: a Leica element's name, an OME `Image Name`, an Imaris image name, a slide's associated image by its role in lower case (`label`, `macro`, `thumbnail`). A slide's main scan, which its format does not name, takes the name of its file.
|
|
198
|
+
3. Otherwise, in a file of several images, a label the file records for each one (a CZI scene name, an ND2 point name, a Micro-Manager or MetaMorph position label), or else the index values that tell them apart: `"series 2"`, `"series 1, tile 3"`.
|
|
199
|
+
4. Otherwise (one image) the name of the dataset's main file without its extension: the index file of a set, the main file of a multi-file format, a Luxendo experiment's folder.
|
|
200
|
+
|
|
201
|
+
Two images left with one name get the index values that differ between them appended, for example `"scan, series 2"`. Resolution levels carry their image's name.
|
|
202
|
+
|
|
200
203
|
### Available index dimensions
|
|
201
204
|
|
|
202
205
|
`micro-reader` uses these index names when a format provides the corresponding dimensions:
|
|
@@ -233,7 +236,7 @@ f.path # the main file: "cells_R3D.dv"
|
|
|
233
236
|
f.opened # what was opened: "cells_R3D.dv.log"
|
|
234
237
|
```
|
|
235
238
|
|
|
236
|
-
You can also open the folder. A folder opens the image it belongs to (an NDTiff dataset folder, a Harmony or CellVoyager measurement folder, a folder of Opera `.flex` files), or the one image it holds. An Opera Flex plate has no index file: any of its `.flex` files opens the whole folder as the plate (`multifile=False` reads only that file). A folder with several images raises an error that lists them.
|
|
239
|
+
You can also open the folder. A folder opens the image it belongs to (an NDTiff dataset folder, a Harmony or CellVoyager measurement folder, a folder of Opera `.flex` files, a Luxendo experiment), or the one image it holds. A folder that holds no image itself, such as a downloaded sample folder with the export one or two levels down, opens the one dataset found in its sub-folders. An Opera Flex plate has no index file: any of its `.flex` files opens the whole folder as the plate (`multifile=False` reads only that file). A folder of one NIS-Elements well-plate run opens as that plate (with `plates=False`, its files are several images). A folder with several images or datasets raises an error that lists them; an OME-Zarr store is refused with a pointer to `ome_zarr_pyramid`, which reads it.
|
|
237
240
|
|
|
238
241
|
To sort many files at once, for example everything under a folder you want to convert, `group_files` says which files make one image:
|
|
239
242
|
|
|
@@ -246,7 +249,7 @@ for group in g.groups:
|
|
|
246
249
|
g.singles # every other file, as given
|
|
247
250
|
```
|
|
248
251
|
|
|
249
|
-
A file that a plate or companion index names is listed only in that index's group. When the main file of a multi-file image is missing, its group has `main=None`.
|
|
252
|
+
A file that a plate or companion index names is listed only in that index's group. When the main file of a multi-file image is missing, its group has `main=None`. It groups as `open()` reads, plates included (`plates=True`, the default): a folder of ND2 files from one NIS-Elements well-plate run is one group; `plates=False` groups as `open(..., plates=False)` reads.
|
|
250
253
|
|
|
251
254
|
## Axes
|
|
252
255
|
|
|
@@ -274,37 +277,47 @@ image.shape
|
|
|
274
277
|
# (1, 3, 40, 5120, 6144)
|
|
275
278
|
```
|
|
276
279
|
|
|
277
|
-
Other dimensions, such as illumination,
|
|
280
|
+
Other dimensions, such as illumination, excitation, tile, etc., are represented as image indices rather than array axes, as discussed above.
|
|
278
281
|
|
|
279
|
-
###
|
|
282
|
+
### Lambda scans
|
|
280
283
|
|
|
281
|
-
|
|
284
|
+
A lambda (spectral) scan is one image in every format. Its bands lie along `c`, a detector's bands next to each other in wavelength order, and each channel has its `emission_wavelength` and its wavelength in its name (`"HyD S 1 420 nm"`).
|
|
282
285
|
|
|
283
286
|
```python
|
|
284
|
-
|
|
287
|
+
f = micro_reader.open("scan.lif")
|
|
288
|
+
f.images[0].axes # 'tczyx'
|
|
289
|
+
[c.emission_wavelength for c in f.images[0].metadata.channels]
|
|
290
|
+
|
|
291
|
+
f = micro_reader.open("scan.lif", axes="native")
|
|
292
|
+
f.images[0].axes # 'tlczyx': a real lambda axis
|
|
293
|
+
f.images[0].metadata.index_values["emission"] # its wavelengths
|
|
285
294
|
```
|
|
286
295
|
|
|
287
|
-
|
|
296
|
+
Asking for axes that name `l`, such as `axes="tlczyx"`, also gives the axis. An LSM stores its bands as channels, so it has no `l` axis; its channels carry their wavelengths. Excitation scans stay an image index.
|
|
297
|
+
|
|
298
|
+
`open` gives every image in this canonical order by default, whatever order the file stores: axes the file lacks are added with size 1 (a 2-D TIFF is `(1, 1, 1, Y, X)`) and the others are reordered. Reading costs the same either way.
|
|
299
|
+
|
|
300
|
+
### Changing axis order
|
|
301
|
+
|
|
302
|
+
To keep the order the file stores (ICS `ctzyx`, Micro-Manager OME-TIFF `tzcyx`, a 2-D TIFF `yx`):
|
|
288
303
|
|
|
289
304
|
```python
|
|
290
|
-
f = micro_reader.open("image.
|
|
305
|
+
f = micro_reader.open("image.ics", axes="native")
|
|
291
306
|
```
|
|
292
307
|
|
|
293
|
-
You can also request
|
|
308
|
+
You can also request a specific axis order:
|
|
294
309
|
|
|
295
310
|
```python
|
|
296
|
-
|
|
311
|
+
image = f.images[0].as_axes("tczyx")
|
|
297
312
|
```
|
|
298
313
|
|
|
299
|
-
|
|
314
|
+
or when opening the file:
|
|
300
315
|
|
|
301
|
-
```
|
|
302
|
-
|
|
316
|
+
```python
|
|
317
|
+
f = micro_reader.open("image.tif", axes="zyx")
|
|
303
318
|
```
|
|
304
319
|
|
|
305
|
-
followed by `s` when RGB samples are
|
|
306
|
-
|
|
307
|
-
Missing axes are added with size 1 and existing axes are reordered.
|
|
320
|
+
`axes="canonical"` (the default) is `t c z y x`, followed by `s` when RGB samples are kept (`samples="keep"`).
|
|
308
321
|
|
|
309
322
|
### Removing singleton axes
|
|
310
323
|
|
|
@@ -358,8 +371,8 @@ print(len(f.images))
|
|
|
358
371
|
print(f.images[0].index)
|
|
359
372
|
# {"series": 0, "sequence": 0}
|
|
360
373
|
|
|
361
|
-
print(f.images[0].axes)
|
|
362
|
-
# yx
|
|
374
|
+
print(f.images[0].axes, f.images[0].shape)
|
|
375
|
+
# tczyx (1, 1, 1, 16, 16) (axes="canonical", the default; axes=None gives yx)
|
|
363
376
|
```
|
|
364
377
|
|
|
365
378
|
If you know that the pages represent `z`, you can tell the reader:
|
|
@@ -371,11 +384,11 @@ f = micro_reader.open(
|
|
|
371
384
|
)
|
|
372
385
|
```
|
|
373
386
|
|
|
374
|
-
The result is a single image with `
|
|
387
|
+
The result is a single image with the 80 pages along `z`.
|
|
375
388
|
|
|
376
389
|
```python
|
|
377
|
-
print(f.images[0].axes)
|
|
378
|
-
# zyx
|
|
390
|
+
print(f.images[0].axes, f.images[0].shape)
|
|
391
|
+
# tczyx (1, 1, 80, 16, 16) (axes=None gives zyx)
|
|
379
392
|
```
|
|
380
393
|
|
|
381
394
|
|
|
@@ -463,6 +476,8 @@ f = micro_reader.open(
|
|
|
463
476
|
|
|
464
477
|
CZI tiles are placed by the file's own stitching information, FluoView tiles by the tiling set's grid (where tiles overlap, the later tile is on top; areas without a tile read as 0). Other formats with tiles raise an error for `tiles="mosaic"`.
|
|
465
478
|
|
|
479
|
+
A format that has tiles it cannot place refuses `tiles="mosaic"` with an error. A file without tiles has nothing to place and opens as usual: Micro-Manager and MetaMorph stage positions are series, not tiles.
|
|
480
|
+
|
|
466
481
|
`tiles="stitched"`, the name before 0.1.0, still works and warns.
|
|
467
482
|
|
|
468
483
|
## Plates
|
|
@@ -503,10 +518,10 @@ is its name (`well="B2"` is `well_name="B2"`).
|
|
|
503
518
|
|
|
504
519
|
`f.outline()` shows the plates, wells and fields of a file.
|
|
505
520
|
|
|
506
|
-
|
|
521
|
+
A plate in a generic format (a CZI, an ND2, an OME-TIFF or a companion with wells) is read as a plate too. `plates=False` reads it by its series instead:
|
|
507
522
|
|
|
508
523
|
```python
|
|
509
|
-
f = micro_reader.open("plate.czi"
|
|
524
|
+
f = micro_reader.open("plate.czi")
|
|
510
525
|
```
|
|
511
526
|
|
|
512
527
|
Missing plane files are read as zeros, with a `MissingImagesWarning`. A field without any of its files is left out.
|
|
@@ -565,6 +580,7 @@ meta.channels[0]
|
|
|
565
580
|
`micro-reader` follows a few important rules:
|
|
566
581
|
|
|
567
582
|
* Units use OME-Zarr-compatible names such as `micrometer`, `nanometer`, and `millisecond`.
|
|
583
|
+
* Each value keeps the unit the file records (lengths above a millimetre become micrometres), so two formats can give the same quantity in different units, for example a time step in milliseconds (ND2, OIR) or in seconds (CZI, LIF). Convert to compare: `image.metadata.pixel_sizes["t"].to("s")`, `.to("um")`. Converting a length to a time, or to an unknown unit, raises `ValueError`.
|
|
568
584
|
* Missing metadata is left as `None` or absent.
|
|
569
585
|
* Pixel sizes are never invented as `1.0`.
|
|
570
586
|
* Colours are not invented when the file does not provide them.
|
|
@@ -576,7 +592,7 @@ meta.channels[0]
|
|
|
576
592
|
|
|
577
593
|
Where possible, format-specific coordinate conventions are converted to this common representation.
|
|
578
594
|
|
|
579
|
-
The position is omitted when it cannot be determined reliably.
|
|
595
|
+
The position is omitted when it cannot be determined reliably. Micro-Manager's separate files and MetaMorph record where the stage was without saying which point of the field that is: their value is in `image.metadata.stage_position`, as recorded, not in `position`.
|
|
580
596
|
|
|
581
597
|
## Supported formats
|
|
582
598
|
|
|
@@ -592,13 +608,14 @@ The position is omitted when it cannot be determined reliably.
|
|
|
592
608
|
| **VSI** | Olympus / Evident cellSens and VS120 / VS200 slide scanners |
|
|
593
609
|
| **NDPIS** | Hamamatsu NanoZoomer fluorescence sets (one NDPI per channel) |
|
|
594
610
|
| **Imaris** | `.ims`, with the `[hdf5]` extra |
|
|
595
|
-
| **HDF5** | ilastik, BigDataViewer / BigStitcher, Luxendo
|
|
611
|
+
| **HDF5** | ilastik, BigDataViewer / BigStitcher, Luxendo experiments and `.lux.h5`, Fiji HDF5, with the `[hdf5]` extra |
|
|
596
612
|
| **ICS / IDS** | Image Cytometry Standard 1.0 and 2.0 (SVI Huygens and others) |
|
|
597
613
|
| **MetaMorph** | `.nd` acquisitions and their TIFF / STK files |
|
|
598
614
|
| **Micro-Manager 1.4**| image-per-file acquisitions (`metadata.txt` + one TIFF per plane) |
|
|
599
615
|
| **DeltaVision** | `.dv`, `.r3d` (softWoRx, OMX, Priism) |
|
|
600
616
|
| **MRC** | Common MRC modes and extended headers |
|
|
601
|
-
| **
|
|
617
|
+
| **KLB** | Every time point and channel, bzip2 and zlib blocks |
|
|
618
|
+
| **Plates (HCS)** | Harmony, Columbus exports, MetaXpress, ImageXpress HCS.ai (`.jdce`), IN Cell, CellVoyager, Opera Flex, scanR and Cellomics (`.C01`) plates; also (by default; `plates=False` reads them by series) CZI plates, OME companion and OME-TIFF plates (Yokogawa CQ1), ND2 files whose XY points are named after wells, and folders of ND2 files from one NIS-Elements well-plate run |
|
|
602
619
|
| **JPEG / PNG / BMP** | Common raster image formats |
|
|
603
620
|
|
|
604
621
|
All formats use the same `File`, `Image`, axes, indexing, metadata, and reading interface wherever possible.
|
|
@@ -662,10 +679,14 @@ f = micro_reader.open("data_t001.ome.tif", multifile=False)
|
|
|
662
679
|
|
|
663
680
|
If the companion is missing or does not list the file, opening the whole set raises an error that names `multifile=False`. With `multifile=False` the file is then read without pixel sizes, with a `CompanionWarning`. A TIFF whose metadata lives in another OME-TIFF (instead of a `.companion.ome`) works the same way, with that OME-TIFF in the companion's place.
|
|
664
681
|
|
|
665
|
-
A companion that describes a plate (`Plate` / `Well` / `WellSample`) reads as one
|
|
682
|
+
A companion that describes a plate (`Plate` / `Well` / `WellSample`) reads as one, as a CZI plate does: `f.plates` lists its plates and wells, and its images are indexed by plate, acquisition, well and field. RGB images in a companion have a sample axis (`t c z y x s`), as RGB OME-TIFFs do.
|
|
666
683
|
|
|
667
684
|
Some OME-TIFF sets keep the full OME-XML in their TIFF files instead of a companion. Opening one of those files also reads the whole set, and `multifile=False` reads only the planes stored in that file, with the set's metadata.
|
|
668
685
|
|
|
686
|
+
Any file of a plate works the same way: opening one plane file opens the plate, and `multifile=False` reads that file alone. Files that belong to the instrument and not to the plate's data, such as CellVoyager shading and dark-current images, open as themselves.
|
|
687
|
+
|
|
688
|
+
Leica's settings files (`FrameProperties`, `IOManagerConfiguation`) open their experiment. A `.xml` beside a `.lif` opens the `.lif`, and so does a `.lifext`; `multifile=False` reads the `.lifext` itself.
|
|
689
|
+
|
|
669
690
|
Micro-Manager stacks always open whole, also with `multifile=False`: they split their files by size, so one file does not hold a complete part of the image.
|
|
670
691
|
|
|
671
692
|
### CZI
|
|
@@ -763,7 +784,7 @@ These need the `[hdf5]` extra as well.
|
|
|
763
784
|
|
|
764
785
|
* **ilastik**: every dataset with ilastik's `axistags` (its exports, its input files, steinbock's `img`) is one image, in the dataset's own axis order. Pixel sizes are read where ilastik recorded units (`axis_units`, since 2025-09).
|
|
765
786
|
* **BigDataViewer / BigStitcher**: the XML with its HDF5 file (also partitioned). One image per tile, angle and illumination, channels on `c` and time points on `t`, with the stored resolution levels. Opening the HDF5 file opens its XML.
|
|
766
|
-
* **Luxendo / Bruker light-sheet
|
|
787
|
+
* **Luxendo / Bruker light-sheet**: an experiment folder (`raw/` with one folder of `.lux.h5` stacks per channel) opens as one image per stack and camera, with all time points and channels. Opening any part opens the whole experiment: the folder, a `.lux.h5`, or the `imaris.ims` / `bdv.xml` next to `raw/`. Files those link files name but `raw/` lacks read as zeros, with a warning. `multifile=False` reads one `.lux.h5` (or `.ims`) alone.
|
|
767
788
|
* **Fiji HDF5** (the Vibez plugin): datasets with `element_size_um`; `t<n>/channel<n>` datasets make one `t c z y x` image.
|
|
768
789
|
|
|
769
790
|
### ICS / IDS
|
|
@@ -929,6 +950,14 @@ Applications work with the same `File` and `Image` model described in [How it wo
|
|
|
929
950
|
|
|
930
951
|
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.
|
|
931
952
|
|
|
953
|
+
## Compatibility with eubi-bridge
|
|
954
|
+
|
|
955
|
+
micro-reader 0.1.0 renamed the image index `scene` to `series`. eubi-bridge 0.1.3 to 0.2.x and eubi-bridge-lite below 0.3.0 still read `scene`, so `import micro_reader` refuses to load next to them. It raises an `ImportError` that names the installed eubi version and the two ways out: upgrade eubi (`pip install -U 'eubi-bridge>=0.3.0'`, or the same for `eubi-bridge-lite`), or keep that eubi with the micro-reader it was built for (`pip install 'micro-reader<0.1'`). The check reads the versions of the installed packages; it never imports eubi. An editable install of eubi is checked like any other: its recorded version can be out of date, so the message then says to reinstall it.
|
|
956
|
+
|
|
957
|
+
To skip the check, set the environment variable `MICRO_READER_SKIP_EUBI_CHECK=1`. Only the value `1` skips it; any other value keeps the check. micro-reader then warns once that the check was skipped. This is meant for work on an eubi checkout, not for normal use.
|
|
958
|
+
|
|
959
|
+
The check will be removed when every eubi-bridge / eubi-bridge-lite line below 0.3.0 has left the support window, checked by the release version matrix; not before.
|
|
960
|
+
|
|
932
961
|
## License
|
|
933
962
|
|
|
934
963
|
MIT
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**Memory-bounded pixel reader for microscopy images.**
|
|
4
4
|
|
|
5
|
-
`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, OIB / OIF, Olympus VSI, Imaris, DeltaVision, MRC, common raster formats, OME companion file sets, and high-content screening plates (Harmony, Columbus, MetaXpress, ImageXpress HCS.ai, IN Cell, CellVoyager, CQ1, Opera Flex, scanR, Cellomics).
|
|
5
|
+
`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, OIB / OIF, Olympus VSI, Imaris, DeltaVision, MRC, KLB, common raster formats, OME companion file sets, and high-content screening plates (Harmony, Columbus, MetaXpress, ImageXpress HCS.ai, IN Cell, CellVoyager, CQ1, Opera Flex, scanR, Cellomics).
|
|
6
6
|
|
|
7
|
-
> **Status:
|
|
7
|
+
> **Status: alpha (0.x)**
|
|
8
8
|
>
|
|
9
9
|
> The API may still change.
|
|
10
10
|
|
|
@@ -23,14 +23,6 @@ pip install "micro-reader[ims]" # Imaris (.ims)
|
|
|
23
23
|
pip install "micro-reader[codecs]" # additional codecs
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
## Compatibility with eubi-bridge
|
|
27
|
-
|
|
28
|
-
micro-reader 0.1.0 renamed the image index `scene` to `series`. eubi-bridge 0.1.3 to 0.2.x and eubi-bridge-lite below 0.3.0 still read `scene`, so `import micro_reader` refuses to load next to them. It raises an `ImportError` that names the installed eubi version and the two ways out: upgrade eubi (`pip install -U 'eubi-bridge>=0.3.0'`, or the same for `eubi-bridge-lite`), or keep that eubi with the micro-reader it was built for (`pip install 'micro-reader<0.1'`). The check reads the versions of the installed packages; it never imports eubi. An editable install of eubi is checked like any other: its recorded version can be out of date, so the message then says to reinstall it.
|
|
29
|
-
|
|
30
|
-
To skip the check, set the environment variable `MICRO_READER_SKIP_EUBI_CHECK=1`. Only the value `1` skips it; any other value keeps the check. micro-reader then warns once that the check was skipped. This is meant for work on an eubi checkout, not for normal use.
|
|
31
|
-
|
|
32
|
-
The check will be removed when every eubi-bridge / eubi-bridge-lite line below 0.3.0 has left the support window, checked by the release version matrix; not before.
|
|
33
|
-
|
|
34
26
|
## Quick start
|
|
35
27
|
|
|
36
28
|
```python
|
|
@@ -143,6 +135,17 @@ f.select(series=1, view=0)
|
|
|
143
135
|
|
|
144
136
|
This returns the images matching the requested indices. For example, if a file contains several series, tiles, and views, selecting `series=1, view=0` returns the tiles belonging to that series and view.
|
|
145
137
|
|
|
138
|
+
### Image names
|
|
139
|
+
|
|
140
|
+
`image.name` follows one rule for every format, and is the same whichever file of a dataset you open:
|
|
141
|
+
|
|
142
|
+
1. A plate's image is named by its well and field: `"well B2 field 0"` (the field counted from 0, as in `select(field=0)`; the plate's name in front when the file holds several plates).
|
|
143
|
+
2. Otherwise the name the format gives the image: a Leica element's name, an OME `Image Name`, an Imaris image name, a slide's associated image by its role in lower case (`label`, `macro`, `thumbnail`). A slide's main scan, which its format does not name, takes the name of its file.
|
|
144
|
+
3. Otherwise, in a file of several images, a label the file records for each one (a CZI scene name, an ND2 point name, a Micro-Manager or MetaMorph position label), or else the index values that tell them apart: `"series 2"`, `"series 1, tile 3"`.
|
|
145
|
+
4. Otherwise (one image) the name of the dataset's main file without its extension: the index file of a set, the main file of a multi-file format, a Luxendo experiment's folder.
|
|
146
|
+
|
|
147
|
+
Two images left with one name get the index values that differ between them appended, for example `"scan, series 2"`. Resolution levels carry their image's name.
|
|
148
|
+
|
|
146
149
|
### Available index dimensions
|
|
147
150
|
|
|
148
151
|
`micro-reader` uses these index names when a format provides the corresponding dimensions:
|
|
@@ -179,7 +182,7 @@ f.path # the main file: "cells_R3D.dv"
|
|
|
179
182
|
f.opened # what was opened: "cells_R3D.dv.log"
|
|
180
183
|
```
|
|
181
184
|
|
|
182
|
-
You can also open the folder. A folder opens the image it belongs to (an NDTiff dataset folder, a Harmony or CellVoyager measurement folder, a folder of Opera `.flex` files), or the one image it holds. An Opera Flex plate has no index file: any of its `.flex` files opens the whole folder as the plate (`multifile=False` reads only that file). A folder with several images raises an error that lists them.
|
|
185
|
+
You can also open the folder. A folder opens the image it belongs to (an NDTiff dataset folder, a Harmony or CellVoyager measurement folder, a folder of Opera `.flex` files, a Luxendo experiment), or the one image it holds. A folder that holds no image itself, such as a downloaded sample folder with the export one or two levels down, opens the one dataset found in its sub-folders. An Opera Flex plate has no index file: any of its `.flex` files opens the whole folder as the plate (`multifile=False` reads only that file). A folder of one NIS-Elements well-plate run opens as that plate (with `plates=False`, its files are several images). A folder with several images or datasets raises an error that lists them; an OME-Zarr store is refused with a pointer to `ome_zarr_pyramid`, which reads it.
|
|
183
186
|
|
|
184
187
|
To sort many files at once, for example everything under a folder you want to convert, `group_files` says which files make one image:
|
|
185
188
|
|
|
@@ -192,7 +195,7 @@ for group in g.groups:
|
|
|
192
195
|
g.singles # every other file, as given
|
|
193
196
|
```
|
|
194
197
|
|
|
195
|
-
A file that a plate or companion index names is listed only in that index's group. When the main file of a multi-file image is missing, its group has `main=None`.
|
|
198
|
+
A file that a plate or companion index names is listed only in that index's group. When the main file of a multi-file image is missing, its group has `main=None`. It groups as `open()` reads, plates included (`plates=True`, the default): a folder of ND2 files from one NIS-Elements well-plate run is one group; `plates=False` groups as `open(..., plates=False)` reads.
|
|
196
199
|
|
|
197
200
|
## Axes
|
|
198
201
|
|
|
@@ -220,37 +223,47 @@ image.shape
|
|
|
220
223
|
# (1, 3, 40, 5120, 6144)
|
|
221
224
|
```
|
|
222
225
|
|
|
223
|
-
Other dimensions, such as illumination,
|
|
226
|
+
Other dimensions, such as illumination, excitation, tile, etc., are represented as image indices rather than array axes, as discussed above.
|
|
224
227
|
|
|
225
|
-
###
|
|
228
|
+
### Lambda scans
|
|
226
229
|
|
|
227
|
-
|
|
230
|
+
A lambda (spectral) scan is one image in every format. Its bands lie along `c`, a detector's bands next to each other in wavelength order, and each channel has its `emission_wavelength` and its wavelength in its name (`"HyD S 1 420 nm"`).
|
|
228
231
|
|
|
229
232
|
```python
|
|
230
|
-
|
|
233
|
+
f = micro_reader.open("scan.lif")
|
|
234
|
+
f.images[0].axes # 'tczyx'
|
|
235
|
+
[c.emission_wavelength for c in f.images[0].metadata.channels]
|
|
236
|
+
|
|
237
|
+
f = micro_reader.open("scan.lif", axes="native")
|
|
238
|
+
f.images[0].axes # 'tlczyx': a real lambda axis
|
|
239
|
+
f.images[0].metadata.index_values["emission"] # its wavelengths
|
|
231
240
|
```
|
|
232
241
|
|
|
233
|
-
|
|
242
|
+
Asking for axes that name `l`, such as `axes="tlczyx"`, also gives the axis. An LSM stores its bands as channels, so it has no `l` axis; its channels carry their wavelengths. Excitation scans stay an image index.
|
|
243
|
+
|
|
244
|
+
`open` gives every image in this canonical order by default, whatever order the file stores: axes the file lacks are added with size 1 (a 2-D TIFF is `(1, 1, 1, Y, X)`) and the others are reordered. Reading costs the same either way.
|
|
245
|
+
|
|
246
|
+
### Changing axis order
|
|
247
|
+
|
|
248
|
+
To keep the order the file stores (ICS `ctzyx`, Micro-Manager OME-TIFF `tzcyx`, a 2-D TIFF `yx`):
|
|
234
249
|
|
|
235
250
|
```python
|
|
236
|
-
f = micro_reader.open("image.
|
|
251
|
+
f = micro_reader.open("image.ics", axes="native")
|
|
237
252
|
```
|
|
238
253
|
|
|
239
|
-
You can also request
|
|
254
|
+
You can also request a specific axis order:
|
|
240
255
|
|
|
241
256
|
```python
|
|
242
|
-
|
|
257
|
+
image = f.images[0].as_axes("tczyx")
|
|
243
258
|
```
|
|
244
259
|
|
|
245
|
-
|
|
260
|
+
or when opening the file:
|
|
246
261
|
|
|
247
|
-
```
|
|
248
|
-
|
|
262
|
+
```python
|
|
263
|
+
f = micro_reader.open("image.tif", axes="zyx")
|
|
249
264
|
```
|
|
250
265
|
|
|
251
|
-
followed by `s` when RGB samples are
|
|
252
|
-
|
|
253
|
-
Missing axes are added with size 1 and existing axes are reordered.
|
|
266
|
+
`axes="canonical"` (the default) is `t c z y x`, followed by `s` when RGB samples are kept (`samples="keep"`).
|
|
254
267
|
|
|
255
268
|
### Removing singleton axes
|
|
256
269
|
|
|
@@ -304,8 +317,8 @@ print(len(f.images))
|
|
|
304
317
|
print(f.images[0].index)
|
|
305
318
|
# {"series": 0, "sequence": 0}
|
|
306
319
|
|
|
307
|
-
print(f.images[0].axes)
|
|
308
|
-
# yx
|
|
320
|
+
print(f.images[0].axes, f.images[0].shape)
|
|
321
|
+
# tczyx (1, 1, 1, 16, 16) (axes="canonical", the default; axes=None gives yx)
|
|
309
322
|
```
|
|
310
323
|
|
|
311
324
|
If you know that the pages represent `z`, you can tell the reader:
|
|
@@ -317,11 +330,11 @@ f = micro_reader.open(
|
|
|
317
330
|
)
|
|
318
331
|
```
|
|
319
332
|
|
|
320
|
-
The result is a single image with `
|
|
333
|
+
The result is a single image with the 80 pages along `z`.
|
|
321
334
|
|
|
322
335
|
```python
|
|
323
|
-
print(f.images[0].axes)
|
|
324
|
-
# zyx
|
|
336
|
+
print(f.images[0].axes, f.images[0].shape)
|
|
337
|
+
# tczyx (1, 1, 80, 16, 16) (axes=None gives zyx)
|
|
325
338
|
```
|
|
326
339
|
|
|
327
340
|
|
|
@@ -409,6 +422,8 @@ f = micro_reader.open(
|
|
|
409
422
|
|
|
410
423
|
CZI tiles are placed by the file's own stitching information, FluoView tiles by the tiling set's grid (where tiles overlap, the later tile is on top; areas without a tile read as 0). Other formats with tiles raise an error for `tiles="mosaic"`.
|
|
411
424
|
|
|
425
|
+
A format that has tiles it cannot place refuses `tiles="mosaic"` with an error. A file without tiles has nothing to place and opens as usual: Micro-Manager and MetaMorph stage positions are series, not tiles.
|
|
426
|
+
|
|
412
427
|
`tiles="stitched"`, the name before 0.1.0, still works and warns.
|
|
413
428
|
|
|
414
429
|
## Plates
|
|
@@ -449,10 +464,10 @@ is its name (`well="B2"` is `well_name="B2"`).
|
|
|
449
464
|
|
|
450
465
|
`f.outline()` shows the plates, wells and fields of a file.
|
|
451
466
|
|
|
452
|
-
|
|
467
|
+
A plate in a generic format (a CZI, an ND2, an OME-TIFF or a companion with wells) is read as a plate too. `plates=False` reads it by its series instead:
|
|
453
468
|
|
|
454
469
|
```python
|
|
455
|
-
f = micro_reader.open("plate.czi"
|
|
470
|
+
f = micro_reader.open("plate.czi")
|
|
456
471
|
```
|
|
457
472
|
|
|
458
473
|
Missing plane files are read as zeros, with a `MissingImagesWarning`. A field without any of its files is left out.
|
|
@@ -511,6 +526,7 @@ meta.channels[0]
|
|
|
511
526
|
`micro-reader` follows a few important rules:
|
|
512
527
|
|
|
513
528
|
* Units use OME-Zarr-compatible names such as `micrometer`, `nanometer`, and `millisecond`.
|
|
529
|
+
* Each value keeps the unit the file records (lengths above a millimetre become micrometres), so two formats can give the same quantity in different units, for example a time step in milliseconds (ND2, OIR) or in seconds (CZI, LIF). Convert to compare: `image.metadata.pixel_sizes["t"].to("s")`, `.to("um")`. Converting a length to a time, or to an unknown unit, raises `ValueError`.
|
|
514
530
|
* Missing metadata is left as `None` or absent.
|
|
515
531
|
* Pixel sizes are never invented as `1.0`.
|
|
516
532
|
* Colours are not invented when the file does not provide them.
|
|
@@ -522,7 +538,7 @@ meta.channels[0]
|
|
|
522
538
|
|
|
523
539
|
Where possible, format-specific coordinate conventions are converted to this common representation.
|
|
524
540
|
|
|
525
|
-
The position is omitted when it cannot be determined reliably.
|
|
541
|
+
The position is omitted when it cannot be determined reliably. Micro-Manager's separate files and MetaMorph record where the stage was without saying which point of the field that is: their value is in `image.metadata.stage_position`, as recorded, not in `position`.
|
|
526
542
|
|
|
527
543
|
## Supported formats
|
|
528
544
|
|
|
@@ -538,13 +554,14 @@ The position is omitted when it cannot be determined reliably.
|
|
|
538
554
|
| **VSI** | Olympus / Evident cellSens and VS120 / VS200 slide scanners |
|
|
539
555
|
| **NDPIS** | Hamamatsu NanoZoomer fluorescence sets (one NDPI per channel) |
|
|
540
556
|
| **Imaris** | `.ims`, with the `[hdf5]` extra |
|
|
541
|
-
| **HDF5** | ilastik, BigDataViewer / BigStitcher, Luxendo
|
|
557
|
+
| **HDF5** | ilastik, BigDataViewer / BigStitcher, Luxendo experiments and `.lux.h5`, Fiji HDF5, with the `[hdf5]` extra |
|
|
542
558
|
| **ICS / IDS** | Image Cytometry Standard 1.0 and 2.0 (SVI Huygens and others) |
|
|
543
559
|
| **MetaMorph** | `.nd` acquisitions and their TIFF / STK files |
|
|
544
560
|
| **Micro-Manager 1.4**| image-per-file acquisitions (`metadata.txt` + one TIFF per plane) |
|
|
545
561
|
| **DeltaVision** | `.dv`, `.r3d` (softWoRx, OMX, Priism) |
|
|
546
562
|
| **MRC** | Common MRC modes and extended headers |
|
|
547
|
-
| **
|
|
563
|
+
| **KLB** | Every time point and channel, bzip2 and zlib blocks |
|
|
564
|
+
| **Plates (HCS)** | Harmony, Columbus exports, MetaXpress, ImageXpress HCS.ai (`.jdce`), IN Cell, CellVoyager, Opera Flex, scanR and Cellomics (`.C01`) plates; also (by default; `plates=False` reads them by series) CZI plates, OME companion and OME-TIFF plates (Yokogawa CQ1), ND2 files whose XY points are named after wells, and folders of ND2 files from one NIS-Elements well-plate run |
|
|
548
565
|
| **JPEG / PNG / BMP** | Common raster image formats |
|
|
549
566
|
|
|
550
567
|
All formats use the same `File`, `Image`, axes, indexing, metadata, and reading interface wherever possible.
|
|
@@ -608,10 +625,14 @@ f = micro_reader.open("data_t001.ome.tif", multifile=False)
|
|
|
608
625
|
|
|
609
626
|
If the companion is missing or does not list the file, opening the whole set raises an error that names `multifile=False`. With `multifile=False` the file is then read without pixel sizes, with a `CompanionWarning`. A TIFF whose metadata lives in another OME-TIFF (instead of a `.companion.ome`) works the same way, with that OME-TIFF in the companion's place.
|
|
610
627
|
|
|
611
|
-
A companion that describes a plate (`Plate` / `Well` / `WellSample`) reads as one
|
|
628
|
+
A companion that describes a plate (`Plate` / `Well` / `WellSample`) reads as one, as a CZI plate does: `f.plates` lists its plates and wells, and its images are indexed by plate, acquisition, well and field. RGB images in a companion have a sample axis (`t c z y x s`), as RGB OME-TIFFs do.
|
|
612
629
|
|
|
613
630
|
Some OME-TIFF sets keep the full OME-XML in their TIFF files instead of a companion. Opening one of those files also reads the whole set, and `multifile=False` reads only the planes stored in that file, with the set's metadata.
|
|
614
631
|
|
|
632
|
+
Any file of a plate works the same way: opening one plane file opens the plate, and `multifile=False` reads that file alone. Files that belong to the instrument and not to the plate's data, such as CellVoyager shading and dark-current images, open as themselves.
|
|
633
|
+
|
|
634
|
+
Leica's settings files (`FrameProperties`, `IOManagerConfiguation`) open their experiment. A `.xml` beside a `.lif` opens the `.lif`, and so does a `.lifext`; `multifile=False` reads the `.lifext` itself.
|
|
635
|
+
|
|
615
636
|
Micro-Manager stacks always open whole, also with `multifile=False`: they split their files by size, so one file does not hold a complete part of the image.
|
|
616
637
|
|
|
617
638
|
### CZI
|
|
@@ -709,7 +730,7 @@ These need the `[hdf5]` extra as well.
|
|
|
709
730
|
|
|
710
731
|
* **ilastik**: every dataset with ilastik's `axistags` (its exports, its input files, steinbock's `img`) is one image, in the dataset's own axis order. Pixel sizes are read where ilastik recorded units (`axis_units`, since 2025-09).
|
|
711
732
|
* **BigDataViewer / BigStitcher**: the XML with its HDF5 file (also partitioned). One image per tile, angle and illumination, channels on `c` and time points on `t`, with the stored resolution levels. Opening the HDF5 file opens its XML.
|
|
712
|
-
* **Luxendo / Bruker light-sheet
|
|
733
|
+
* **Luxendo / Bruker light-sheet**: an experiment folder (`raw/` with one folder of `.lux.h5` stacks per channel) opens as one image per stack and camera, with all time points and channels. Opening any part opens the whole experiment: the folder, a `.lux.h5`, or the `imaris.ims` / `bdv.xml` next to `raw/`. Files those link files name but `raw/` lacks read as zeros, with a warning. `multifile=False` reads one `.lux.h5` (or `.ims`) alone.
|
|
713
734
|
* **Fiji HDF5** (the Vibez plugin): datasets with `element_size_um`; `t<n>/channel<n>` datasets make one `t c z y x` image.
|
|
714
735
|
|
|
715
736
|
### ICS / IDS
|
|
@@ -875,6 +896,14 @@ Applications work with the same `File` and `Image` model described in [How it wo
|
|
|
875
896
|
|
|
876
897
|
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.
|
|
877
898
|
|
|
899
|
+
## Compatibility with eubi-bridge
|
|
900
|
+
|
|
901
|
+
micro-reader 0.1.0 renamed the image index `scene` to `series`. eubi-bridge 0.1.3 to 0.2.x and eubi-bridge-lite below 0.3.0 still read `scene`, so `import micro_reader` refuses to load next to them. It raises an `ImportError` that names the installed eubi version and the two ways out: upgrade eubi (`pip install -U 'eubi-bridge>=0.3.0'`, or the same for `eubi-bridge-lite`), or keep that eubi with the micro-reader it was built for (`pip install 'micro-reader<0.1'`). The check reads the versions of the installed packages; it never imports eubi. An editable install of eubi is checked like any other: its recorded version can be out of date, so the message then says to reinstall it.
|
|
902
|
+
|
|
903
|
+
To skip the check, set the environment variable `MICRO_READER_SKIP_EUBI_CHECK=1`. Only the value `1` skips it; any other value keeps the check. micro-reader then warns once that the check was skipped. This is meant for work on an eubi checkout, not for normal use.
|
|
904
|
+
|
|
905
|
+
The check will be removed when every eubi-bridge / eubi-bridge-lite line below 0.3.0 has left the support window, checked by the release version matrix; not before.
|
|
906
|
+
|
|
878
907
|
## License
|
|
879
908
|
|
|
880
909
|
MIT
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "micro-reader"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.4.0"
|
|
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"
|
|
@@ -16,7 +16,7 @@ authors = [{ name = "Bugra Oezdemir", email = "bugraa.ozdemir@gmail.com" }]
|
|
|
16
16
|
keywords = ["microscopy", "bioimage", "image-io", "memory-bounded", "tiff", "ome-tiff", "czi", "nd2",
|
|
17
17
|
"lif", "leica", "oir", "imaris", "ims", "mrc", "lsm", "eer", "cryo-em"]
|
|
18
18
|
classifiers = [
|
|
19
|
-
"Development Status ::
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
20
|
"Intended Audience :: Science/Research",
|
|
21
21
|
"Programming Language :: Python :: 3",
|
|
22
22
|
"Programming Language :: Python :: 3.11",
|
|
@@ -7,8 +7,9 @@ axes an image has: formats with an order of their own (TIFF, MRC) in that
|
|
|
7
7
|
order, the others in ``tczyx``. An axes image rearranges them without
|
|
8
8
|
guessing:
|
|
9
9
|
|
|
10
|
-
- ``axes=None`` keeps the image's order
|
|
11
|
-
|
|
10
|
+
- ``axes=None`` keeps the image's order (``open``'s ``"native"``);
|
|
11
|
+
``"canonical"`` gives ``t c z y x``, then ``s`` (``open``'s default since
|
|
12
|
+
0.4.0, decision MR-4); any other string is the exact axes wanted;
|
|
12
13
|
- axes the file lacks are inserted with size 1;
|
|
13
14
|
- RGB samples ``s`` stay a trailing ``s`` when asked for, or are folded into
|
|
14
15
|
the channels with ``samples="channels"`` (channel ``k`` = sample ``k % S``
|
|
@@ -50,8 +51,9 @@ def folded_channels(channels, n: int, samples: int) -> tuple:
|
|
|
50
51
|
(Bio-Formats-style: one channel per sample) is kept; otherwise each
|
|
51
52
|
channel becomes "<name> R" / "G" / "B" ("A"), tinted from ``SAMPLE_COLORS``,
|
|
52
53
|
keeping its wavelengths; an unnamed one among several is "Channel {c} R";
|
|
53
|
-
a single unnamed channel gives unnamed, tinted channels;
|
|
54
|
-
|
|
54
|
+
a single unnamed channel gives unnamed, tinted channels; two channels of
|
|
55
|
+
one name are "<name> {c} R"; sample counts without labels are numbered
|
|
56
|
+
("<name> 0") and untinted."""
|
|
55
57
|
from ._metadata import Channel
|
|
56
58
|
channels = tuple(channels)
|
|
57
59
|
if len(channels) == n * samples:
|
|
@@ -61,10 +63,13 @@ def folded_channels(channels, n: int, samples: int) -> tuple:
|
|
|
61
63
|
labels = SAMPLE_LABELS.get(samples, tuple(str(i) for i in range(samples)))
|
|
62
64
|
colors = SAMPLE_COLORS.get(samples) or (None,) * samples
|
|
63
65
|
single_unnamed = n == 1 and not channels[0].name
|
|
66
|
+
names = [ch.name for ch in channels]
|
|
64
67
|
out = []
|
|
65
68
|
for c, ch in enumerate(channels):
|
|
69
|
+
# two channels of one name (an RGB ND2): told apart by their position
|
|
70
|
+
label = ch.name if names.count(ch.name) == 1 else f"{ch.name} {c}"
|
|
66
71
|
for i in range(samples):
|
|
67
|
-
name = None if single_unnamed else f"{ch.name
|
|
72
|
+
name = None if single_unnamed else f"{label if ch.name else f'Channel {c}'} {labels[i]}"
|
|
68
73
|
out.append(Channel(name=name, color=colors[i],
|
|
69
74
|
emission_wavelength=ch.emission_wavelength,
|
|
70
75
|
excitation_wavelength=ch.excitation_wavelength))
|
|
@@ -96,6 +101,8 @@ class AxesImage(Image):
|
|
|
96
101
|
if left:
|
|
97
102
|
hint = ("; samples='channels' folds RGB samples into the channels"
|
|
98
103
|
if "s" in left else "")
|
|
104
|
+
if "l" in left:
|
|
105
|
+
hint += "; opened without axes='native', a lambda scan has its bands along c"
|
|
99
106
|
raise ValueError(f"{source.name!r} has axes {''.join(left)!r} that {axes!r} cannot "
|
|
100
107
|
f"hold{hint}")
|
|
101
108
|
if self._fold and "c" not in axes:
|
|
@@ -130,7 +137,7 @@ class AxesImage(Image):
|
|
|
130
137
|
return STANDARD + ("s" if "s" in free and not fold else "")
|
|
131
138
|
if len(set(axes)) != len(axes) or not axes.isalpha():
|
|
132
139
|
raise ValueError(f"axes {axes!r}: one letter per axis, each once")
|
|
133
|
-
stray = sorted(set(axes) - set(CANONICAL))
|
|
140
|
+
stray = sorted(set(axes) - set(CANONICAL) - set(names))
|
|
134
141
|
if stray:
|
|
135
142
|
raise ValueError(f"axes {axes!r}: {stray} are not array axes (arrays have "
|
|
136
143
|
f"{CANONICAL!r}; other dimensions are image indices)")
|
|
@@ -201,11 +208,17 @@ class AxesImage(Image):
|
|
|
201
208
|
@property
|
|
202
209
|
def metadata(self):
|
|
203
210
|
"""The native image's metadata; with samples folded into the channels,
|
|
204
|
-
one channel per folded channel (``folded_channels``).
|
|
211
|
+
one channel per folded channel (``folded_channels``). The sizes of axes this view
|
|
212
|
+
does not have are left out (``squeeze`` dropped them: a ``yx`` view listed the
|
|
213
|
+
file's z size; Bugra, 2026-11-04); an axis it has keeps what the file gives,
|
|
214
|
+
a size-1 one too, and none is made up for an axis the view adds."""
|
|
215
|
+
from dataclasses import replace
|
|
205
216
|
meta = self.source.metadata
|
|
217
|
+
sizes = dict(meta.pixel_sizes or {})
|
|
218
|
+
if any(axis not in self.axes for axis in sizes):
|
|
219
|
+
meta = replace(meta, pixel_sizes={a: q for a, q in sizes.items() if a in self.axes})
|
|
206
220
|
if not self._fold:
|
|
207
221
|
return meta
|
|
208
|
-
from dataclasses import replace
|
|
209
222
|
n = self.source.shape[self._names.index("c")] if "c" in self._names else 1
|
|
210
223
|
return replace(meta, channels=folded_channels(meta.channels, n, self._samples))
|
|
211
224
|
|