Skip to content

Input/Output

All the input/output operations are handled by the panseg.io module. This module provides functions to read and write data in different formats. The supported formats are tiff, h5, and zarr, jpeg, png.

Reading

panseg.io.smart_load(path: Path, key: str | None = None, default=load_tiff) -> np.ndarray

Load a dataset from a file. The loader is chosen based on the file extension. Supported formats are: tiff, h5, zarr, and PIL images. If the format is not supported, a default loader can be provided (default: load_tiff).

Parameters:

  • path (Path) –

    path to the file to load.

  • key (str, default: None ) –

    key of the dataset to load (if h5 or zarr).

  • default (callable, default: load_tiff ) –

    default loader if the type is not understood.

Returns:

  • stack ( ndarray ) –

    numpy array with the image data.

Examples:

>>> data = smart_load('path/to/file.tif')
>>> data = smart_load('path/to/file.h5', key='raw')
Source code in panseg/io/io.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def smart_load(path: Path, key: str | None = None, default=load_tiff) -> np.ndarray:
    """
    Load a dataset from a file. The loader is chosen based on the file extension.
    Supported formats are: tiff, h5, zarr, and PIL images.
    If the format is not supported, a default loader can be provided (default: load_tiff).

    Args:
        path (Path): path to the file to load.
        key (str): key of the dataset to load (if h5 or zarr).
        default (callable): default loader if the type is not understood.

    Returns:
        stack (np.ndarray): numpy array with the image data.

    Examples:
        >>> data = smart_load('path/to/file.tif')
        >>> data = smart_load('path/to/file.h5', key='raw')

    """
    ext = (path.suffix).lower()
    if key == "":
        key = None

    if ext in H5_EXTENSIONS:
        return load_h5(path, key)

    elif ext in TIFF_EXTENSIONS:
        return load_tiff(path)

    elif ext in PIL_EXTENSIONS:
        return load_pil(path)

    elif ext in ZARR_EXTENSIONS:
        return load_zarr(path, key)

    else:
        logger.warning(f"No default found for {ext}, reverting to default loader.")
        return default(path)

Writing

panseg.io.create_tiff(path: Path, stack: np.ndarray, voxel_size: VoxelSize, layout: str = 'ZYX', t_spacing: Optional[float] = None, force_bigtiff=False) -> None

Create a tiff file from a numpy array

A time-bearing layout is always written as an OME-TIFF: the ImageJ format cannot represent the time axis. BigTIFF is still only used when the data exceeds 4 GiB or force_bigtiff is set.

Parameters:

  • path (Path) –

    path to save the tiff file

  • stack (ndarray) –

    numpy array to save as tiff

  • voxel_size (list or tuple) –

    tuple of the voxel size

  • voxel_size_unit (str) –

    units of the voxel size

  • t_spacing (float | None, default: None ) –

    time spacing between timepoints in seconds, written as OME-XML TimeIncrement when the layout carries a time axis. None (unknown) exports SizeT without time metadata.

  • force_bigtiff (bool, default: False ) –

    forces the use of bigtiff. Used for testing.

Source code in panseg/io/tiff.py
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
def create_tiff(
    path: Path,
    stack: np.ndarray,
    voxel_size: VoxelSize,
    layout: str = "ZYX",
    t_spacing: Optional[float] = None,
    force_bigtiff=False,
) -> None:
    """
    Create a tiff file from a numpy array

    A time-bearing layout is always written as an OME-TIFF: the ImageJ
    format cannot represent the time axis. BigTIFF is still only used when
    the data exceeds 4 GiB or ``force_bigtiff`` is set.

    Args:
        path (Path): path to save the tiff file
        stack (np.ndarray): numpy array to save as tiff
        voxel_size (list or tuple): tuple of the voxel size
        voxel_size_unit (str): units of the voxel size
        t_spacing (float | None): time spacing between timepoints in seconds,
            written as OME-XML TimeIncrement when the layout carries a time
            axis. None (unknown) exports SizeT without time metadata.
        force_bigtiff (bool): forces the use of bigtiff. Used for testing.

    """
    # taken from: https://pypi.org/project/tifffile docs
    # dimensions in TZCYXS order
    if layout == "ZYX":
        assert stack.ndim == 3, "Stack dimensions must be in ZYX order"
        z, y, x = stack.shape
        logical_stack, logical_axes = stack, "ZYX"
        stack = stack.reshape(1, z, 1, y, x, 1)

    elif layout == "YX":
        assert stack.ndim == 2, "Stack dimensions must be in YX order"
        y, x = stack.shape
        logical_stack, logical_axes = stack, "YX"
        stack = stack.reshape(1, 1, 1, y, x, 1)

    elif layout == "CYX":
        assert stack.ndim == 3, "Stack dimensions must be in CYX order"
        c, y, x = stack.shape
        logical_stack, logical_axes = stack, "CYX"
        stack = stack.reshape(1, 1, c, y, x, 1)

    elif layout == "ZCYX":
        assert stack.ndim == 4, "Stack dimensions must be in ZCYX order"
        z, c, y, x = stack.shape
        logical_stack, logical_axes = stack, "ZCYX"
        stack = stack.reshape(1, z, c, y, x, 1)

    elif layout == "CZYX":
        assert stack.ndim == 4, "Stack dimensions must be in CZYX order"
        stack = np.transpose(stack, (1, 0, 2, 3))
        z, c, y, x = stack.shape
        logical_stack, logical_axes = stack, "ZCYX"
        stack = stack.reshape(1, z, c, y, x, 1)

    elif layout == "TYX":
        assert stack.ndim == 3, "Stack dimensions must be in TYX order"
        logical_stack, logical_axes = stack, "TYX"

    elif layout == "TCYX":
        assert stack.ndim == 4, "Stack dimensions must be in TCYX order"
        logical_stack, logical_axes = stack, "TCYX"

    elif layout == "TZYX":
        assert stack.ndim == 4, "Stack dimensions must be in TZYX order"
        logical_stack, logical_axes = stack, "TZYX"

    elif layout == "TCZYX":
        assert stack.ndim == 5, "Stack dimensions must be in TCZYX order"
        logical_stack, logical_axes = stack, "TCZYX"

    else:
        raise ValueError(f"Layout {layout} not supported")

    is_timeseries = "T" in layout

    if voxel_size.voxels_size is not None:
        assert len(voxel_size.voxels_size) == 3, (
            "Voxel size must have 3 elements (z, y, x)"
        )
        spacing, y, x = voxel_size.voxels_size
    else:
        spacing, y, x = (1.0, 1.0, 1.0)

    resolution = (1.0 / x, 1.0 / y)
    # Save output results as tiff

    use_bigtiff = stack.nbytes > 4294967295 or force_bigtiff
    if is_timeseries or use_bigtiff:
        # OME-XML (unlike the shaped-JSON format) does not read `spacing`/`unit`
        # metadata keys and rejects the 6-D TZCYXS reshape, so write the logical
        # stack with explicit PhysicalSize* attributes to keep the voxel size.
        ome_metadata = {
            "axes": logical_axes,
            "PhysicalSizeX": x,
            "PhysicalSizeXUnit": voxel_size.unit,
            "PhysicalSizeY": y,
            "PhysicalSizeYUnit": voxel_size.unit,
            "PhysicalSizeZ": spacing,
            "PhysicalSizeZUnit": voxel_size.unit,
        }
        if is_timeseries and t_spacing is not None:
            # the time spacing is canonical seconds
            ome_metadata["TimeIncrement"] = t_spacing
            ome_metadata["TimeIncrementUnit"] = "s"
        if len(suffs := path.suffixes[-2:]) in [1, 2] and suffs[0] != ".ome":
            path = path.with_name(path.stem + ".ome" + path.suffix)
        tifffile.imwrite(
            path,
            data=logical_stack,
            dtype=logical_stack.dtype,
            bigtiff=use_bigtiff,
            ome=True,
            photometric="minisblack",
            resolution=resolution,
            metadata=ome_metadata,
        )
    else:
        tifffile.imwrite(
            path,
            data=stack,
            dtype=stack.dtype,
            imagej=True,
            resolution=resolution,
            metadata={"axes": "TZCYXS", "spacing": spacing, "unit": voxel_size.unit},
            compression="zlib",
        )

panseg.io.create_h5(path: Path, stack: np.ndarray, key: str, voxel_size: Optional[VoxelSize] = None, mode: str = 'a', axis_order: Optional[str] = None, t_spacing: Optional[float] = None, t_spacing_unit: str = 's') -> None

Create a dataset inside a h5 file from a numpy array.

Parameters:

  • path (Path) –

    path to the h5 file

  • stack (ndarray) –

    numpy array to save as dataset in the h5 file.

  • key (str) –

    key of the dataset in the h5 file.

  • voxel_size (VoxelSize, default: None ) –

    voxel size of the dataset.

  • mode (str, default: 'a' ) –

    mode to open the h5 file ['w', 'a'].

  • axis_order (Optional[str], default: None ) –

    layout string of the stack (e.g. "TZYX"), written as the dataset attr axis_order. Written on every PanSeg export; old readers ignore it.

  • t_spacing (Optional[float], default: None ) –

    time spacing between timepoints in seconds, written as the dataset attr t_spacing when the axis order carries a time axis. None (unknown) writes no time attrs.

  • t_spacing_unit (str, default: 's' ) –

    unit of the time spacing, written as the dataset attr t_spacing_unit.

Source code in panseg/io/h5.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
def create_h5(
    path: Path,
    stack: np.ndarray,
    key: str,
    voxel_size: Optional[VoxelSize] = None,
    mode: str = "a",
    axis_order: Optional[str] = None,
    t_spacing: Optional[float] = None,
    t_spacing_unit: str = "s",
) -> None:
    """
    Create a dataset inside a h5 file from a numpy array.

    Args:
        path (Path): path to the h5 file
        stack (np.ndarray): numpy array to save as dataset in the h5 file.
        key (str): key of the dataset in the h5 file.
        voxel_size (VoxelSize): voxel size of the dataset.
        mode (str): mode to open the h5 file ['w', 'a'].
        axis_order (Optional[str]): layout string of the stack (e.g. "TZYX"),
            written as the dataset attr ``axis_order``. Written on every
            PanSeg export; old readers ignore it.
        t_spacing (Optional[float]): time spacing between timepoints in
            seconds, written as the dataset attr ``t_spacing`` when the axis
            order carries a time axis. None (unknown) writes no time attrs.
        t_spacing_unit (str): unit of the time spacing, written as the
            dataset attr ``t_spacing_unit``.

    """

    if key is None:
        raise ValueError("Key is required to create a dataset in a h5 file.")

    if key == "":
        raise ValueError("Key cannot be empty to create a dataset in a h5 file.")

    with h5py.File(path, mode) as f:
        if key in f:
            del f[key]
        f.create_dataset(key, data=stack, compression="gzip")
        # save voxel_size
        if voxel_size is not None and voxel_size.voxels_size is not None:
            f[key].attrs["element_size_um"] = voxel_size.voxels_size
        if axis_order is not None:
            f[key].attrs["axis_order"] = axis_order
            if "T" in axis_order and t_spacing is not None:
                f[key].attrs["t_spacing"] = t_spacing
                f[key].attrs["t_spacing_unit"] = t_spacing_unit

panseg.io.create_zarr(path: Path, stack: np.ndarray, key: str, voxel_size: VoxelSize, mode: str = 'a', axis_order: str | None = None, t_spacing: float | None = None, t_spacing_unit: str = 's') -> None

Create a Zarr array from a NumPy array.

Parameters:

  • path (Path) –

    The path to the Zarr file.

  • stack (ndarray) –

    The NumPy array to save as a dataset.

  • key (str) –

    The internal key of the desired dataset.

  • voxel_size (VoxelSize) –

    The voxel size of the dataset.

  • mode (str, default: 'a' ) –

    The mode to open the Zarr file ['w', 'a'].

  • axis_order (str | None, default: None ) –

    Layout string of the stack (e.g. "TZYX"), written as the dataset attr axis_order. Written on every PanSeg export; old readers ignore it.

  • t_spacing (float | None, default: None ) –

    Time spacing between timepoints in seconds, written as the dataset attr t_spacing when the axis order carries a time axis. None (unknown) writes no time attrs.

  • t_spacing_unit (str, default: 's' ) –

    Unit of the time spacing, written as the dataset attr t_spacing_unit.

Source code in panseg/io/zarr.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def create_zarr(
    path: Path,
    stack: np.ndarray,
    key: str,
    voxel_size: VoxelSize,
    mode: str = "a",
    axis_order: str | None = None,
    t_spacing: float | None = None,
    t_spacing_unit: str = "s",
) -> None:
    """
    Create a Zarr array from a NumPy array.

    Args:
        path (Path): The path to the Zarr file.
        stack (np.ndarray): The NumPy array to save as a dataset.
        key (str): The internal key of the desired dataset.
        voxel_size (VoxelSize): The voxel size of the dataset.
        mode (str): The mode to open the Zarr file ['w', 'a'].
        axis_order (str | None): Layout string of the stack (e.g. "TZYX"),
            written as the dataset attr ``axis_order``. Written on every
            PanSeg export; old readers ignore it.
        t_spacing (float | None): Time spacing between timepoints in
            seconds, written as the dataset attr ``t_spacing`` when the axis
            order carries a time axis. None (unknown) writes no time attrs.
        t_spacing_unit (str): Unit of the time spacing, written as the
            dataset attr ``t_spacing_unit``.
    """
    if not key:
        raise ValueError("Key cannot be None or empty.")

    zarr_file = zarr.open_group(store=str(path), mode=mode)

    if IS_ZARR_V3:
        # v3 requires explicit `create_array`, and write manually
        assert isinstance(zarr_file, zarr.Group), f"Invalid Zarr file: {path}"
        zarr_file.create_array(name=key, shape=stack.shape, dtype=stack.dtype)
        zarr_file[key][:] = stack
    else:
        # v2 allows `create_dataset` with `overwrite`
        zarr_file.create_dataset(key, data=stack, compression="gzip", overwrite=True)

    zarr_file[key].attrs["element_size_um"] = voxel_size.voxels_size
    if axis_order is not None:
        zarr_file[key].attrs["axis_order"] = axis_order
        if "T" in axis_order and t_spacing is not None:
            zarr_file[key].attrs["t_spacing"] = t_spacing
            zarr_file[key].attrs["t_spacing_unit"] = t_spacing_unit

Tiff Utilities

panseg.io.tiff.read_tiff_voxel_size(file_path: Path) -> VoxelSize

Returns the voxels size and the voxel units for imagej and ome style tiff (if absent returns [1, 1, 1], um)

Parameters:

  • file_path (Path) –

    path to the tiff file

Returns:

  • VoxelSize ( VoxelSize ) –

    voxel size and unit

Source code in panseg/io/tiff.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def read_tiff_voxel_size(file_path: Path) -> VoxelSize:
    """
    Returns the voxels size and the voxel units for imagej and ome style tiff (if absent returns [1, 1, 1], um)

    Args:
        file_path (Path): path to the tiff file

    Returns:
        VoxelSize: voxel size and unit

    """
    with tifffile.TiffFile(file_path) as tiff:
        try:
            if tiff.imagej_metadata is not None:
                return _read_imagej_meta(tiff)

            elif tiff.ome_metadata is not None:
                return _read_ome_meta(tiff)
        except ValidationError:
            warnings.warn(
                "Error parsing tiff meta unit. Reverting to default "
                "voxel size (1., 1., 1.) um"
            )
            return VoxelSize()

        warnings.warn("No metadata found.")
        return VoxelSize()

H5 Utilities

panseg.io.h5.list_h5_keys(path: Path) -> list[str]

List all keys in a h5 file

Parameters:

  • path (Path) –

    path to the h5 file (Path object)

Returns:

  • keys ( list[str] ) –

    A list of keys in the h5 file.

Source code in panseg/io/h5.py
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
def list_h5_keys(path: Path) -> list[str]:
    """
    List all keys in a h5 file

    Args:
        path (Path): path to the h5 file (Path object)

    Returns:
        keys (list[str]): A list of keys in the h5 file.

    """
    _validate_h5_file(path)

    def _recursive_find_keys(f, base="/"):
        _list_keys = []
        for key, dataset in f.items():
            if isinstance(dataset, h5py.Group):
                new_base = f"{base}{key}/"
                _list_keys += _recursive_find_keys(dataset, new_base)

            elif isinstance(dataset, h5py.Dataset):
                _list_keys.append(f"{base}{key}")
        return _list_keys

    with h5py.File(path, "r") as h5_f:
        return _recursive_find_keys(h5_f)

panseg.io.h5.del_h5_key(path: Path, key: str, mode: str = 'a') -> None

helper function to delete a dataset from a h5file

Parameters:

  • path (Path) –

    path to the h5 file (Path object)

  • key (str) –

    key of the dataset to delete

  • mode (str, default: 'a' ) –

    mode to open the h5 file ['r', 'r+']

Source code in panseg/io/h5.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def del_h5_key(path: Path, key: str, mode: str = "a") -> None:
    """
    helper function to delete a dataset from a h5file

    Args:
        path (Path): path to the h5 file (Path object)
        key (str): key of the dataset to delete
        mode (str): mode to open the h5 file ['r', 'r+']

    """
    _validate_h5_file(path)
    with h5py.File(path, mode) as f:
        if key in f:
            del f[key]
            f.close()

panseg.io.h5.rename_h5_key(path: Path, old_key: str, new_key: str, mode='r+') -> None

Rename the 'old_key' dataset to 'new_key'

Parameters:

  • path (Path) –

    path to the h5 file (Path object)

  • old_key (str) –

    old key name

  • new_key (str) –

    new key name

  • mode (str, default: 'r+' ) –

    mode to open the h5 file ['r', 'r+']

Source code in panseg/io/h5.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
def rename_h5_key(path: Path, old_key: str, new_key: str, mode="r+") -> None:
    """
    Rename the 'old_key' dataset to 'new_key'

    Args:
        path (Path): path to the h5 file (Path object)
        old_key (str): old key name
        new_key (str): new key name
        mode (str): mode to open the h5 file ['r', 'r+']

    """
    _validate_h5_file(path)
    with h5py.File(path, mode) as f:
        if old_key in f:
            f[new_key] = f[old_key]
            del f[old_key]
            f.close()

Zarr Utilities

panseg.io.zarr.list_zarr_keys(path: Path) -> list[str]

List all keys in a Zarr file.

Parameters:

  • path (Path) –

    The path to the Zarr file.

Returns:

  • list[str] –

    list[str]: A list of keys in the Zarr file.

Source code in panseg/io/zarr.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
def list_zarr_keys(path: Path) -> list[str]:
    """
    List all keys in a Zarr file.

    Args:
        path (Path): The path to the Zarr file.

    Returns:
        list[str]: A list of keys in the Zarr file.
    """

    def _recursive_find_keys(
        zarr_group: zarr.Group, base: Path = Path("")
    ) -> list[str]:
        _list_keys = []
        for key, dataset in zarr_group.members():
            if isinstance(dataset, zarr.Group):
                new_base = base / key
                _list_keys.extend(_recursive_find_keys(dataset, new_base))
            elif isinstance(dataset, zarr.Array):
                _list_keys.append(str(base / key))
        return _list_keys

    zarr_file = zarr.open_group(store=path, mode="r")
    return _recursive_find_keys(zarr_file)

panseg.io.zarr.del_zarr_key(path: Path, key: str, mode: str = 'a') -> None

Delete a dataset from a Zarr file.

Parameters:

  • path (Path) –

    The path to the Zarr file.

  • key (str) –

    The internal key of the dataset to be deleted.

  • mode (str, default: 'a' ) –

    The mode to open the Zarr file ['w', 'a'].

Source code in panseg/io/zarr.py
260
261
262
263
264
265
266
267
268
269
270
271
272
def del_zarr_key(path: Path, key: str, mode: str = "a") -> None:
    """
    Delete a dataset from a Zarr file.

    Args:
        path (Path): The path to the Zarr file.
        key (str): The internal key of the dataset to be deleted.
        mode (str): The mode to open the Zarr file ['w', 'a'].

    """
    zarr_file = zarr.open_group(store=path, mode=mode)
    if key in zarr_file:
        del zarr_file[key]

panseg.io.zarr.rename_zarr_key(path: Path, old_key: str, new_key: str, mode: str = 'r+') -> None

Rename a dataset in a Zarr file.

Parameters:

  • path (Path) –

    The path to the Zarr file.

  • old_key (str) –

    The current key of the dataset.

  • new_key (str) –

    The new key for the dataset.

  • mode (str, default: 'r+' ) –

    The mode to open the Zarr file ['r+'].

Source code in panseg/io/zarr.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
def rename_zarr_key(path: Path, old_key: str, new_key: str, mode: str = "r+") -> None:
    """
    Rename a dataset in a Zarr file.

    Args:
        path (Path): The path to the Zarr file.
        old_key (str): The current key of the dataset.
        new_key (str): The new key for the dataset.
        mode (str): The mode to open the Zarr file ['r+'].

    """
    zarr_file = zarr.open_group(store=path, mode=mode)
    if old_key in zarr_file:
        old_array = zarr_file[old_key]
        new_array = zarr_file.create_array(
            name=new_key, shape=old_array.shape, dtype=old_array.dtype
        )
        new_array[:] = old_array[:]
        new_array.attrs.update(dict(old_array.attrs))
        del zarr_file[old_key]