Skip to content

zarr

insert_omero_metadata #

insert_omero_metadata(src, window_max=None, window_min=None, window_start=None, window_end=None, id=None, name=None, dry_run=False)

Insert or update missing omero transitional metadata into .zattrs metadata of parent group for the input zarr array.

Parameters:

Name Type Description Default
src str

Path to Zarr array.

required
window_max int

Max view window value. Defaults to None.

None
window_min int

Min view window value. Defaults to None.

None
window_start int

Contrast min value. Defaults to None.

None
window_end int

Contrast max value. Defaults to None.

None
id int

Defaults to None.

None
name str

Name of the dataset. Defaults to None.

None
dry_run bool

if True, compute the omero metadata but do not write it to the group's attrs. Defaults to False.

False

Returns:

Name Type Description
dict dict

the omero metadata that was written (or would have been written, if dry_run is True).

Source code in src/cellmap_utils/zarr/metadata.py
def insert_omero_metadata(
    src: str,
    window_max: int = None,
    window_min: int = None,
    window_start: int = None,
    window_end: int = None,
    id: int = None,
    name: str = None,
    dry_run: bool = False,
) -> dict:
    """
    Insert or update missing omero transitional metadata into .zattrs metadata of parent group for the input zarr array.


    Args:
        src (str): Path to Zarr array.
        window_max (int, optional): Max view window value. Defaults to None.
        window_min (int, optional): Min view window value. Defaults to None.
        window_start (int, optional): Contrast min value. Defaults to None.
        window_end (int, optional): Contrast max value. Defaults to None.
        id (int, optional): Defaults to None.
        name (str, optional): Name of the dataset. Defaults to None.
        dry_run (bool, optional): if True, compute the omero metadata but do not
            write it to the group's attrs. Defaults to False.

    Returns:
        dict: the omero metadata that was written (or would have been written, if
            dry_run is True).
    """

    store_path, zarr_path = separate_store_path(src, "")

    z_store = zarr.NestedDirectoryStore(store_path)
    z_arr = zarr.open(store=z_store, path=zarr_path, mode="a")

    parent_group = access_parent(z_arr)

    if window_max == None:
        window_max = numpy.iinfo(z_arr.dtype).max
    if window_min == None:
        window_min = numpy.iinfo(z_arr.dtype).min

    omero = dict()
    omero["id"] = 1 if id == None else id
    omero["name"] = (
        os.path.basename(z_store.path.rstrip("/")).split(".")[0]
        if name == None
        else name
    )
    omero["version"] = "0.4"
    omero["channels"] = [
        {
            "active": True,
            "coefficient": 1,
            "color": "FFFFFF",
            "inverted": False,
            "label": parent_group.path.split("/")[-1],
            "window": {
                "end": window_max if window_end == None else window_end,
                "max": window_max,
                "min": window_min,
                "start": window_min if window_start == None else window_start,
            },
        }
    ]
    omero["rdefs"] = {
        "defaultT": 0,
        "defaultZ": int(z_arr.shape[0] / 2),
        "model": "greyscale",
    }

    if dry_run:
        logger.info(f"[dry_run] would write omero metadata to {parent_group.path}: {omero}")
    else:
        parent_group.attrs["omero"] = omero

    return omero

get_single_scale_metadata #

get_single_scale_metadata(ds_name, voxel_size, translation, name, units='nanometer', axes=['z', 'y', 'x'])

Returns multiscales ngff metadata with a single level.

Parameters:

Name Type Description Default
ds_name str

name of the dataset that contains the data.

required
voxel_size list[float]

scale. Example: [1.0, 1.0, 1.0]

required
translation list[float]

offset. Example: [0.0, 0.0, 0.0]

required
name str

Name of a multiscale set.

required
units str

Physical units. Defaults to 'nanometer'.

'nanometer'
axes list[str]

Axes labeling. Defaults to ['z', 'y', 'x'].

['z', 'y', 'x']

Returns:

Name Type Description
_type_

description

Source code in src/cellmap_utils/zarr/metadata.py
def get_single_scale_metadata(
    ds_name: str,
    voxel_size: list[float],
    translation: list[float],
    name: str,
    units: str = "nanometer",
    axes: list[str] = ["z", "y", "x"],
):
    """Returns multiscales ngff metadata with a single level.

    Args:
        ds_name (str): name of the dataset that contains the data.
        voxel_size (list[float]): scale. Example: [1.0, 1.0, 1.0]
        translation (list[float]): offset. Example: [0.0, 0.0, 0.0]
        name (str): Name of a multiscale set.
        units (str, optional): Physical units. Defaults to 'nanometer'.
        axes (list[str], optional): Axes labeling. Defaults to ['z', 'y', 'x'].

    Returns:
        _type_: _description_
    """
    z_attrs: dict = {"multiscales": [{}]}
    z_attrs["multiscales"][0]["axes"] = [
        {"name": axis, "type": "space", "unit": units} for axis in axes
    ]
    z_attrs["multiscales"][0]["coordinateTransformations"] = [
        {"scale": [1.0, 1.0, 1.0], "type": "scale"}
    ]
    z_attrs["multiscales"][0]["datasets"] = [
        {
            "coordinateTransformations": [
                {"scale": [float(item) for item in voxel_size], "type": "scale"},
                {"translation": translation, "type": "translation"},
            ],
            "path": ds_name,
        }
    ]

    z_attrs["multiscales"][0]["name"] = name
    z_attrs["multiscales"][0]["version"] = "0.4"

    return z_attrs

get_multiscale_metadata #

get_multiscale_metadata(voxel_size, translation, levels, units='nanometer', axes=['z', 'y', 'x'], name='')

Generates a multiscale metadata from specified voxel size, offset and multi-scale pyramid levels.

Parameters:

Name Type Description Default
voxel_size list[float]

physical size of the voxel

required
translation list[float]

physical translation of the center of the voxel.

required
levels int

how many levels are present in the multis-scale pyramid.

required
units str

Physical units. Defaults to 'nanometer'.

'nanometer'
axes list[str]

Axis order. Defaults to ['z', 'y', 'x'].

['z', 'y', 'x']
name str

Name of the dataset that would utilize multi-scale metadata. Defaults to ''.

''

Returns:

Name Type Description
_type_

description

Source code in src/cellmap_utils/zarr/metadata.py
def get_multiscale_metadata(
    voxel_size: list[float],
    translation: list[float],
    levels: int,
    units: str = "nanometer",
    axes: list[str] = ["z", "y", "x"],
    name: str = "",
):
    """Generates a multiscale metadata from specified voxel size, offset and multi-scale pyramid levels.

    Args:
        voxel_size (list[float]): physical size of the voxel
        translation (list[float]): physical translation of the center of the voxel.
        levels (int): how many levels are present in the multis-scale pyramid.
        units (str, optional): Physical units. Defaults to 'nanometer'.
        axes (list[str], optional): Axis order. Defaults to ['z', 'y', 'x'].
        name (str, optional): Name of the dataset that would utilize multi-scale metadata. Defaults to ''.

    Returns:
        _type_: _description_
    """

    multsc = get_single_scale_metadata("s0", voxel_size, translation, name, units, axes)

    z_attrs = multsc
    base_scale = z_attrs["multiscales"][0]["datasets"][0]["coordinateTransformations"][
        0
    ]["scale"]
    base_trans = z_attrs["multiscales"][0]["datasets"][0]["coordinateTransformations"][
        1
    ]["translation"]
    num_levels = levels
    for level in range(1, num_levels + 1):
        # print(f'{level=}')

        sn = [float(dim * pow(2, level)) for dim in base_scale]
        trn = [
            (dim * (pow(2, level - 1) - 0.5)) + tr
            for (dim, tr) in zip(base_scale, base_trans)
        ]

        z_attrs["multiscales"][0]["datasets"].append(
            {
                "coordinateTransformations": [
                    {"type": "scale", "scale": sn},
                    {"type": "translation", "translation": trn},
                ],
                "path": f"s{level}",
            }
        )

    return z_attrs

ome_ngff_only #

ome_ngff_only(zg, dry_run=False)

Delete all attrs from .zattrs that are not part of the OME-NGFF Zarr spec and CellMap metadata.

Parameters:

Name Type Description Default
zg Group

zarr group that contains multiscale metadata.

required
dry_run bool

if True, compute which attrs would be deleted but do not delete them. Defaults to False.

False

Returns:

Name Type Description
list list

the attr names that were deleted (or would have been deleted, if dry_run is True).

Source code in src/cellmap_utils/zarr/metadata.py
def ome_ngff_only(zg: zarr.Group, dry_run: bool = False) -> list:
    """Delete all attrs from .zattrs that are not part of the OME-NGFF Zarr spec and CellMap metadata.

    Args:
        zg (zarr.Group): zarr group that contains multiscale metadata.
        dry_run (bool, optional): if True, compute which attrs would be deleted
            but do not delete them. Defaults to False.

    Returns:
        list: the attr names that were deleted (or would have been deleted, if
            dry_run is True).
    """
    to_keep = [
        "multiscales",
        "cellmap",
        "omero",
        "bioformats2raw.layout",
        "labels",
        "well",
        "plate",
    ]
    to_delete_attrs = [attr for attr in list(zg.attrs) if attr not in to_keep]

    if dry_run:
        logger.info(f"[dry_run] would delete attrs {to_delete_attrs} from {zg.path}")
    else:
        for attr_name in to_delete_attrs:
            zg.attrs.__delitem__(attr_name)

    return to_delete_attrs

round_decimals #

round_decimals(group, decimals, dry_run=False)

Round scale and translation metadata

Parameters:

Name Type Description Default
group Group

zarr group with ome-zarr metadata

required
decimals int

number of decimals to round

required
dry_run bool

if True, compute the rounded metadata but do not write it back to the group's attrs. Defaults to False.

False

Returns:

Name Type Description
list list

the rounded 'multiscales' metadata that was written (or would have been written, if dry_run is True).

Source code in src/cellmap_utils/zarr/metadata.py
def round_decimals(group : zarr.Group, decimals : int, dry_run : bool = False) -> list:
    """Round scale and translation metadata

    Args:
        group (zarr.Group): zarr group with ome-zarr metadata
        decimals (int): number of decimals to round
        dry_run (bool, optional): if True, compute the rounded metadata but do not
            write it back to the group's attrs. Defaults to False.

    Returns:
        list: the rounded 'multiscales' metadata that was written (or would have
            been written, if dry_run is True).
    """
    import copy

    z_attrs = dict()
    # deep copy: avoids writing through to the store in dry_run mode
    z_attrs['multiscales'] = copy.deepcopy(group.attrs['multiscales'])

    # multiscale levels
    ms_levels = z_attrs['multiscales'][0]['datasets']
    for level in ms_levels:
        scale = level['coordinateTransformations'][0]['scale']
        translation = level['coordinateTransformations'][1]['translation']
        level['coordinateTransformations'][0]['scale'] = [round(sc, decimals) for sc in scale]
        level['coordinateTransformations'][1]['translation'] = [round(tr, decimals) for tr in translation]

    if dry_run:
        logger.info(f"[dry_run] would write rounded multiscales metadata to {group.path}: {z_attrs['multiscales']}")
    else:
        group.attrs['multiscales'] = z_attrs['multiscales']

    return z_attrs['multiscales']

get_s0_level #

get_s0_level(zg, strict=True)

Read the base (s0) level's scale and translation.

Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores). Requires the optional 'zarr3' extra (ome-zarr-models).

Parameters:

Name Type Description Default
zg Group

zarr group with OME-NGFF multiscale metadata.

required
strict bool

if True (default), require full OME-NGFF schema validation, and raise if it fails. If False, fall back to a best-effort, unvalidated read of the attrs when schema validation fails, instead of raising. Defaults to True.

True

Returns:

Type Description
Tuple[list[float], list[float]]

Tuple[list[float], list[float]]: (scale, translation) of the s0 level.

Source code in src/cellmap_utils/zarr/metadata.py
def get_s0_level(zg : zarr.Group, strict : bool = True) -> Tuple[list[float],list[float]]:
    """Read the base (s0) level's scale and translation.

    Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores).
    Requires the optional 'zarr3' extra (ome-zarr-models).

    Args:
        zg (zarr.Group): zarr group with OME-NGFF multiscale metadata.
        strict (bool, optional): if True (default), require full OME-NGFF schema
            validation, and raise if it fails. If False, fall back to a best-effort,
            unvalidated read of the attrs when schema validation fails, instead of
            raising. Defaults to True.

    Returns:
        Tuple[list[float], list[float]]: (scale, translation) of the s0 level.
    """
    if strict:
        datasets = _read_multiscale_datasets(zg)
    else:
        try:
            datasets = _read_multiscale_datasets(zg)
        except RuntimeError:
            datasets = _read_multiscale_datasets_raw(zg)
    return _scale_and_translation(datasets[0])

remove_checksum #

remove_checksum(path_to_arr, dry_run=False)

Remove checksum parameter from zarr array metadata, to make it compatible with tensorstore.

Parameters:

Name Type Description Default
path_to_arr str

path to Zarr array

required
dry_run bool

if True, compute the corrected metadata but do not write it back to the .zarray file. Defaults to False.

False

Returns:

Name Type Description
dict dict

the array metadata with the checksum removed (or that would have been written, if dry_run is True).

Source code in src/cellmap_utils/zarr/metadata.py
def remove_checksum(path_to_arr: str, dry_run: bool = False) -> dict:
    """Remove checksum parameter from zarr array metadata, to make it compatible with tensorstore.

    Args:
        path_to_arr (str): path to Zarr array
        dry_run (bool, optional): if True, compute the corrected metadata but do
            not write it back to the .zarray file. Defaults to False.

    Returns:
        dict: the array metadata with the checksum removed (or that would have
            been written, if dry_run is True).
    """
    import json

    try:
        path_to_zarray = os.path.join(path_to_arr, '.zarray')
        with open(path_to_zarray, 'r+') as f:
            data = json.load(f)
            logger.info(f'old array metadata: {data}')
            try:
                del data['compressor']['checksum']
            except KeyError:
                logger.warning('No checksum found in compressor metadata')

        if dry_run:
            logger.info(f'[dry_run] would write new array metadata to {path_to_zarray}: {data}')
            return data

        logger.info(f'new array metadata: {data}')
        with open(path_to_zarray, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=3)

        with open(path_to_zarray, 'r+') as f:
            data = json.load(f)

        return data

    except Exception as e:
        logger.error(f'FAILED to remove checksum in {path_to_zarray}: {e}')
        raise

access_parent #

access_parent(node)

Get the parent (zarr.Group) of an input zarr array(ds).

Parameters:

Name Type Description Default
node Array or Group

description

required

Raises:

Type Description
RuntimeError

returned if the node array is in the parent group,

Returns:

Type Description

zarr.hierarchy.Group : parent group that contains input group/array

Source code in src/cellmap_utils/zarr/node.py
def access_parent(node: zarr.Group | zarr.Array):
    """
    Get the parent (zarr.Group) of an input zarr array(ds).


    Args:
        node (zarr.core.Array or zarr.hierarchy.Group): _description_

    Raises:
        RuntimeError: returned if the node array is in the parent group,
        or the group itself is the root group

    Returns:
        zarr.hierarchy.Group : parent group that contains input group/array
    """

    store_path, node_path = separate_store_path(node.store.path, node.path)
    if node_path == "":
        raise RuntimeError(
            f"{node.name} is in the root group of the {node.store.path} store."
        )
    else:
        return zarr.open(store=store_path, path=os.path.split(node_path)[0], mode="a")

repair_zarr_branch #

repair_zarr_branch(input_zarr_path, dry_run=False)

A recursive methond that adds missing .zgroup file in any parent zarr group between input zarr group and root of the zarr container.

Parameters:

Name Type Description Default
input_zarr_path str

description

required
dry_run bool

if True, only report which paths are missing a .zgroup file, without writing anything. Defaults to False.

False

Returns:

Name Type Description
list list

paths that had a .zgroup file written (or that would have, if dry_run is True).

Source code in src/cellmap_utils/zarr/node.py
def repair_zarr_branch(input_zarr_path: str, dry_run: bool = False) -> list:
    """A recursive methond that adds missing .zgroup file in any parent zarr group
       between input zarr group and root of the zarr container.

    Args:
        input_zarr_path (str): _description_
        dry_run (bool, optional): if True, only report which paths are missing a
            .zgroup file, without writing anything. Defaults to False.

    Returns:
        list: paths that had a .zgroup file written (or that would have, if
            dry_run is True).
    """
    try:
        zarr_path = input_zarr_path.rstrip("/ ")  # remove unnecessary '/' and ' '
        fs = get_file_system(zarr_path)
        fs.exists(zarr_path)
    except:
        sys.exit("Path not found!")

    z_store, z_path = zarr_path.split(".zarr")

    repaired = []
    try:
        zarr.open_group(zarr_path, mode="r")
    except:
        if dry_run:
            print("[dry_run] not found, would add .zgroup to: ", zarr_path)
        else:
            print("not found, added .zgroup to: ", zarr_path)
            with fs.open(UPath(os.path.join(zarr_path, ".zgroup")), mode="w") as f:
                f.write(str({"zarr_format": 2}).replace("'", '"'))
        repaired.append(zarr_path)

    if z_path.lstrip("/ ").rstrip("/ ") != "":
        repaired.extend(
            repair_zarr_branch(
                os.path.join(f"{z_store}.zarr", os.path.split(z_path)[0].lstrip("/")),
                dry_run=dry_run,
            )
        )

    return repaired

get_matching_scale #

get_matching_scale(dataset, roi)

Find the roi pyramid level whose scale matches the dataset's s0 scale.

Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores). Requires the optional 'zarr3' extra (ome-zarr-models).

Parameters:

Name Type Description Default
dataset Group

dataset zarr group with a multiscale pyramid.

required
roi Group

roi zarr group with a multiscale pyramid.

required

Returns:

Type Description
Tuple[list[float], list[float]]

Tuple[list[float], list[float]]: (matching roi scale, matching roi translation)

Source code in src/cellmap_utils/zarr/roi.py
def get_matching_scale(dataset : zarr.Group,
                       roi : zarr.Group) -> Tuple[list[float], list[float]]:
    """Find the roi pyramid level whose scale matches the dataset's s0 scale.

    Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores).
    Requires the optional 'zarr3' extra (ome-zarr-models).

    Args:
        dataset (zarr.Group): dataset zarr group with a multiscale pyramid.
        roi (zarr.Group): roi zarr group with a multiscale pyramid.

    Returns:
        Tuple[list[float], list[float]]: (matching roi scale, matching roi translation)
    """
    ds_scale, _ = get_s0_level(dataset)

    for level in _read_multiscale_datasets(roi):
        scale, translation = _scale_and_translation(level)
        if scale == ds_scale:
            return (scale, translation)
    raise ValueError("Could not find ROI scale values that matches with s0 level of the dataset")

get_normalized_scale #

get_normalized_scale(reference, dataset, dataset_level='s0')

Correct a dataset's voxel size to match the reference pyramid level that has the same array shape.

This is useful when a dataset (for example, a segmentation) was labeled with the wrong voxel size, but its array shape matches one of the levels of a trusted reference multiscale image. The scale of the matching reference level is treated as the correct voxel size for the dataset. The dataset's translation (offset) is not changed, because translation already stores an absolute physical position in nanometers, and does not depend on the scale label.

Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores). Requires the optional 'zarr3' extra (ome-zarr-models).

Parameters:

Name Type Description Default
reference Group

reference zarr group with a multiscale pyramid.

required
dataset Group

dataset zarr group to normalize, for example a segmentation.

required
dataset_level str

path of the dataset level to normalize. Defaults to 's0'.

's0'

Returns:

Name Type Description
dict dict

{'scale': matched reference scale, 'translation': dataset's own translation, unchanged}

Source code in src/cellmap_utils/zarr/roi.py
def get_normalized_scale(reference : zarr.Group,
                          dataset : zarr.Group,
                          dataset_level : str = 's0') -> dict:
    """Correct a dataset's voxel size to match the reference pyramid level that has the same array shape.

    This is useful when a dataset (for example, a segmentation) was labeled with the wrong
    voxel size, but its array shape matches one of the levels of a trusted reference multiscale
    image. The scale of the matching reference level is treated as the correct voxel size for the
    dataset. The dataset's translation (offset) is not changed, because translation already stores
    an absolute physical position in nanometers, and does not depend on the scale label.

    Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores).
    Requires the optional 'zarr3' extra (ome-zarr-models).

    Args:
        reference (zarr.Group): reference zarr group with a multiscale pyramid.
        dataset (zarr.Group): dataset zarr group to normalize, for example a segmentation.
        dataset_level (str, optional): path of the dataset level to normalize. Defaults to 's0'.

    Returns:
        dict: {'scale': matched reference scale, 'translation': dataset's own translation, unchanged}
    """

    ds_levels = {level.path: level for level in _read_multiscale_datasets(dataset)}
    if dataset_level not in ds_levels:
        raise ValueError(f"Dataset does not have a level named '{dataset_level}'")
    _, ds_translation = _scale_and_translation(ds_levels[dataset_level])

    ds_shape = list(dataset[dataset_level].shape)

    for level in _read_multiscale_datasets(reference):
        ref_shape = list(reference[level.path].shape)
        if ref_shape == ds_shape:
            matched_scale, _ = _scale_and_translation(level)
            return {'scale': matched_scale, 'translation': ds_translation}

    raise ValueError(
        f"Could not find a reference level with shape matching dataset level '{dataset_level}' shape {ds_shape}"
    )

apply_normalized_scale #

apply_normalized_scale(dataset, dataset_level, normalized, dry_run=True)

Write a corrected scale into a dataset's OME-NGFF metadata.

Use this after get_normalized_scale() to save its result. Only the scale for dataset_level changes. The translation already in the dataset's metadata is not touched.

Supports both OME-NGFF 0.4 (top-level "multiscales" attrs key) and OME-NGFF 0.5 ("ome" -> "multiscales" attrs key).

Parameters:

Name Type Description Default
dataset Group

dataset zarr group to update.

required
dataset_level str

path of the level to update. Example: 's0'.

required
normalized dict

output of get_normalized_scale(). Must have a 'scale' key.

required
dry_run bool

if True (default), compute the updated metadata but do not write it to the group's attrs.

True

Returns:

Name Type Description
list list

the full 'multiscales' metadata that was written, or that would have been written if dry_run is True.

Raises:

Type Description
ValueError

if dataset_level is not present in the dataset's metadata.

Source code in src/cellmap_utils/zarr/roi.py
def apply_normalized_scale(
    dataset: zarr.Group,
    dataset_level: str,
    normalized: dict,
    dry_run: bool = True,
) -> list:
    """Write a corrected scale into a dataset's OME-NGFF metadata.

    Use this after get_normalized_scale() to save its result. Only the scale
    for dataset_level changes. The translation already in the dataset's
    metadata is not touched.

    Supports both OME-NGFF 0.4 (top-level "multiscales" attrs key) and
    OME-NGFF 0.5 ("ome" -> "multiscales" attrs key).

    Args:
        dataset (zarr.Group): dataset zarr group to update.
        dataset_level (str): path of the level to update. Example: 's0'.
        normalized (dict): output of get_normalized_scale(). Must have a
            'scale' key.
        dry_run (bool, optional): if True (default), compute the updated
            metadata but do not write it to the group's attrs.

    Returns:
        list: the full 'multiscales' metadata that was written, or that
            would have been written if dry_run is True.

    Raises:
        ValueError: if dataset_level is not present in the dataset's
            metadata.
    """
    attrs = dict(dataset.attrs)
    is_v05 = "ome" in attrs
    root = attrs["ome"] if is_v05 else attrs
    multiscales = copy.deepcopy(root["multiscales"])

    ms_levels = multiscales[0]["datasets"]
    level_entry = next((lvl for lvl in ms_levels if lvl["path"] == dataset_level), None)
    if level_entry is None:
        raise ValueError(f"Dataset does not have a level named '{dataset_level}'")

    scale_transform = next(
        t for t in level_entry["coordinateTransformations"] if t["type"] == "scale"
    )
    scale_transform["scale"] = list(normalized["scale"])

    if dry_run:
        logger.info(
            f"[dry_run] would set scale for level '{dataset_level}' of "
            f"{dataset.path} to {normalized['scale']}"
        )
    elif is_v05:
        new_ome = copy.deepcopy(attrs["ome"])
        new_ome["multiscales"] = multiscales
        dataset.attrs["ome"] = new_ome
    else:
        dataset.attrs["multiscales"] = multiscales

    return multiscales

recalibrate_offset #

recalibrate_offset(roi, grid_spacing)

The offset of the roi at multiscale level with scale=grid_spacing must be divisible by grid_spacing. This method would recalibrate offset, if roi grid does not align with grid {scale : grid_spacing, translation : [0.0, 0.0, 0.0]}

Parameters:

Name Type Description Default
roi Group

roi zarr group with multiscale pyramid

required
grid_spacing list[float]

grid spacing, with assumption that translation=[0.0, 0.0, 0.0]

required

Returns:

Type Description
Tuple[list[float], list[float]]

Tuple[list[float], list[float]]: returns (ROI s0 scale, recalibrated offset)

Source code in src/cellmap_utils/zarr/roi.py
def recalibrate_offset(roi: zarr.Group, grid_spacing : list[float]) -> Tuple[list[float], list[float]]:
    """The offset of the roi at multiscale level with scale=grid_spacing must be divisible by grid_spacing.
        This method would recalibrate offset, if roi grid does not align with grid {scale : grid_spacing, translation : [0.0, 0.0, 0.0]}  

    Args:
        roi (zarr.Group): roi zarr group with multiscale pyramid 
        grid_spacing (list[float]): grid spacing, with assumption that translation=[0.0, 0.0, 0.0]

    Returns:
        Tuple[list[float], list[float]]: returns (ROI s0 scale, recalibrated offset)
    """

    roi_scale, roi_offset = get_s0_level(roi)

    # calculate log2(roi s0 level/grid_spacing), for transforming it to the dataset 
    from math import log2
    roi_level = [log2(float(roi_sc)/float(ds_sc)) for roi_sc, ds_sc in zip(roi_scale, grid_spacing)] 

    # calculate roi translation as if it was rescaled to scale=grid_spacing
    roi_tr_at_grid_spacing = [
            tr_n - sc_n *(0.5 - pow(2, -(l_n+1)))
            for (sc_n, tr_n, l_n) in zip(roi_scale, roi_offset, roi_level)
        ]

    # shift roi offset to align with grid spacing  
    tr_roi_at_s0_correct = [round(float(tr)/float(sc))*sc for sc, tr in zip(grid_spacing, roi_tr_at_grid_spacing)]
    tr_roi_sn_correct = [
        round((sc * (pow(2, level - 1) - 0.5)) + tr, 2)
        for (sc, tr, level) in zip(grid_spacing, tr_roi_at_s0_correct, roi_level)
    ]

    return {'scale': roi_scale, 'translation' : tr_roi_sn_correct}

separate_store_path #

separate_store_path(store, path)

sometimes you can pass a total os path to node, leading to an empty('') node.path attribute. the correct way is to separate path to container(.n5, .zarr) from path to array within a container.

Parameters:

Name Type Description Default
store string

path to store

required
path string

path array/group (.n5 or .zarr)

required

Returns:

Type Description
(string, string)

returns regularized store and group/array path

Source code in src/cellmap_utils/zarr/store.py
def separate_store_path(store, path):
    """
    sometimes you can pass a total os path to node, leading to
    an empty('') node.path attribute.
    the correct way is to separate path to container(.n5, .zarr)
    from path to array within a container.

    Args:
        store (string): path to store
        path (string): path array/group (.n5 or .zarr)

    Returns:
        (string, string): returns regularized store and group/array path
    """
    new_store, path_prefix = os.path.split(store)
    if ".zarr" in path_prefix:
        return store, path
    return separate_store_path(new_store, os.path.join(path_prefix, path))

is_valid_ome #

is_valid_ome(zg, version=None)

Check whether a zarr group's OME-NGFF metadata validates, without raising.

Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores). Requires the optional 'zarr3' extra (ome-zarr-models).

Parameters:

Name Type Description Default
zg Group

the zarr group to check.

required
version str

restrict the check to a specific OME-NGFF version ('0.4', '0.5', or '0.6'). Defaults to None, which tries every version.

None

Returns:

Name Type Description
bool bool

True if the metadata validates against a known OME-NGFF model, False otherwise.

Source code in src/cellmap_utils/zarr/validate.py
def is_valid_ome(zg : zarr.Group, version : Optional[str] = None) -> bool:
    """Check whether a zarr group's OME-NGFF metadata validates, without raising.

    Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores).
    Requires the optional 'zarr3' extra (ome-zarr-models).

    Args:
        zg (zarr.Group): the zarr group to check.
        version (str, optional): restrict the check to a specific OME-NGFF version
            ('0.4', '0.5', or '0.6'). Defaults to None, which tries every version.

    Returns:
        bool: True if the metadata validates against a known OME-NGFF model, False otherwise.
    """
    from ome_zarr_models import open_ome_zarr

    try:
        open_ome_zarr(zg, version=version)
        return True
    except RuntimeError:
        return False

validate_ome #

validate_ome(zg)

Validate that a zarr group has well-formed OME-NGFF multiscale metadata.

Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores). Requires the optional 'zarr3' extra (ome-zarr-models). Raises if the metadata does not validate against any known OME-NGFF version. Use is_valid_ome() instead if you want a boolean result rather than an exception.

Parameters:

Name Type Description Default
zg Group

the input zarr group to validate.

required
Source code in src/cellmap_utils/zarr/validate.py
def validate_ome(zg : zarr.Group):
    """Validate that a zarr group has well-formed OME-NGFF multiscale metadata.

    Supports both OME-NGFF 0.4 (Zarr v2 stores) and OME-NGFF 0.5 (Zarr v3 stores).
    Requires the optional 'zarr3' extra (ome-zarr-models). Raises if the metadata
    does not validate against any known OME-NGFF version. Use `is_valid_ome()`
    instead if you want a boolean result rather than an exception.

    Args:
        zg (zarr.Group): the input zarr group to validate.
    """
    from ome_zarr_models import open_ome_zarr

    open_ome_zarr(zg)