Adding labels¶
import numpy as np
from ome_zarr_io import Writer
image = np.random.randint(0, 255, size=(512, 512), dtype=np.uint8)
label_mask = np.zeros_like(image, dtype=np.uint8)
label_mask[100:200, 100:200] = 1
writer = Writer(
path="example_labels.ome.zarr",
image=image,
dims=["y", "x"],
axis_units={"y": "micrometer", "x": "micrometer"},
downscale_levels=2,
overwrite=True,
)
writer.write()
writer.add_labels(
name="cell_space_segmentation",
array=label_mask,
colors=[
{"label-value": 0, "rgba": [0, 0, 128, 128]},
{"label-value": 1, "rgba": [0, 128, 0, 128]},
],
properties=[
{"label-value": 0, "class": "intercellular space"},
{"label-value": 1, "class": "cell"},
],
)
This creates a nested labels/cell_space_segmentation group under the image and writes NGFF 0.5 label metadata in the parent ome.labels list and the label group’s ome.image-label block. See OME-Zarr 0.5 spec for more details.
A label always gets the same number of resolution levels as the image on disk, as the spec requires. The
downscale_levels argument of add_labels is deprecated.
Label arrays must have an integer data type (uint8, int8, uint16, int16, uint32, int32, uint64 or
int64). Float and boolean arrays are rejected; convert a boolean mask with mask.astype("uint8"). validate()
reports a label with another data type, or with a different number of levels than its image, as invalid.
Adding labels to an existing image¶
Writer.from_existing attaches a Writer to an OME-Zarr image you did not just write, so you can add labels to it
without re-supplying its dimensions, pixel sizes or pyramid settings. For example, to add a second label image to the
file written above:
import numpy as np
from ome_zarr_io import Writer
nuclei_mask = np.zeros((512, 512), dtype=np.uint8) # same shape as the image
nuclei_mask[150:250, 150:250] = 1
writer = Writer.from_existing("example_labels.ome.zarr") # or a plate field: "screen.ome.zarr/A/1/0"
writer.add_labels("nuclei", nuclei_mask)
Adding a label image under a name that already exists raises an error, so re-running a script does not replace
anything by accident. To replace it, pass overwrite=True to add_labels, not to from_existing, which never
changes the image itself:
writer = Writer.from_existing("example_labels.ome.zarr")
writer.add_labels("nuclei", nuclei_mask, overwrite=True) # replaces the existing "nuclei" label image
Everything add_labels needs is read from the image’s metadata (axes, shape, pixel sizes, number of levels and
downscale factor); no pixel data is read, and the image is not modified. write() is disabled on the returned
Writer, so the image cannot be overwritten by accident.
The image’s pyramid must be one this library could have written: only Y and X downscaled, by a constant integer
factor, with no translation or multiscale-level coordinateTransformations. Otherwise from_existing raises a
ValueError explaining what differs, because labels written for it would not line up with the image. Pass a field
of a plate (.../A/1/0), not the plate itself.
One Writer is attached to one image group, so to label several images (the fields of a plate, or the numbered
images of a bioformats2raw collection) call from_existing on each image group in turn. An image group that holds
several entries in its multiscales list is rejected: labels are stored once per group, and the specification does
not say which of the entries they belong to.