Feature extraction¶
Measure per-label features and store them as a table.
Measure regionprops features from a segmented image with ngio and skimage, and write
them back as a feature table in the OME-Zarr container. By the end the container holds a
table with one row per label, ready to be read back or aggregated across a plate.
Step 1: write the measurement function¶
Start with the function that does the measuring — here a thin wrapper around
skimage.measure.regionprops_table, taking one image patch and one label patch.
import numpy as np
import pandas as pd
from skimage import measure
def extract_features(image: np.ndarray, label: np.ndarray) -> pd.DataFrame:
"""Basic feature extraction using skimage.measure.regionprops_table."""
label = label.squeeze(-1) # Remove the channel axis if present
roi_feat_table = measure.regionprops_table(
label_image=label,
intensity_image=image,
properties=[
"label",
"area",
"mean_intensity",
"max_intensity",
"min_intensity",
],
)
return pd.DataFrame(roi_feat_table)
Step 2: open the OME-Zarr container¶
from pathlib import Path
from ngio import open_ome_zarr_container
from ngio.utils import download_ome_zarr_dataset
# Download the dataset
download_dir = Path("./data").absolute()
hcs_path = download_ome_zarr_dataset("CardiomyocyteTinyMip", download_dir=download_dir)
image_path = hcs_path / "B" / "03" / "0"
# Open the OME-Zarr container
ome_zarr = open_ome_zarr_container(image_path)
Step 3: set up the inputs¶
from ngio.transforms import ZoomTransform
# Take the image to measure
image = ome_zarr.get_image()
# Get the nuclei label
nuclei = ome_zarr.get_label("nuclei")
# Here the image is stored at a higher resolution than the nuclei label
print(f"Image dimensions: {image.dimensions}, pixel size: {image.pixel_size}")
print(f"Nuclei dimensions: {nuclei.dimensions}, pixel size: {nuclei.pixel_size}")
# So resample the label up to the image resolution with a transform
zoom_transform = ZoomTransform(
input_image=nuclei,
target_image=image,
order="nearest", # Nearest-neighbour interpolation, so label ids stay intact
)
Image dimensions: Dimensions(c: 1, z: 1, y: 2160, x: 5120), pixel size: x=0.1625 y=0.1625 z=1.0 t=1.0 space_unit='micrometer' time_unit=None
Nuclei dimensions: Dimensions(z: 1, y: 540, x: 1280), pixel size: x=0.65 y=0.65 z=1.0 t=1.0 space_unit='micrometer' time_unit=None
Step 4: use the FeatureExtractorIterator to create a feature table¶
from ngio.iterators import FeatureExtractorIterator
from ngio.tables import FeatureTable
iterator = FeatureExtractorIterator(
input_image=image,
input_label=nuclei,
label_transforms=[zoom_transform],
axes_order=["y", "x", "c"],
)
feat_table = []
for image_data, label_data, roi in iterator.iter_as_numpy():
print(f"Processing ROI: {roi}")
roi_feat_table = extract_features(image=image_data, label=label_data)
feat_table.append(roi_feat_table)
# Concatenate the per-region frames into one table
feat_table = pd.concat(feat_table)
feat_table = FeatureTable(table_data=feat_table, reference_label="nuclei")
ome_zarr.add_table("nuclei_regionprops", feat_table, overwrite=True)
Processing ROI: name=None slices=[z: 0.0->1.0, y: 0.0->351.0, x: 0.0->832.0] label=None space='world'
Sanity check: read the table back¶
| label | area | mean_intensity-0 | max_intensity-0 | min_intensity-0 |
|---|---|---|---|---|
| 1 | 1360.00 | 184.58 | 268.00 | 125.00 |
| 2 | 2464.00 | 273.25 | 461.00 | 132.00 |
| 3 | 1968.00 | 277.29 | 429.00 | 143.00 |
| 4 | 5120.00 | 279.04 | 413.00 | 118.00 |
| 5 | 288.00 | 243.32 | 341.00 | 147.00 |
Next steps¶
- HCS exploration — aggregate feature tables across a plate.
- Table specifications — how feature tables are stored.