micro-reader 0.2.1__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.
Files changed (76) hide show
  1. {micro_reader-0.2.1 → micro_reader-0.4.0}/.gitignore +2 -1
  2. {micro_reader-0.2.1 → micro_reader-0.4.0}/PKG-INFO +92 -44
  3. {micro_reader-0.2.1 → micro_reader-0.4.0}/README.md +90 -42
  4. {micro_reader-0.2.1 → micro_reader-0.4.0}/pyproject.toml +2 -2
  5. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/__init__.py +2 -2
  6. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_axes.py +69 -6
  7. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_base.py +131 -21
  8. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_errors.py +6 -0
  9. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_fileset.py +77 -42
  10. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_groups.py +28 -8
  11. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_io.py +4 -2
  12. micro_reader-0.4.0/src/micro_reader/_lambda.py +147 -0
  13. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_memory.py +7 -2
  14. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_metadata.py +30 -3
  15. micro_reader-0.4.0/src/micro_reader/_naming.py +113 -0
  16. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_outline.py +4 -4
  17. micro_reader-0.4.0/src/micro_reader/_parts.py +366 -0
  18. micro_reader-0.4.0/src/micro_reader/_plate.py +558 -0
  19. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_pool.py +6 -2
  20. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_registry.py +123 -21
  21. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_spatial.py +2 -2
  22. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_stack.py +5 -0
  23. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_version.py +1 -1
  24. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_czi_metadata.py +18 -0
  25. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_ets.py +8 -1
  26. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_hdf5.py +17 -1
  27. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_lif_metadata.py +3 -2
  28. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_nd2_legacy.py +11 -2
  29. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_nd2_metadata.py +45 -11
  30. micro_reader-0.4.0/src/micro_reader/backends/_nd2_run.py +266 -0
  31. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_nd2_structure.py +47 -1
  32. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_tiff_metadata.py +134 -17
  33. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/bdv.py +21 -3
  34. micro_reader-0.4.0/src/micro_reader/backends/cellomics.py +228 -0
  35. micro_reader-0.4.0/src/micro_reader/backends/columbus.py +113 -0
  36. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/companion.py +223 -37
  37. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/cv7000.py +30 -10
  38. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/czi.py +23 -3
  39. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/dv.py +4 -1
  40. micro_reader-0.4.0/src/micro_reader/backends/flex.py +351 -0
  41. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/harmony.py +51 -8
  42. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/hdf5.py +6 -3
  43. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/ims.py +47 -11
  44. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/incell.py +15 -1
  45. micro_reader-0.4.0/src/micro_reader/backends/jdce.py +253 -0
  46. micro_reader-0.4.0/src/micro_reader/backends/klb.py +175 -0
  47. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/lif.py +67 -2
  48. micro_reader-0.4.0/src/micro_reader/backends/luxendo.py +718 -0
  49. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/metamorph.py +36 -1
  50. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/metaxpress.py +42 -19
  51. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/micromanager.py +41 -7
  52. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/nd2.py +118 -11
  53. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/oir.py +28 -2
  54. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/raster.py +7 -0
  55. micro_reader-0.4.0/src/micro_reader/backends/scanr.py +491 -0
  56. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/tiff.py +127 -9
  57. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/vsi.py +9 -4
  58. micro_reader-0.2.1/src/micro_reader/_parts.py +0 -188
  59. micro_reader-0.2.1/src/micro_reader/_plate.py +0 -195
  60. {micro_reader-0.2.1 → micro_reader-0.4.0}/LICENSE +0 -0
  61. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_cfb.py +0 -0
  62. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_codecs.py +0 -0
  63. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_eubi_guard.py +0 -0
  64. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_index.py +0 -0
  65. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_jpeg_restarts.py +0 -0
  66. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_mosaic.py +0 -0
  67. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/_stream.py +0 -0
  68. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/__init__.py +0 -0
  69. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_tiff_variants.py +0 -0
  70. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/_vsi_tree.py +0 -0
  71. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/ics.py +0 -0
  72. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/ilastik.py +0 -0
  73. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/mrc.py +0 -0
  74. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/ndpis.py +0 -0
  75. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/oif.py +0 -0
  76. {micro_reader-0.2.1 → micro_reader-0.4.0}/src/micro_reader/backends/omp2info.py +0 -0
@@ -20,5 +20,6 @@ Thumbs.db
20
20
  # irrelevant
21
21
  smoke_tests/
22
22
  harness/
23
- docs/
23
+ docs_private/
24
+ site/
24
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: micro-reader
3
- Version: 0.2.1
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 :: 2 - Pre-Alpha
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, MetaXpress, IN Cell, CellVoyager).
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: pre-alpha (0.0.x)**
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), or the one image it holds. 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, emission, tile, etc., are represented as image indices rather than array axes, as discussed above.
280
+ Other dimensions, such as illumination, excitation, tile, etc., are represented as image indices rather than array axes, as discussed above.
278
281
 
279
- ### Changing axis order
282
+ ### Lambda scans
280
283
 
281
- You can request a specific axis order:
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
- image = f.images[0].as_axes("tczyx")
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
- or when opening the file:
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.tif", axes="tczyx")
305
+ f = micro_reader.open("image.ics", axes="native")
291
306
  ```
292
307
 
293
- You can also request the canonical order:
308
+ You can also request a specific axis order:
294
309
 
295
310
  ```python
296
- f = micro_reader.open("image.tif", axes="canonical")
311
+ image = f.images[0].as_axes("tczyx")
297
312
  ```
298
313
 
299
- Canonical order is:
314
+ or when opening the file:
300
315
 
301
- ```text
302
- t c z y x
316
+ ```python
317
+ f = micro_reader.open("image.tif", axes="zyx")
303
318
  ```
304
319
 
305
- followed by `s` when RGB samples are present.
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
 
@@ -324,18 +337,18 @@ Only axes with size 1 can be removed. `y` and `x` are never removed.
324
337
 
325
338
  ### RGB samples
326
339
 
327
- By default, RGB samples are represented by the `s` axis.
340
+ `micro_reader.open` turns RGB samples into channels: an RGB image has 3 channels, named "<name> R", "G" and "B" and coloured red, green and blue (a file without a channel name gives three unnamed channels in those colours). An image that has channels already gets c × s of them: "<name> R" ... for each.
328
341
 
329
- You can instead represent RGB as channels:
342
+ To keep RGB samples as their own `s` axis, the way the file stores them:
330
343
 
331
344
  ```python
332
345
  f = micro_reader.open(
333
346
  "image.tif",
334
- samples="channels",
347
+ samples="keep",
335
348
  )
336
349
  ```
337
350
 
338
- If there are existing channels already, RGB samples will fold with them, leading to a total of c × s channels.
351
+ `image.source` is then not needed: with the default, it is the image as stored, with its `s` axis. `image.as_axes(...)` keeps `s` unless asked for `samples="channels"`.
339
352
 
340
353
 
341
354
  ## Ambiguous dimensions
@@ -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 `zyx` axes.
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
@@ -475,21 +490,38 @@ The plate layout is available through:
475
490
  plate = f.plates[0]
476
491
  plate.wells[0].name
477
492
  # "B2"
493
+ plate.row_names, plate.column_names # ('A', 'B', ...), ('1', '2', ...)
494
+ part = plate.rows[2:5].columns[3:7] # a part of the plate: still a plate
478
495
  ```
479
496
 
497
+ Every level knows what it is and what holds it: `.kind` is `"file"`, `"plate"`, `"well"`
498
+ or `"field"`, and `.parent` is the level above. Accessors go down only: a file holds
499
+ `.plates`, a plate `.wells`, a well `.fields`, a field `.images`. `plate.rows` and
500
+ `plate.columns` slice a plate by position, name or a list of them, as `f.select(row=...,
501
+ column=...)` does; rows and columns are a well's address, not a level.
502
+
480
503
  Images can be reached through the wells, or selected by well:
481
504
 
482
505
  ```python
483
506
  image = plate.wells[0].fields[0].image
484
- f.select(well=plate.well_index("B2"))
507
+ f.select(well=plate.well_index("B2")) # by its place in plate.wells
508
+ f.select(well_name="B2") # by name
509
+ f.select(row_name="B", column=range(1, 4)) # rows and columns: names, or 0-based numbers
510
+ f.select(well=["B2", "C5"], field=[0, 1]) # a list, tuple, range or set: any of them
511
+ f.select(row=slice(2, None)) # a slice of positions: row C onward
485
512
  ```
486
513
 
514
+ A plate's places have a position and a name, a keyword each: `plate` / `plate_name`,
515
+ `acquisition` / `acquisition_name`, `well` / `well_name`, `row` / `row_name`, `column` /
516
+ `column_name` (the columns of ome-zarr-pyramid's table). A name given to a position keyword
517
+ is its name (`well="B2"` is `well_name="B2"`).
518
+
487
519
  `f.outline()` shows the plates, wells and fields of a file.
488
520
 
489
- Zeiss CZI plates are read as plates with `plates=True`, and as series otherwise:
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:
490
522
 
491
523
  ```python
492
- f = micro_reader.open("plate.czi", plates=True)
524
+ f = micro_reader.open("plate.czi")
493
525
  ```
494
526
 
495
527
  Missing plane files are read as zeros, with a `MissingImagesWarning`. A field without any of its files is left out.
@@ -548,6 +580,7 @@ meta.channels[0]
548
580
  `micro-reader` follows a few important rules:
549
581
 
550
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`.
551
584
  * Missing metadata is left as `None` or absent.
552
585
  * Pixel sizes are never invented as `1.0`.
553
586
  * Colours are not invented when the file does not provide them.
@@ -559,7 +592,7 @@ meta.channels[0]
559
592
 
560
593
  Where possible, format-specific coordinate conventions are converted to this common representation.
561
594
 
562
- 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`.
563
596
 
564
597
  ## Supported formats
565
598
 
@@ -575,13 +608,14 @@ The position is omitted when it cannot be determined reliably.
575
608
  | **VSI** | Olympus / Evident cellSens and VS120 / VS200 slide scanners |
576
609
  | **NDPIS** | Hamamatsu NanoZoomer fluorescence sets (one NDPI per channel) |
577
610
  | **Imaris** | `.ims`, with the `[hdf5]` extra |
578
- | **HDF5** | ilastik, BigDataViewer / BigStitcher, Luxendo, Fiji HDF5, with the `[hdf5]` extra |
611
+ | **HDF5** | ilastik, BigDataViewer / BigStitcher, Luxendo experiments and `.lux.h5`, Fiji HDF5, with the `[hdf5]` extra |
579
612
  | **ICS / IDS** | Image Cytometry Standard 1.0 and 2.0 (SVI Huygens and others) |
580
613
  | **MetaMorph** | `.nd` acquisitions and their TIFF / STK files |
581
614
  | **Micro-Manager 1.4**| image-per-file acquisitions (`metadata.txt` + one TIFF per plane) |
582
615
  | **DeltaVision** | `.dv`, `.r3d` (softWoRx, OMX, Priism) |
583
616
  | **MRC** | Common MRC modes and extended headers |
584
- | **Plates (HCS)** | Harmony, MetaXpress, IN Cell and CellVoyager plates |
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 |
585
619
  | **JPEG / PNG / BMP** | Common raster image formats |
586
620
 
587
621
  All formats use the same `File`, `Image`, axes, indexing, metadata, and reading interface wherever possible.
@@ -645,8 +679,14 @@ f = micro_reader.open("data_t001.ome.tif", multifile=False)
645
679
 
646
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.
647
681
 
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.
683
+
648
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.
649
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
+
650
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.
651
691
 
652
692
  ### CZI
@@ -744,7 +784,7 @@ These need the `[hdf5]` extra as well.
744
784
 
745
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).
746
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.
747
- * **Luxendo / Bruker light-sheet** (`.lux.h5`): one stack per file. A whole acquisition opens through the Imaris file Luxendo writes next to it.
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.
748
788
  * **Fiji HDF5** (the Vibez plugin): datasets with `element_size_um`; `t<n>/channel<n>` datasets make one `t c z y x` image.
749
789
 
750
790
  ### ICS / IDS
@@ -910,6 +950,14 @@ Applications work with the same `File` and `Image` model described in [How it wo
910
950
 
911
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.
912
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
+
913
961
  ## License
914
962
 
915
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, MetaXpress, IN Cell, CellVoyager).
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: pre-alpha (0.0.x)**
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), or the one image it holds. 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, emission, tile, etc., are represented as image indices rather than array axes, as discussed above.
226
+ Other dimensions, such as illumination, excitation, tile, etc., are represented as image indices rather than array axes, as discussed above.
224
227
 
225
- ### Changing axis order
228
+ ### Lambda scans
226
229
 
227
- You can request a specific axis order:
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
- image = f.images[0].as_axes("tczyx")
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
- or when opening the file:
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.tif", axes="tczyx")
251
+ f = micro_reader.open("image.ics", axes="native")
237
252
  ```
238
253
 
239
- You can also request the canonical order:
254
+ You can also request a specific axis order:
240
255
 
241
256
  ```python
242
- f = micro_reader.open("image.tif", axes="canonical")
257
+ image = f.images[0].as_axes("tczyx")
243
258
  ```
244
259
 
245
- Canonical order is:
260
+ or when opening the file:
246
261
 
247
- ```text
248
- t c z y x
262
+ ```python
263
+ f = micro_reader.open("image.tif", axes="zyx")
249
264
  ```
250
265
 
251
- followed by `s` when RGB samples are present.
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
 
@@ -270,18 +283,18 @@ Only axes with size 1 can be removed. `y` and `x` are never removed.
270
283
 
271
284
  ### RGB samples
272
285
 
273
- By default, RGB samples are represented by the `s` axis.
286
+ `micro_reader.open` turns RGB samples into channels: an RGB image has 3 channels, named "<name> R", "G" and "B" and coloured red, green and blue (a file without a channel name gives three unnamed channels in those colours). An image that has channels already gets c × s of them: "<name> R" ... for each.
274
287
 
275
- You can instead represent RGB as channels:
288
+ To keep RGB samples as their own `s` axis, the way the file stores them:
276
289
 
277
290
  ```python
278
291
  f = micro_reader.open(
279
292
  "image.tif",
280
- samples="channels",
293
+ samples="keep",
281
294
  )
282
295
  ```
283
296
 
284
- If there are existing channels already, RGB samples will fold with them, leading to a total of c × s channels.
297
+ `image.source` is then not needed: with the default, it is the image as stored, with its `s` axis. `image.as_axes(...)` keeps `s` unless asked for `samples="channels"`.
285
298
 
286
299
 
287
300
  ## Ambiguous dimensions
@@ -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 `zyx` axes.
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
@@ -421,21 +436,38 @@ The plate layout is available through:
421
436
  plate = f.plates[0]
422
437
  plate.wells[0].name
423
438
  # "B2"
439
+ plate.row_names, plate.column_names # ('A', 'B', ...), ('1', '2', ...)
440
+ part = plate.rows[2:5].columns[3:7] # a part of the plate: still a plate
424
441
  ```
425
442
 
443
+ Every level knows what it is and what holds it: `.kind` is `"file"`, `"plate"`, `"well"`
444
+ or `"field"`, and `.parent` is the level above. Accessors go down only: a file holds
445
+ `.plates`, a plate `.wells`, a well `.fields`, a field `.images`. `plate.rows` and
446
+ `plate.columns` slice a plate by position, name or a list of them, as `f.select(row=...,
447
+ column=...)` does; rows and columns are a well's address, not a level.
448
+
426
449
  Images can be reached through the wells, or selected by well:
427
450
 
428
451
  ```python
429
452
  image = plate.wells[0].fields[0].image
430
- f.select(well=plate.well_index("B2"))
453
+ f.select(well=plate.well_index("B2")) # by its place in plate.wells
454
+ f.select(well_name="B2") # by name
455
+ f.select(row_name="B", column=range(1, 4)) # rows and columns: names, or 0-based numbers
456
+ f.select(well=["B2", "C5"], field=[0, 1]) # a list, tuple, range or set: any of them
457
+ f.select(row=slice(2, None)) # a slice of positions: row C onward
431
458
  ```
432
459
 
460
+ A plate's places have a position and a name, a keyword each: `plate` / `plate_name`,
461
+ `acquisition` / `acquisition_name`, `well` / `well_name`, `row` / `row_name`, `column` /
462
+ `column_name` (the columns of ome-zarr-pyramid's table). A name given to a position keyword
463
+ is its name (`well="B2"` is `well_name="B2"`).
464
+
433
465
  `f.outline()` shows the plates, wells and fields of a file.
434
466
 
435
- Zeiss CZI plates are read as plates with `plates=True`, and as series otherwise:
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:
436
468
 
437
469
  ```python
438
- f = micro_reader.open("plate.czi", plates=True)
470
+ f = micro_reader.open("plate.czi")
439
471
  ```
440
472
 
441
473
  Missing plane files are read as zeros, with a `MissingImagesWarning`. A field without any of its files is left out.
@@ -494,6 +526,7 @@ meta.channels[0]
494
526
  `micro-reader` follows a few important rules:
495
527
 
496
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`.
497
530
  * Missing metadata is left as `None` or absent.
498
531
  * Pixel sizes are never invented as `1.0`.
499
532
  * Colours are not invented when the file does not provide them.
@@ -505,7 +538,7 @@ meta.channels[0]
505
538
 
506
539
  Where possible, format-specific coordinate conventions are converted to this common representation.
507
540
 
508
- 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`.
509
542
 
510
543
  ## Supported formats
511
544
 
@@ -521,13 +554,14 @@ The position is omitted when it cannot be determined reliably.
521
554
  | **VSI** | Olympus / Evident cellSens and VS120 / VS200 slide scanners |
522
555
  | **NDPIS** | Hamamatsu NanoZoomer fluorescence sets (one NDPI per channel) |
523
556
  | **Imaris** | `.ims`, with the `[hdf5]` extra |
524
- | **HDF5** | ilastik, BigDataViewer / BigStitcher, Luxendo, Fiji HDF5, with the `[hdf5]` extra |
557
+ | **HDF5** | ilastik, BigDataViewer / BigStitcher, Luxendo experiments and `.lux.h5`, Fiji HDF5, with the `[hdf5]` extra |
525
558
  | **ICS / IDS** | Image Cytometry Standard 1.0 and 2.0 (SVI Huygens and others) |
526
559
  | **MetaMorph** | `.nd` acquisitions and their TIFF / STK files |
527
560
  | **Micro-Manager 1.4**| image-per-file acquisitions (`metadata.txt` + one TIFF per plane) |
528
561
  | **DeltaVision** | `.dv`, `.r3d` (softWoRx, OMX, Priism) |
529
562
  | **MRC** | Common MRC modes and extended headers |
530
- | **Plates (HCS)** | Harmony, MetaXpress, IN Cell and CellVoyager plates |
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 |
531
565
  | **JPEG / PNG / BMP** | Common raster image formats |
532
566
 
533
567
  All formats use the same `File`, `Image`, axes, indexing, metadata, and reading interface wherever possible.
@@ -591,8 +625,14 @@ f = micro_reader.open("data_t001.ome.tif", multifile=False)
591
625
 
592
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.
593
627
 
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.
629
+
594
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.
595
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
+
596
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.
597
637
 
598
638
  ### CZI
@@ -690,7 +730,7 @@ These need the `[hdf5]` extra as well.
690
730
 
691
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).
692
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.
693
- * **Luxendo / Bruker light-sheet** (`.lux.h5`): one stack per file. A whole acquisition opens through the Imaris file Luxendo writes next to it.
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.
694
734
  * **Fiji HDF5** (the Vibez plugin): datasets with `element_size_um`; `t<n>/channel<n>` datasets make one `t c z y x` image.
695
735
 
696
736
  ### ICS / IDS
@@ -856,6 +896,14 @@ Applications work with the same `File` and `Image` model described in [How it wo
856
896
 
857
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.
858
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
+
859
907
  ## License
860
908
 
861
909
  MIT
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "micro-reader"
9
- version = "0.2.1"
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 :: 2 - Pre-Alpha",
19
+ "Development Status :: 3 - Alpha",
20
20
  "Intended Audience :: Science/Research",
21
21
  "Programming Language :: Python :: 3",
22
22
  "Programming Language :: Python :: 3.11",