2. Images and labels¶
Read and write the pixel data.
An Image gives you the data of one resolution level: as numpy or dask, sliced
by axis or by a region of interest in world coordinates. A Label is a
segmentation stored the same way, and behaves the same way.
Images¶
To start working with the image data, instantiate an Image object.
ngio provides a high-level API to access the image data at different resolution levels and pixel sizes.
Getting an image¶
By default, the get_image method returns the highest resolution image:
To get a specific pyramid level, you can use the path parameter:
If you want to get an image with a specific pixel size, you can use the pixel_size parameter:
from ngio import PixelSize
pixel_size = PixelSize(x=0.65, y=0.65, z=1.0)
image = ome_zarr_container.get_image(pixel_size=pixel_size)
print(image)
ngio returns the level whose pixel size is nearest to the one you ask for. That is the default, strict=False, spelled out here:
from ngio import PixelSize
pixel_size = PixelSize(x=0.60, y=0.60, z=1.0)
image = ome_zarr_container.get_image(pixel_size=pixel_size, strict=False)
print(image)
strict=True instead to require an exact match, and raise NgioValueError when no level has that pixel size. The module-level open_image and open_label functions default the other way, to strict=True.
Similarly to the OME-Zarr container, the Image object provides a high-level API to access the image metadata.
dimensions attribute returns an object with the image dimensions for each axis.
pixel_size attribute returns the pixel size for each axis.
Working with image data¶
Once you have the Image object, you can access the image data as a:
get_array is the generic form of the two above: one entry point that picks the backend from a mode argument. Reach for it when the backend is decided at runtime; otherwise prefer the explicit get_as_numpy / get_as_dask.
# One entry point for both, selected with mode="numpy" or mode="dask"
data = image.get_array(mode="numpy")
print(data.shape, data.dtype)
The get_as_* methods can also slice the image data, and return the axes in an order you choose:
# Get a specific channel and axes order
image_slice = image.get_as_numpy(
channel_selection="DAPI",
x=slice(0, 128),
axes_order=["t", "z", "y", "x", "c"],
)
print(image_slice.shape)
To write pixel data back, use the set_array method:
It accepts a numpy array or a dask array, and takes the same slicing and axes_order
arguments as the getters, so you can write back exactly the region you read.
A minimal read-modify-write example:
import numpy as np
def process(patch: np.ndarray) -> np.ndarray:
"""Placeholder for your own processing step.
Replace the body with the operation you want to apply to the patch.
"""
return patch
# Get the image data as a numpy array
data = image.get_as_numpy(
channel_selection="DAPI",
x=slice(0, 128),
y=slice(0, 128),
axes_order=["z", "y", "x", "c"],
)
# Modify the image data
data = process(data)
# Set the modified image data
image.set_array(
data,
channel_selection="DAPI",
x=slice(0, 128),
y=slice(0, 128),
axes_order=["z", "y", "x", "c"],
)
# Consolidate the changes to all resolution levels, see below for more details
image.consolidate()
Important
set_array writes to one resolution level only. Once you have finished editing, consolidate the changes so the rest of the pyramid is rebuilt from it:
World coordinates slicing¶
To read or write a specific region of the image defined in world coordinates, you can use the Roi object.
from ngio import Roi
# Define a ROI in world coordinates
roi = Roi.from_values(slices={"x": (34.1, 321.6), "y": (10, 330)}, name=None)
# Get the image data in the ROI as a numpy array
print(image.get_roi_as_numpy(roi).shape)
The ROI is defined in micrometres, so it names the same region whatever pyramid level you read it from — on the left it is outlined on the whole image, on the right it is the region that came back:
Labels¶
A label is a segmentation mask that identifies objects in the image. In ngio a Label
behaves like an Image, and is accessed and manipulated the same way.
Getting a label¶
See which labels are available in the image:
Here is how to reach one of them:
By default, the get_label method returns the highest resolution label:
To get a specific pyramid level, you can use the path parameter:
If you want to get a label with a specific pixel size, you can use the pixel_size parameter:
from ngio import PixelSize
pixel_size = PixelSize(x=0.65, y=0.65, z=1.0)
label_nuclei = ome_zarr_container.get_label("nuclei", pixel_size=pixel_size)
print(label_nuclei)
As with images, the nearest level wins unless you ask for an exact match with strict=True:
from ngio import PixelSize
pixel_size = PixelSize(x=0.60, y=0.60, z=1.0)
label_nuclei = ome_zarr_container.get_label(
"nuclei", pixel_size=pixel_size, strict=False
)
print(label_nuclei)
Each object in a label carries its own id, drawn here in its own colour over the channel it was segmented from:
Working with label data¶
Reading and writing label data works exactly as it does for images: get_as_numpy, get_as_dask, get_roi_as_numpy and set_array are all available on a Label.
Deriving a label¶
Often, you might want to create a new label based on an existing image. You can do this using the derive_label method:
# Derive a new label
new_label = ome_zarr_container.derive_label("new_label", overwrite=True)
print(new_label)
This will create a new label with the same dimensions as the original image (without channels) and compatible metadata. If you want to create a new label with slightly different metadata see the images API reference.
Next steps¶
- Tables — use ROIs to slice the image data you just learned to read.
- Masked images and labels — work object-by-object using a segmentation.
- Images API reference — every method on
ImageandLabel.