1. nima_io: Microscopy Data Reading Tutorial#

This notebook demonstrates reading various microscopy file formats, comparing nima_io (bioio-based) with tifffile, and inspecting OME metadata for all available test data.

1.1. Setup#

%load_ext autoreload
%autoreload 2

from pathlib import Path

import matplotlib.pyplot as plt
import tifffile

import nima_io.read as ir

tdata = Path("../../tests/data")

1.2. 1. Single-scene OME-TIFF#

A simple multi-channel time-series OME-TIFF with known structure: 5 timepoints, 3 channels, 17x13 pixels.

# nima_io: returns xarray.DataArray backed by dask
da = ir.read_image(str(tdata / "im1s1z3c5t_a.ome.tif"))
print(f"dims={da.dims}, shape={da.shape}, dtype={da.dtype}")
da.data
/home/runner/work/nima_io/nima_io/.venv/lib/python3.14/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.6.3) or chardet (7.0.1)/charset_normalizer (3.4.5) doesn't match a supported version!
  warnings.warn(
dims=('T', 'C', 'Z', 'Y', 'X'), shape=(5, 3, 1, 17, 13), dtype=uint16
Array Chunk
Bytes 6.47 kiB 442 B
Shape (5, 3, 1, 17, 13) (1, 1, 1, 17, 13)
Dask graph 15 chunks in 54 graph layers
Data type uint16 numpy.ndarray
3 5 13 17 1
# Access OME metadata from attrs
da.attrs["ome_metadata"].images[0].pixels.channels
[Channel(
    id='Channel:0:0',
    light_source_settings={'id': 'LightSource:b3870238-479a-45b4-8627-ddb41680e7be_1', 'attenuation': 0.9, 'wavelength': 482.0},
    detector_settings={'id': 'Detector:ec659ce6-9847-4cab-b64a-43b5e9d88f63', 'gain': 5.0, 'binning': <Binning.ONEBYONE: '1x1'>},
    light_path={'excitation_filters': [{'id': 'Filter:cb860710-71c0-46ee-8903-f20019f4b54e'}, {'id': 'Filter:cc5646d0-d3e9-42b6-a147-ad2bda838adf'}, {'id': 'Filter:058c011a-165c-4b82-9cf1-62af018e70d1'}]},
    samples_per_pixel=1,
    illumination_type='Epifluorescence',
    acquisition_mode='WideField',
 ),
 Channel(
    id='Channel:0:1',
    light_source_settings={'id': 'LightSource:b3870238-479a-45b4-8627-ddb41680e7be_2', 'attenuation': 0.9, 'wavelength': 563.0},
    detector_settings={'id': 'Detector:ec659ce6-9847-4cab-b64a-43b5e9d88f63', 'gain': 5.0, 'binning': <Binning.ONEBYONE: '1x1'>},
    light_path={'excitation_filters': [{'id': 'Filter:cb860710-71c0-46ee-8903-f20019f4b54e'}, {'id': 'Filter:cc5646d0-d3e9-42b6-a147-ad2bda838adf'}, {'id': 'Filter:058c011a-165c-4b82-9cf1-62af018e70d1'}]},
    samples_per_pixel=1,
    illumination_type='Epifluorescence',
    acquisition_mode='WideField',
 ),
 Channel(
    id='Channel:0:2',
    light_source_settings={'id': 'LightSource:b3870238-479a-45b4-8627-ddb41680e7be_4', 'attenuation': 0.9, 'wavelength': 458.0},
    detector_settings={'id': 'Detector:ec659ce6-9847-4cab-b64a-43b5e9d88f63', 'gain': 5.0, 'binning': <Binning.ONEBYONE: '1x1'>},
    light_path={'excitation_filters': [{'id': 'Filter:cb860710-71c0-46ee-8903-f20019f4b54e'}, {'id': 'Filter:cc5646d0-d3e9-42b6-a147-ad2bda838adf'}, {'id': 'Filter:058c011a-165c-4b82-9cf1-62af018e70d1'}]},
    samples_per_pixel=1,
    illumination_type='Epifluorescence',
    acquisition_mode='WideField',
 )]
# Structured metadata (consolidated from OME)
md = da.attrs["metadata"]
print(md)
print()
print(f"Objective: {md.objective[0]}")
print(f"Pixel size: {md.voxel_size[0]}")
print(f"Date: {md.date[0]}")
print()
for ci, ch in enumerate(md.channels[0]):
    print(
        f"Ch[{ci}]: wavelength={ch.wavelength}nm, "
        f"attenuation={ch.attenuation}, "
        f"exposure={ch.exposure}s, "
        f"gain={ch.gain}, "
        f"binning={ch.binning}"
    )
Metadata(S=1, T=[5], C=[3], Z=[1], Y=[17], X=[13]
  bits=[16], obj=['Objective:60XWater:f3efd78d-8646-4017-b655-b9b63e83d037']
  voxel_size=[VoxelSize(x=0.1364024, y=0.1364024, z=1000.0)]
  channels=
[[Channel(λ=482, att=0.9, exp=0.045, gain=5.0, binning=1x1),
  Channel(λ=563, att=0.9, exp=0.125, gain=5.0, binning=1x1),
  Channel(λ=458, att=0.9, exp=0.25, gain=5.0, binning=1x1)]])

Objective: Objective:60XWater:f3efd78d-8646-4017-b655-b9b63e83d037
Pixel size: VoxelSize(x=0.1364024, y=0.1364024, z=1000.0)
Date: 2016-09-01 10:49:21

Ch[0]: wavelength=482nm, attenuation=0.9, exposure=0.045s, gain=5.0, binning=1x1
Ch[1]: wavelength=563nm, attenuation=0.9, exposure=0.125s, gain=5.0, binning=1x1
Ch[2]: wavelength=458nm, attenuation=0.9, exposure=0.25s, gain=5.0, binning=1x1
# tifffile comparison
with tifffile.TiffFile(tdata / "im1s1z3c5t_a.ome.tif") as tif:
    print(f"Series: {len(tif.series)}")
    s = tif.series[0]
    print(f"shape={s.shape}, axes={s.axes}, dtype={s.dtype}")
    print(f"OME: {tif.is_ome}")
Series: 1
shape=(5, 3, 17, 13), axes=TCYX, dtype=uint16
OME: True

1.2.1. OME Metadata#

Access the full OME metadata object via bioio.

from bioio import BioImage

img = BioImage(tdata / "im1s1z3c5t_a.ome.tif")
ome = img.ome_metadata
px = ome.images[0].pixels
print(f"Image: {ome.images[0].name or ome.images[0].id}")
print(
    f"Dims: X={px.size_x}, Y={px.size_y}, C={px.size_c}, T={px.size_t}, Z={px.size_z}"
)
print(f"Pixel sizes: {img.physical_pixel_sizes}")
print("Channels:")
for ci, ch in enumerate(px.channels):
    ls = ch.light_source_settings
    wl = ls.wavelength if ls else None
    print(f"  [{ci}] id={ch.id}, wavelength={wl}")
Image: Image:0
Dims: X=13, Y=17, C=3, T=5, Z=1
Pixel sizes: PhysicalPixelSizes(Z=1000.0, Y=0.1364024, X=0.1364024)
Channels:
  [0] id=Channel:0:0, wavelength=482.0
  [1] id=Channel:0:1, wavelength=563.0
  [2] id=Channel:0:2, wavelength=458.0

1.2.2. Per-channel acquisition settings#

Exposure time and timestamps live in planes (one per T/C/Z combination). Channel-level settings (wavelength, attenuation, binning, gain) are on the Channel object. Combine both for a complete per-channel summary.

# Per-channel acquisition summary
for ci, ch in enumerate(px.channels):
    ls = ch.light_source_settings
    ds = ch.detector_settings
    # Exposure from the first plane of this channel
    plane = next(p for p in px.planes if p.the_c == ci)
    print(
        f"Ch[{ci}]: "
        f"wl={ls.wavelength if ls else None}, "
        f"att={ls.attenuation if ls else None}, "
        f"exposure={plane.exposure_time}, "
        f"binning={ds.binning if ds else None}, "
        f"gain={ds.gain if ds else None}"
    )
Ch[0]: wl=482.0, att=0.9, exposure=0.045, binning=Binning.ONEBYONE, gain=5.0
Ch[1]: wl=563.0, att=0.9, exposure=0.125, binning=Binning.ONEBYONE, gain=5.0
Ch[2]: wl=458.0, att=0.9, exposure=0.25, binning=Binning.ONEBYONE, gain=5.0

1.2.3. Channel naming#

Assign semantic channel names for ratio analysis.

da_named = ir.read_image(
    str(tdata / "im1s1z3c5t_a.ome.tif"),
    channels=["G", "R", "C"],
)
print(f"Channel coords: {list(da_named.coords['C'].values)}")
da_named.sel(C="G", T=0).data
Channel coords: [np.str_('G'), np.str_('R'), np.str_('C')]
Array Chunk
Bytes 442 B 442 B
Shape (1, 17, 13) (1, 17, 13)
Dask graph 1 chunks in 55 graph layers
Data type uint16 numpy.ndarray
13 17 1

1.3. 2. Multi-channel time-series OME-TIFF#

A 7-timepoint, 3-channel image without wavelength metadata.

da_mcts = ir.read_image(str(tdata / "multi-channel-time-series.ome.tif"))
print(f"dims={da_mcts.dims}, shape={da_mcts.shape}, dtype={da_mcts.dtype}")

img_mcts = BioImage(tdata / "multi-channel-time-series.ome.tif")
print(f"Channel names: {img_mcts.channel_names}")
print(f"Pixel sizes: {img_mcts.physical_pixel_sizes}")
print(f"Image name: {img_mcts.ome_metadata.images[0].name}")
dims=('T', 'C', 'Z', 'Y', 'X'), shape=(7, 3, 1, 167, 439), dtype=int8
Channel names: [np.str_('Channel:0:0'), np.str_('Channel:0:1'), np.str_('Channel:0:2')]
Pixel sizes: PhysicalPixelSizes(Z=None, Y=None, X=None)
Image name: multi-channel-time-series

1.4. 3. File sequences with tifffile.TiffSequence#

For sets of related TIFF files, tifffile.TiffSequence stacks them. This is useful when acquisitions split across multiple files.

fp_glob = str(tdata / "im1s1z3c5t_?.ome.tif")

tifs = tifffile.TiffSequence(fp_glob)
d = tifs.asarray()
print(f"Glob matched {len(tifs)} files")
print(f"Stacked shape: {d.shape}")
print("Individual files:")
for f in sorted(tifs):
    print(f"  {Path(f).name}")
Glob matched 2 files
Stacked shape: (2, 5, 3, 17, 13)
Individual files:
  im1s1z3c5t_a.ome.tif
  im1s1z3c5t_b.ome.tif

1.5. 4. Tiled images (FEI multi-scene)#

FEI microscopes save tiled acquisitions as multi-scene OME-TIFFs. Each scene is one tile with stage position metadata.

1.5.1. 4a. Regular tile grid (t4_1.tif)#

# bioio sees each tile as a separate scene
img_tile = BioImage(tdata / "t4_1.tif")
print(f"Scenes: {len(img_tile.scenes)}")
print(f"Per-tile shape: {img_tile.shape}")
print(f"Pixel sizes: {img_tile.physical_pixel_sizes}")

# tifffile comparison
with tifffile.TiffFile(tdata / "t4_1.tif") as tif:
    print(f"\ntifffile series: {len(tif.series)}")
    print(f"Per-series shape: {tif.series[0].shape}, axes={tif.series[0].axes}")
Scenes: 15
Per-tile shape: (3, 4, 1, 256, 512)
Pixel sizes: PhysicalPixelSizes(Z=1000.0, Y=0.1333333, X=0.1333333)

tifffile series: 15
Per-series shape: (3, 4, 256, 512), axes=TCYX
# Stitch into a single DataArray
stitched = ir.stitch_scenes(str(tdata / "t4_1.tif"))
print(f"Stitched: dims={stitched.dims}, shape=T{stitched.sizes['T']}")
print(f"  Y={stitched.sizes['Y']}, X={stitched.sizes['X']}")
stitched.data
Stitched: dims=('T', 'C', 'Z', 'Y', 'X'), shape=T3
  Y=1280, X=1536
Array Chunk
Bytes 45.00 MiB 256.00 kiB
Shape (3, 4, 1, 1280, 1536) (1, 1, 1, 256, 512)
Dask graph 180 chunks in 651 graph layers
Data type uint16 numpy.ndarray
4 3 1536 1280 1
# Tilemap shows scene layout (row, col) -> scene_index
import numpy as np

tilemap = stitched.attrs["tilemap"]
print(f"Tile grid: {tilemap.shape[0]} rows x {tilemap.shape[1]} cols")
print(tilemap)
Tile grid: 5 rows x 3 cols
[[ 0  1  2]
 [ 5  4  3]
 [ 6  7  8]
 [11 10  9]
 [12 13 14]]

1.5.2. 4b. Tile grid with void tiles (tile6_1.tif)#

stitched_void = ir.stitch_scenes(str(tdata / "tile6_1.tif"))
print(f"Stitched: Y={stitched_void.sizes['Y']}, X={stitched_void.sizes['X']}")

tilemap_void = stitched_void.attrs["tilemap"]
print(f"Tile grid ({tilemap_void.shape}):")
print(tilemap_void)
print(f"Void tiles (=-1): {np.sum(tilemap_void == -1)}")
Stitched: Y=2560, X=2560
Tile grid ((5, 5)):
[[-1 -1  6  4 -1]
 [-1  7  5  3  8]
 [13  2 10  9 -1]
 [12  1 11 -1 -1]
 [-1  0 -1 -1 -1]]
Void tiles (=-1): 11
plt.imshow(stitched_void.sel(T=1, Z=0, C=1), cmap="Reds", vmax=1000, vmin=1)
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0x7fca7f104ad0>
../_images/162f0036088c5001f28eb48962d0912480222ed487956e8dece055fec75fa892.png

1.5.3. Stage positions#

OME metadata provides physical stage positions for each tile.

ome_tile = BioImage(tdata / "tile6_1.tif").ome_metadata
print(f"{'Scene':>5} {'X pos':>10} {'Y pos':>10}")
for i, im in enumerate(ome_tile.images):
    p = im.pixels.planes[0]
    print(f"{i:5d} {float(p.position_x):10.2f} {float(p.position_y):10.2f}")
Scene      X pos      Y pos
    0      50.63      81.84
    1      50.63      81.73
    2      50.63      81.63
    3      50.83      81.53
    4      50.83      81.43
    5      50.73      81.53
    6      50.73      81.43
    7      50.63      81.53
    8      50.93      81.53
    9      50.83      81.63
   10      50.73      81.63
   11      50.73      81.73
   12      50.52      81.73
   13      50.52      81.63

1.6. 5. TF8 format (.tf8)#

TF8 files are TIFFs with a non-standard extension. nima_io handles this transparently via a temp symlink.

da_tf8 = ir.read_image(str(tdata / "LC26GFP_1.tf8"))
print(f"dims={da_tf8.dims}, shape={da_tf8.shape}, dtype={da_tf8.dtype}")
cjdk: Installing JDK zulu-jre:11.0.30 to /home/runner/.cache/cjdk

Download   0% of  41.2 MiB |             | Elapsed Time: 0:00:00 ETA:  --:--:--
Download  17% of  41.2 MiB |##           | Elapsed Time: 0:00:00 ETA:   0:00:00
Download  45% of  41.2 MiB |#####        | Elapsed Time: 0:00:00 ETA:   0:00:00
Download  56% of  41.2 MiB |#######      | Elapsed Time: 0:00:00 ETA:   0:00:00
Download  65% of  41.2 MiB |########     | Elapsed Time: 0:00:00 ETA:   0:00:00
Download  74% of  41.2 MiB |#########    | Elapsed Time: 0:00:00 ETA:   0:00:00
Download  82% of  41.2 MiB |##########   | Elapsed Time: 0:00:00 ETA:   0:00:00
Download  91% of  41.2 MiB |###########  | Elapsed Time: 0:00:00 ETA:   0:00:00
Download 100% of  41.2 MiB |#############| Elapsed Time: 0:00:00 ETA:  00:00:00
Download 100% of  41.2 MiB |#############| Elapsed Time: 0:00:00 Time:  0:00:00

Extract / |#                                          | 0 Elapsed Time: 0:00:00
Extract - |    #                                     | 24 Elapsed Time: 0:00:00
Extract \ |     #                                    | 41 Elapsed Time: 0:00:00
Extract | |      #                                  | 371 Elapsed Time: 0:00:00

cjdk: Installing Maven to /home/runner/.cache/cjdk
Download   0% of   8.7 MiB |             | Elapsed Time: 0:00:00 ETA:  --:--:--
Download   0% of   8.7 MiB |             | Elapsed Time: 0:00:00 ETA:   0:02:01
Download   0% of   8.7 MiB |             | Elapsed Time: 0:00:00 ETA:   0:01:15
Download   0% of   8.7 MiB |             | Elapsed Time: 0:00:00 ETA:   0:00:57
Download   1% of   8.7 MiB |             | Elapsed Time: 0:00:00 ETA:   0:00:50
Download   1% of   8.7 MiB |             | Elapsed Time: 0:00:00 ETA:   0:00:42
Download   2% of   8.7 MiB |             | Elapsed Time: 0:00:00 ETA:   0:00:40
Download   2% of   8.7 MiB |             | Elapsed Time: 0:00:01 ETA:   0:00:37
Download   3% of   8.7 MiB |             | Elapsed Time: 0:00:01 ETA:   0:00:34
Download   3% of   8.7 MiB |             | Elapsed Time: 0:00:01 ETA:   0:00:34
Download   3% of   8.7 MiB |             | Elapsed Time: 0:00:01 ETA:   0:00:38
Download   4% of   8.7 MiB |             | Elapsed Time: 0:00:01 ETA:   0:00:32
Download   5% of   8.7 MiB |             | Elapsed Time: 0:00:01 ETA:   0:00:32
Download   5% of   8.7 MiB |             | Elapsed Time: 0:00:01 ETA:   0:00:31
Download   6% of   8.7 MiB |             | Elapsed Time: 0:00:02 ETA:   0:00:31
Download   6% of   8.7 MiB |             | Elapsed Time: 0:00:02 ETA:   0:00:30
Download   6% of   8.7 MiB |             | Elapsed Time: 0:00:02 ETA:   0:00:33
Download   7% of   8.7 MiB |#            | Elapsed Time: 0:00:02 ETA:   0:00:30
Download   8% of   8.7 MiB |#            | Elapsed Time: 0:00:02 ETA:   0:00:31
Download   8% of   8.7 MiB |#            | Elapsed Time: 0:00:02 ETA:   0:00:31
Download   8% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:30
Download   9% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:30
Download   9% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:30
Download  10% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:30
Download  10% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:29
Download  10% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:29
Download  11% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:29
Download  11% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:29
Download  11% of   8.7 MiB |#            | Elapsed Time: 0:00:03 ETA:   0:00:29
Download  12% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:29
Download  12% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:29
Download  12% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:28
Download  13% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:28
Download  13% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:28
Download  13% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:28
Download  14% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:28
Download  14% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:28
Download  14% of   8.7 MiB |#            | Elapsed Time: 0:00:04 ETA:   0:00:28
Download  15% of   8.7 MiB |#            | Elapsed Time: 0:00:05 ETA:   0:00:28
Download  15% of   8.7 MiB |#            | Elapsed Time: 0:00:05 ETA:   0:00:28
Download  15% of   8.7 MiB |##           | Elapsed Time: 0:00:05 ETA:   0:00:27
Download  16% of   8.7 MiB |##           | Elapsed Time: 0:00:05 ETA:   0:00:27
Download  16% of   8.7 MiB |##           | Elapsed Time: 0:00:05 ETA:   0:00:27
Download  17% of   8.7 MiB |##           | Elapsed Time: 0:00:05 ETA:   0:00:27
Download  17% of   8.7 MiB |##           | Elapsed Time: 0:00:05 ETA:   0:00:27
Download  17% of   8.7 MiB |##           | Elapsed Time: 0:00:05 ETA:   0:00:26
Download  18% of   8.7 MiB |##           | Elapsed Time: 0:00:05 ETA:   0:00:26
Download  18% of   8.7 MiB |##           | Elapsed Time: 0:00:06 ETA:   0:00:26
Download  19% of   8.7 MiB |##           | Elapsed Time: 0:00:06 ETA:   0:00:26
Download  19% of   8.7 MiB |##           | Elapsed Time: 0:00:06 ETA:   0:00:26
Download  20% of   8.7 MiB |##           | Elapsed Time: 0:00:06 ETA:   0:00:25
Download  20% of   8.7 MiB |##           | Elapsed Time: 0:00:06 ETA:   0:00:25
Download  21% of   8.7 MiB |##           | Elapsed Time: 0:00:06 ETA:   0:00:25
Download  21% of   8.7 MiB |##           | Elapsed Time: 0:00:06 ETA:   0:00:25
Download  22% of   8.7 MiB |##           | Elapsed Time: 0:00:07 ETA:   0:00:24
Download  22% of   8.7 MiB |##           | Elapsed Time: 0:00:07 ETA:   0:00:24
Download  22% of   8.7 MiB |##           | Elapsed Time: 0:00:07 ETA:   0:00:24
Download  23% of   8.7 MiB |###          | Elapsed Time: 0:00:07 ETA:   0:00:24
Download  23% of   8.7 MiB |###          | Elapsed Time: 0:00:07 ETA:   0:00:24
Download  24% of   8.7 MiB |###          | Elapsed Time: 0:00:07 ETA:   0:00:23
Download  24% of   8.7 MiB |###          | Elapsed Time: 0:00:07 ETA:   0:00:23
Download  25% of   8.7 MiB |###          | Elapsed Time: 0:00:07 ETA:   0:00:23
Download  25% of   8.7 MiB |###          | Elapsed Time: 0:00:07 ETA:   0:00:23
Download  25% of   8.7 MiB |###          | Elapsed Time: 0:00:08 ETA:   0:00:23
Download  26% of   8.7 MiB |###          | Elapsed Time: 0:00:08 ETA:   0:00:23
Download  26% of   8.7 MiB |###          | Elapsed Time: 0:00:08 ETA:   0:00:22
Download  27% of   8.7 MiB |###          | Elapsed Time: 0:00:08 ETA:   0:00:22
Download  27% of   8.7 MiB |###          | Elapsed Time: 0:00:08 ETA:   0:00:22
Download  27% of   8.7 MiB |###          | Elapsed Time: 0:00:08 ETA:   0:00:22
Download  28% of   8.7 MiB |###          | Elapsed Time: 0:00:08 ETA:   0:00:22
Download  28% of   8.7 MiB |###          | Elapsed Time: 0:00:08 ETA:   0:00:21
Download  29% of   8.7 MiB |###          | Elapsed Time: 0:00:09 ETA:   0:00:21
Download  30% of   8.7 MiB |###          | Elapsed Time: 0:00:09 ETA:   0:00:21
Download  30% of   8.7 MiB |###          | Elapsed Time: 0:00:09 ETA:   0:00:21
Download  30% of   8.7 MiB |####         | Elapsed Time: 0:00:09 ETA:   0:00:20
Download  31% of   8.7 MiB |####         | Elapsed Time: 0:00:09 ETA:   0:00:20
Download  32% of   8.7 MiB |####         | Elapsed Time: 0:00:09 ETA:   0:00:20
Download  32% of   8.7 MiB |####         | Elapsed Time: 0:00:09 ETA:   0:00:20
Download  32% of   8.7 MiB |####         | Elapsed Time: 0:00:09 ETA:   0:00:19
Download  33% of   8.7 MiB |####         | Elapsed Time: 0:00:09 ETA:   0:00:19
Download  33% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:19
Download  34% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:19
Download  34% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:19
Download  35% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:19
Download  35% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:18
Download  36% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:18
Download  36% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:18
Download  37% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:18
Download  37% of   8.7 MiB |####         | Elapsed Time: 0:00:10 ETA:   0:00:18
Download  37% of   8.7 MiB |####         | Elapsed Time: 0:00:11 ETA:   0:00:18
Download  38% of   8.7 MiB |#####        | Elapsed Time: 0:00:11 ETA:   0:00:17
Download  39% of   8.7 MiB |#####        | Elapsed Time: 0:00:11 ETA:   0:00:17
Download  39% of   8.7 MiB |#####        | Elapsed Time: 0:00:11 ETA:   0:00:17
Download  39% of   8.7 MiB |#####        | Elapsed Time: 0:00:11 ETA:   0:00:17
Download  40% of   8.7 MiB |#####        | Elapsed Time: 0:00:11 ETA:   0:00:17
Download  41% of   8.7 MiB |#####        | Elapsed Time: 0:00:11 ETA:   0:00:16
Download  41% of   8.7 MiB |#####        | Elapsed Time: 0:00:11 ETA:   0:00:16
Download  41% of   8.7 MiB |#####        | Elapsed Time: 0:00:12 ETA:   0:00:16
Download  42% of   8.7 MiB |#####        | Elapsed Time: 0:00:12 ETA:   0:00:16
Download  43% of   8.7 MiB |#####        | Elapsed Time: 0:00:12 ETA:   0:00:16
Download  43% of   8.7 MiB |#####        | Elapsed Time: 0:00:12 ETA:   0:00:16
Download  44% of   8.7 MiB |#####        | Elapsed Time: 0:00:12 ETA:   0:00:15
Download  44% of   8.7 MiB |#####        | Elapsed Time: 0:00:12 ETA:   0:00:15
Download  44% of   8.7 MiB |#####        | Elapsed Time: 0:00:12 ETA:   0:00:15
Download  45% of   8.7 MiB |#####        | Elapsed Time: 0:00:12 ETA:   0:00:15
Download  46% of   8.7 MiB |#####        | Elapsed Time: 0:00:13 ETA:   0:00:15
Download  46% of   8.7 MiB |######       | Elapsed Time: 0:00:13 ETA:   0:00:15
Download  47% of   8.7 MiB |######       | Elapsed Time: 0:00:13 ETA:   0:00:14
Download  48% of   8.7 MiB |######       | Elapsed Time: 0:00:13 ETA:   0:00:14
Download  48% of   8.7 MiB |######       | Elapsed Time: 0:00:13 ETA:   0:00:14
Download  48% of   8.7 MiB |######       | Elapsed Time: 0:00:13 ETA:   0:00:14
Download  49% of   8.7 MiB |######       | Elapsed Time: 0:00:13 ETA:   0:00:14
Download  50% of   8.7 MiB |######       | Elapsed Time: 0:00:14 ETA:   0:00:14
Download  50% of   8.7 MiB |######       | Elapsed Time: 0:00:14 ETA:   0:00:13
Download  51% of   8.7 MiB |######       | Elapsed Time: 0:00:14 ETA:   0:00:13
Download  51% of   8.7 MiB |######       | Elapsed Time: 0:00:14 ETA:   0:00:13
Download  52% of   8.7 MiB |######       | Elapsed Time: 0:00:14 ETA:   0:00:13
Download  52% of   8.7 MiB |######       | Elapsed Time: 0:00:14 ETA:   0:00:13
Download  52% of   8.7 MiB |######       | Elapsed Time: 0:00:14 ETA:   0:00:13
Download  53% of   8.7 MiB |######       | Elapsed Time: 0:00:14 ETA:   0:00:13
Download  53% of   8.7 MiB |######       | Elapsed Time: 0:00:15 ETA:   0:00:12
Download  54% of   8.7 MiB |#######      | Elapsed Time: 0:00:15 ETA:   0:00:12
Download  54% of   8.7 MiB |#######      | Elapsed Time: 0:00:15 ETA:   0:00:12
Download  55% of   8.7 MiB |#######      | Elapsed Time: 0:00:15 ETA:   0:00:12
Download  55% of   8.7 MiB |#######      | Elapsed Time: 0:00:15 ETA:   0:00:12
Download  56% of   8.7 MiB |#######      | Elapsed Time: 0:00:15 ETA:   0:00:12
Download  56% of   8.7 MiB |#######      | Elapsed Time: 0:00:15 ETA:   0:00:12
Download  57% of   8.7 MiB |#######      | Elapsed Time: 0:00:15 ETA:   0:00:11
Download  57% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  57% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  58% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  58% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  59% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  59% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  59% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  60% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  60% of   8.7 MiB |#######      | Elapsed Time: 0:00:16 ETA:   0:00:11
Download  60% of   8.7 MiB |#######      | Elapsed Time: 0:00:17 ETA:   0:00:10
Download  61% of   8.7 MiB |#######      | Elapsed Time: 0:00:17 ETA:   0:00:10
Download  61% of   8.7 MiB |########     | Elapsed Time: 0:00:17 ETA:   0:00:10
Download  62% of   8.7 MiB |########     | Elapsed Time: 0:00:17 ETA:   0:00:10
Download  62% of   8.7 MiB |########     | Elapsed Time: 0:00:17 ETA:   0:00:10
Download  63% of   8.7 MiB |########     | Elapsed Time: 0:00:17 ETA:   0:00:10
Download  63% of   8.7 MiB |########     | Elapsed Time: 0:00:17 ETA:   0:00:10
Download  64% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:10
Download  64% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:09
Download  64% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:09
Download  65% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:09
Download  65% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:09
Download  66% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:09
Download  66% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:09
Download  67% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:09
Download  67% of   8.7 MiB |########     | Elapsed Time: 0:00:18 ETA:   0:00:09
Download  67% of   8.7 MiB |########     | Elapsed Time: 0:00:19 ETA:   0:00:09
Download  68% of   8.7 MiB |########     | Elapsed Time: 0:00:19 ETA:   0:00:08
Download  68% of   8.7 MiB |########     | Elapsed Time: 0:00:19 ETA:   0:00:08
Download  69% of   8.7 MiB |########     | Elapsed Time: 0:00:19 ETA:   0:00:08
Download  69% of   8.7 MiB |#########    | Elapsed Time: 0:00:19 ETA:   0:00:08
Download  70% of   8.7 MiB |#########    | Elapsed Time: 0:00:19 ETA:   0:00:08
Download  70% of   8.7 MiB |#########    | Elapsed Time: 0:00:19 ETA:   0:00:08
Download  70% of   8.7 MiB |#########    | Elapsed Time: 0:00:19 ETA:   0:00:08
Download  71% of   8.7 MiB |#########    | Elapsed Time: 0:00:19 ETA:   0:00:08
Download  71% of   8.7 MiB |#########    | Elapsed Time: 0:00:20 ETA:   0:00:07
Download  72% of   8.7 MiB |#########    | Elapsed Time: 0:00:20 ETA:   0:00:07
Download  72% of   8.7 MiB |#########    | Elapsed Time: 0:00:20 ETA:   0:00:07
Download  73% of   8.7 MiB |#########    | Elapsed Time: 0:00:20 ETA:   0:00:07
Download  73% of   8.7 MiB |#########    | Elapsed Time: 0:00:20 ETA:   0:00:07
Download  73% of   8.7 MiB |#########    | Elapsed Time: 0:00:20 ETA:   0:00:07
Download  74% of   8.7 MiB |#########    | Elapsed Time: 0:00:20 ETA:   0:00:07
Download  74% of   8.7 MiB |#########    | Elapsed Time: 0:00:20 ETA:   0:00:07
Download  75% of   8.7 MiB |#########    | Elapsed Time: 0:00:21 ETA:   0:00:06
Download  75% of   8.7 MiB |#########    | Elapsed Time: 0:00:21 ETA:   0:00:06
Download  75% of   8.7 MiB |#########    | Elapsed Time: 0:00:21 ETA:   0:00:06
Download  76% of   8.7 MiB |#########    | Elapsed Time: 0:00:21 ETA:   0:00:06
Download  76% of   8.7 MiB |#########    | Elapsed Time: 0:00:21 ETA:   0:00:06
Download  77% of   8.7 MiB |##########   | Elapsed Time: 0:00:21 ETA:   0:00:06
Download  77% of   8.7 MiB |##########   | Elapsed Time: 0:00:21 ETA:   0:00:06
Download  77% of   8.7 MiB |##########   | Elapsed Time: 0:00:22 ETA:   0:00:06
Download  78% of   8.7 MiB |##########   | Elapsed Time: 0:00:22 ETA:   0:00:05
Download  78% of   8.7 MiB |##########   | Elapsed Time: 0:00:22 ETA:   0:00:05
Download  79% of   8.7 MiB |##########   | Elapsed Time: 0:00:22 ETA:   0:00:05
Download  79% of   8.7 MiB |##########   | Elapsed Time: 0:00:22 ETA:   0:00:05
Download  80% of   8.7 MiB |##########   | Elapsed Time: 0:00:22 ETA:   0:00:05
Download  80% of   8.7 MiB |##########   | Elapsed Time: 0:00:22 ETA:   0:00:05
Download  81% of   8.7 MiB |##########   | Elapsed Time: 0:00:23 ETA:   0:00:05
Download  81% of   8.7 MiB |##########   | Elapsed Time: 0:00:23 ETA:   0:00:05
Download  82% of   8.7 MiB |##########   | Elapsed Time: 0:00:23 ETA:   0:00:05
Download  82% of   8.7 MiB |##########   | Elapsed Time: 0:00:23 ETA:   0:00:04
Download  82% of   8.7 MiB |##########   | Elapsed Time: 0:00:23 ETA:   0:00:04
Download  83% of   8.7 MiB |##########   | Elapsed Time: 0:00:23 ETA:   0:00:04
Download  83% of   8.7 MiB |##########   | Elapsed Time: 0:00:23 ETA:   0:00:04
Download  83% of   8.7 MiB |##########   | Elapsed Time: 0:00:23 ETA:   0:00:04
Download  84% of   8.7 MiB |###########  | Elapsed Time: 0:00:24 ETA:   0:00:04
Download  85% of   8.7 MiB |###########  | Elapsed Time: 0:00:24 ETA:   0:00:04
Download  85% of   8.7 MiB |###########  | Elapsed Time: 0:00:24 ETA:   0:00:04
Download  86% of   8.7 MiB |###########  | Elapsed Time: 0:00:24 ETA:   0:00:03
Download  86% of   8.7 MiB |###########  | Elapsed Time: 0:00:24 ETA:   0:00:03
Download  86% of   8.7 MiB |###########  | Elapsed Time: 0:00:24 ETA:   0:00:03
Download  87% of   8.7 MiB |###########  | Elapsed Time: 0:00:24 ETA:   0:00:03
Download  87% of   8.7 MiB |###########  | Elapsed Time: 0:00:24 ETA:   0:00:03
Download  88% of   8.7 MiB |###########  | Elapsed Time: 0:00:25 ETA:   0:00:03
Download  88% of   8.7 MiB |###########  | Elapsed Time: 0:00:25 ETA:   0:00:03
Download  89% of   8.7 MiB |###########  | Elapsed Time: 0:00:25 ETA:   0:00:03
Download  89% of   8.7 MiB |###########  | Elapsed Time: 0:00:25 ETA:   0:00:02
Download  89% of   8.7 MiB |###########  | Elapsed Time: 0:00:25 ETA:   0:00:02
Download  90% of   8.7 MiB |###########  | Elapsed Time: 0:00:25 ETA:   0:00:02
Download  90% of   8.7 MiB |###########  | Elapsed Time: 0:00:26 ETA:   0:00:02
Download  92% of   8.7 MiB |###########  | Elapsed Time: 0:00:26 ETA:   0:00:02
Download  92% of   8.7 MiB |############ | Elapsed Time: 0:00:26 ETA:   0:00:02
Download  92% of   8.7 MiB |############ | Elapsed Time: 0:00:26 ETA:   0:00:02
Download  93% of   8.7 MiB |############ | Elapsed Time: 0:00:26 ETA:   0:00:01
Download  93% of   8.7 MiB |############ | Elapsed Time: 0:00:26 ETA:   0:00:01
Download  93% of   8.7 MiB |############ | Elapsed Time: 0:00:26 ETA:   0:00:01
Download  94% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:01
Download  94% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:01
Download  95% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:01
Download  95% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:01
Download  95% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:01
Download  96% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:01
Download  96% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:01
Download  96% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:00
Download  97% of   8.7 MiB |############ | Elapsed Time: 0:00:27 ETA:   0:00:00
Download  97% of   8.7 MiB |############ | Elapsed Time: 0:00:28 ETA:   0:00:00
Download  97% of   8.7 MiB |############ | Elapsed Time: 0:00:28 ETA:   0:00:00
Download  98% of   8.7 MiB |############ | Elapsed Time: 0:00:28 ETA:   0:00:00
Download  98% of   8.7 MiB |############ | Elapsed Time: 0:00:28 ETA:   0:00:00
Download  99% of   8.7 MiB |############ | Elapsed Time: 0:00:28 ETA:   0:00:00
Download  99% of   8.7 MiB |############ | Elapsed Time: 0:00:28 ETA:   0:00:00
Download 100% of   8.7 MiB |#############| Elapsed Time: 0:00:28 ETA:  00:00:00
Download 100% of   8.7 MiB |#############| Elapsed Time: 0:00:28 Time:  0:00:28

Extract / |#                                          | 0 Elapsed Time: 0:00:00
Extract | |#                                        | 102 Elapsed Time: 0:00:00

/home/runner/work/nima_io/nima_io/.venv/lib/python3.14/site-packages/pydantic/main.py:250: UserWarning: Casting invalid LightSourceID 'Lightsource:6272fd82-9593-4afe-a826-6d427c2e8771' to 'LightSource:0'
  validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
dims=('T', 'C', 'Z', 'Y', 'X'), shape=(1, 1, 1, 1200, 1600), dtype=uint16

1.7. 6. Large single-scene image (exp2_2.tif)#

81 timepoints, 2 channels (340nm/380nm ratiometric), 1200x1600 pixels.

img_exp = BioImage(tdata / "exp2_2.tif")
print(f"Shape: {img_exp.shape}")
print(f"Pixel sizes: {img_exp.physical_pixel_sizes}")

ome_exp = img_exp.ome_metadata
px_exp = ome_exp.images[0].pixels
for ci, ch in enumerate(px_exp.channels):
    ls = ch.light_source_settings
    wl = ls.wavelength if ls else None
    print(f"  Ch[{ci}]: wavelength={wl}")

# Instrument metadata
if ome_exp.instruments:
    inst = ome_exp.instruments[0]
    if inst.objectives:
        obj = inst.objectives[0]
        print(f"Objective: NA={obj.lens_na}, mag={obj.nominal_magnification}")
    if inst.detectors:
        print(f"Detector: {inst.detectors[0].model}")
/home/runner/work/nima_io/nima_io/.venv/lib/python3.14/site-packages/pydantic/main.py:250: UserWarning: Casting invalid LightSourceID 'Lightsource:e9ce7657-3552-460a-bc05-f6fc83991d36' to 'LightSource:1'
  validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
/home/runner/work/nima_io/nima_io/.venv/lib/python3.14/site-packages/pydantic/main.py:250: UserWarning: Casting invalid LightSourceID 'Lightsource:e9ce7657-3552-460a-bc05-f6fc83991d36' to 'LightSource:2'
  validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
/home/runner/work/nima_io/nima_io/.venv/lib/python3.14/site-packages/pydantic/main.py:250: UserWarning: Casting invalid LightSourceID 'Lightsource:e9ce7657-3552-460a-bc05-f6fc83991d36' to 'LightSource:3'
  validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
Shape: (81, 2, 1, 1200, 1600)
Pixel sizes: PhysicalPixelSizes(Z=1000.0, Y=0.74, X=0.74)
  Ch[0]: wavelength=380.0
  Ch[1]: wavelength=340.0
Objective: NA=0.3, mag=10.0
Detector: QImaging Retiga 2000DC
# Lazy read - no data loaded until .values or .compute()
da_exp = ir.read_image(str(tdata / "exp2_2.tif"))
print(f"Lazy DataArray: {da_exp.dims}, {da_exp.shape}")
print(f"Dask chunks: {da_exp.data.chunks}")
/home/runner/work/nima_io/nima_io/.venv/lib/python3.14/site-packages/pydantic/main.py:250: UserWarning: Casting invalid LightSourceID 'Lightsource:e9ce7657-3552-460a-bc05-f6fc83991d36' to 'LightSource:4'
  validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
/home/runner/work/nima_io/nima_io/.venv/lib/python3.14/site-packages/pydantic/main.py:250: UserWarning: Casting invalid LightSourceID 'Lightsource:e9ce7657-3552-460a-bc05-f6fc83991d36' to 'LightSource:5'
  validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
/home/runner/work/nima_io/nima_io/.venv/lib/python3.14/site-packages/pydantic/main.py:250: UserWarning: Casting invalid LightSourceID 'Lightsource:e9ce7657-3552-460a-bc05-f6fc83991d36' to 'LightSource:6'
  validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
Lazy DataArray: ('T', 'C', 'Z', 'Y', 'X'), (81, 2, 1, 1200, 1600)
Dask chunks: ((1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), (1, 1), (1,), (1200,), (1600,))

1.8. 7. Leica LIF files#

Multi-scene confocal Z-stacks read via bioio-lif (pure Python, no Java).

img_lif = BioImage(tdata / "2015Aug28_TransHXB2_50min+DMSO.lif")
print(f"Reader: {type(img_lif.reader).__module__}")
print(f"Scenes ({len(img_lif.scenes)}): {img_lif.scenes}")

for si, scene in enumerate(img_lif.scenes):
    img_lif.set_scene(si)
    print(
        f"  {scene}: shape={img_lif.shape}, "
        f"channels={img_lif.channel_names}, "
        f"voxel_z={img_lif.physical_pixel_sizes.Z}"
    )
Reader: bioio_lif.reader
Scenes (5): ('Series001', 'Series004', 'Series007', 'Series009', 'Series011')
  Series001: shape=(1, 3, 41, 512, 512), channels=[np.str_('Green'), np.str_('Gray'), np.str_('Red')], voxel_z=0.2964
  Series004: shape=(1, 3, 40, 512, 512), channels=[np.str_('Green'), np.str_('Gray'), np.str_('Red')], voxel_z=0.2964
  Series007: shape=(1, 3, 43, 512, 512), channels=[np.str_('Green'), np.str_('Gray'), np.str_('Red')], voxel_z=0.2964
  Series009: shape=(1, 3, 39, 512, 512), channels=[np.str_('Green'), np.str_('Gray'), np.str_('Red')], voxel_z=0.2964
  Series011: shape=(1, 3, 37, 512, 512), channels=[np.str_('Green'), np.str_('Gray'), np.str_('Red')], voxel_z=0.2964

1.9. 8. File comparison (diff)#

Compare two files for pixel-level equality.

a = str(tdata / "im1s1z3c5t_a.ome.tif")
b = str(tdata / "im1s1z3c5t_b.ome.tif")
bpix = str(tdata / "im1s1z3c5t_bpix.ome.tif")

print(f"a vs a (identical):  {ir.diff(a, a)}")
print(f"a vs b (same data):  {ir.diff(a, b)}")
print(f"a vs bpix (1px off): {ir.diff(a, bpix)}")
a vs a (identical):  True
a vs b (same data):  True
a vs bpix (1px off): False

1.10. 9. Backend comparison: nima_io vs tifffile#

Key differences between reading with nima_io (bioio) and raw tifffile.

# tifffile: raw arrays, manual dimension handling
with tifffile.TiffFile(tdata / "t4_1.tif") as tif:
    # Each series is a tile - tifffile doesn't auto-stitch
    tf_data = tif.series[0].asarray()
    print(f"tifffile single series: shape={tf_data.shape}, axes={tif.series[0].axes}")

# nima_io: auto-stitched, named dims, lazy
nio_data = ir.stitch_scenes(str(tdata / "t4_1.tif"))
print(f"nima_io stitched:     shape={dict(nio_data.sizes)}")
print(f"  lazy (dask):        {type(nio_data.data).__name__}")
tifffile single series: shape=(3, 4, 256, 512), axes=TCYX
nima_io stitched:     shape={'T': 3, 'C': 4, 'Z': 1, 'Y': 1280, 'X': 1536}
  lazy (dask):        Array
# tifffile: reading OME metadata requires manual XML parsing
with tifffile.TiffFile(tdata / "im1s1z3c5t_a.ome.tif") as tif:
    # tifffile exposes raw OME-XML string
    ome_xml = tif.ome_metadata  # raw XML string
    print(f"tifffile OME-XML: {type(ome_xml).__name__}, {len(ome_xml)} chars")

# nima_io/bioio: parsed OME object with typed attributes
img = BioImage(tdata / "im1s1z3c5t_a.ome.tif")
ome = img.ome_metadata  # ome_types.OME object
print(f"bioio OME: {type(ome).__name__}")
print(f"  images: {len(ome.images)}")
print(f"  instruments: {len(ome.instruments)}")
ch0 = ome.images[0].pixels.channels[0]
wl = ch0.light_source_settings.wavelength
print(f"  channels[0].wavelength: {wl}")
tifffile OME-XML: str, 47251 chars
bioio OME: OME
  images: 1
  instruments: 1
  channels[0].wavelength: 482.0