Images/Labels/Tables¶
In this notebook we will show how to use the Image
, Label
and Table
objects to do image processing.
import matplotlib.pyplot as plt
from ngio.core.ngff_image import NgffImage
ngff_image = NgffImage("../../data/20200812-CardiomyocyteDifferentiation14-Cycle1_mip.zarr/B/03/0")
Images¶
Images can be loaded from a NgffImage
object.
image = ngff_image.get_image()
print("Image information:")
print(f"{image.shape=}")
print(f"{image.axes_names=}")
print(f"{image.pixel_size=}")
print(f"{image.channel_labels=}")
print(f"{image.dimensions=}")
Image information: image.shape=(3, 1, 4320, 5120) image.axes_names=['c', 'z', 'y', 'x'] image.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=<SpaceUnits.micrometer: 'micrometer'>, virtual=False) image.channel_labels=['DAPI', 'nanog', 'Lamin B1'] image.dimensions=Dimensions(c=3, z=1, y=4320, x=5120)
Data Loading¶
The Image
object created is a lazy object, meaning that the image is not loaded into memory until it is needed.
To get the image data from disk we can use the .array
attribute or we can get it as a dask.array
object using the .dask_array
attribute.
# Get image as a dask array
dask_array = image.on_disk_dask_array
dask_array
|
Note, directly accessing the .on_disk_array
or .on_disk_dask_array
attributes will load the image as stored in the file.
Since in principle the images can have different axes order. A safer way to access the image data is to use the .get_array()
method, which will return the image data in canonical order (TCZYX).
image_numpy = image.get_array(c=0, x=slice(0, 250), y=slice(0, 250), preserve_dimensions=False, mode="numpy")
print(f"{image_numpy.shape=}")
image_numpy.shape=(1, 250, 250)
RoiTable/Image Interaction¶
roi
objects from a roi_table
can be used to extract a region of interest from an image or a label.
roi_table = ngff_image.tables.get_table("FOV_ROI_table")
roi = roi_table.get_roi("FOV_1")
print(f"{roi=}")
image_roi_1 = image.get_array_from_roi(roi=roi, c=0, preserve_dimensions=True, mode="dask")
image_roi_1
roi=WorldCooROI(x_length=416.0, y_length=351.0, z_length=1.0, x=0.0, y=0.0, z=0.0)
|
The roi object can is defined in physical coordinates, and can be used to extract the region of interest from the image or label at any resolution.
image_2 = ngff_image.get_image(path="2")
# Two images at different resolutions
print(f"{image.pixel_size=}")
print(f"{image_2.pixel_size=}")
# Get roi for higher resolution image
image_1_roi_1 = image.get_array_from_roi(roi=roi, c=0, preserve_dimensions=False)
# Get roi for lower resolution image
image_2_roi_1 = image_2.get_array_from_roi(roi=roi, c=0, preserve_dimensions=False)
# Plot the two images side by side
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
axs[0].imshow(image_1_roi_1[0], cmap="gray")
axs[1].imshow(image_2_roi_1[0], cmap="gray")
plt.show()
image.pixel_size=PixelSize(x=0.1625, y=0.1625, z=1.0, unit=<SpaceUnits.micrometer: 'micrometer'>, virtual=False) image_2.pixel_size=PixelSize(x=0.65, y=0.65, z=1.0, unit=<SpaceUnits.micrometer: 'micrometer'>, virtual=False)
Writing Images¶
Similarly to the .array()
we can use the .set_array()
(or set_array_from_roi
) method to write part of an image to disk.
import numpy as np
# Get a small slice of the image
small_slice = image.get_array(x=slice(1000, 2000), y=slice(1000, 2000))
# Set the sample slice to zeros
zeros_slice = np.zeros_like(small_slice)
image.set_array(patch=zeros_slice, x=slice(1000, 2000), y=slice(1000, 2000))
# Load the image from disk and show the edited image
nuclei = ngff_image.labels.get_label("nuclei")
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
axs[0].imshow(image.on_disk_array[0, 0], cmap="gray")
axs[1].imshow(nuclei.on_disk_array[0])
for ax in axs:
ax.axis("off")
plt.tight_layout()
plt.show()
# Add back the original slice to the image
image.set_array(patch=small_slice, x=slice(1000, 2000), y=slice(1000, 2000))
Deriving a new label¶
When doing image analysis, we often need to create new labels or tables. The ngff_image
allows us to simply create new labels and tables.
# Create a a new label object and set it to a simple segmentation
new_label = ngff_image.labels.derive("new_label", overwrite=True)
simple_segmentation = image.on_disk_array[0] > 100
new_label.on_disk_array[...] = simple_segmentation
# make a subplot with two image show side by side
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
axs[0].imshow(image.on_disk_array[0, 0], cmap="gray")
axs[1].imshow(new_label.on_disk_array[0], cmap="gray")
for ax in axs:
ax.axis("off")
plt.tight_layout()
plt.show()
Image Consolidation¶
Every time we modify a label or a image, we are modifying the on-disk data on one layer only.
This means that if we have the image saved in multiple resolutions, we need to consolidate the changes to all resolutions.
To do so, we can use the .consolidate()
method.
label_0 = ngff_image.labels.get_label("new_label", path="0")
label_2 = ngff_image.labels.get_label("new_label", path="2")
label_before_consolidation = label_2.on_disk_array[...]
# Consolidate the label
label_0.consolidate()
label_after_consolidation = label_2.on_disk_array[...]
# make a subplot with two image show side by side
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
axs[0].imshow(label_before_consolidation[0], cmap="gray")
axs[1].imshow(label_after_consolidation[0], cmap="gray")
for ax in axs:
ax.axis("off")
plt.tight_layout()
plt.show()
Creating a new table¶
We can simply create a new table by create a new Table
object from a pandas dataframe.
For a simple feature table the only reuiremnt is to have a integer column named label
that will be used to link the table to the objects in the image.
import numpy as np
import pandas as pd
print(f"List of feature table: {ngff_image.tables.list(table_type='feature_table')}")
nuclei = ngff_image.labels.get_label('nuclei')
# Create a table with random features for each nuclei in each ROI
list_of_records = []
for roi in roi_table.rois:
nuclei_in_roi = nuclei.get_array_from_roi(roi, mode='numpy')
for nuclei_id in np.unique(nuclei_in_roi)[1:]:
list_of_records.append(
{"label": nuclei_id,
"feat1": np.random.rand(),
"feat2": np.random.rand(),
"ROI": roi.infos.get("FieldIndex")}
)
feat_df = pd.DataFrame.from_records(list_of_records)
# Create a new feature table
feat_table = ngff_image.tables.new(name='new_feature_table',
label_image='../nuclei',
table_type='feature_table',
overwrite=True)
print(f"New list of feature table: {ngff_image.tables.list(table_type='feature_table')}")
feat_table.set_table(feat_df)
feat_table.consolidate()
feat_table.table
List of feature table: ['regionprops_DAPI', 'nuclei_measurements_wf3', 'nuclei_measurements_wf4', 'nuclei_lamin_measurements_wf4'] New list of feature table: ['regionprops_DAPI', 'nuclei_measurements_wf3', 'nuclei_measurements_wf4', 'nuclei_lamin_measurements_wf4', 'new_feature_table']
/opt/hostedtoolcache/Python/3.13.1/x64/lib/python3.13/site-packages/anndata/_core/anndata.py:1756: UserWarning: Observation names are not unique. To make them unique, call `.obs_names_make_unique`. utils.warn_names_duplicates("obs")
feat1 | feat2 | ROI | |
---|---|---|---|
label | |||
1 | 0.331763 | 0.828369 | FOV_1 |
2 | 0.383828 | 0.803842 | FOV_1 |
3 | 0.051203 | 0.545570 | FOV_1 |
4 | 0.049017 | 0.633649 | FOV_1 |
5 | 0.960582 | 0.241663 | FOV_1 |
... | ... | ... | ... |
2987 | 0.161796 | 0.682795 | FOV_4 |
2991 | 0.967702 | 0.347997 | FOV_4 |
2993 | 0.025032 | 0.210568 | FOV_4 |
2995 | 0.814387 | 0.076944 | FOV_4 |
2996 | 0.592485 | 0.757603 | FOV_4 |
3091 rows × 3 columns