HCS OME-Zarr analysis utilities for czitools analysis tools.
Provides :func:process_hcs_omezarr to count objects per well in an HCS OME-Zarr plate.
Vendored (with light edits) from czi_omezarr_utils.processing in the omezarr_playground repository.
These features require optional dependencies. Install them with::
pip install "czitools[analysis]"
Functions:
Process an HCS OME-ZARR file to count objects per well.
Iterates over every well and field in the plate, applies Otsu thresholding followed by connected-component labelling, and returns the total object count per well. Currently only 2D images (squeezed from the channel dimension) are supported.
Parameters:
- (
str) – Path to the HCS OME-ZARR file or directory.
- (
int, default: 0 ) – Index of the channel to analyse (default: 0).
- (
tuple[str, ...], default: ('label', 'area', 'centroid', 'bbox') ) – regionprops properties to measure (default: label/area/centroid/bbox).
Returns:
-
dict[str, int] – dict[str, int]: Dictionary mapping well path strings (e.g. "B/4") to total object counts.
Raises:
-
Exception – Re-raises any validation error from nz.from_hcs_zarr.
Source code in czitools/analysis_tools/hcs_analysis.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 | def process_hcs_omezarr(
hcs_omezarr_path: str,
channel2analyze: int = 0,
measure_properties: tuple[str, ...] = ("label", "area", "centroid", "bbox"),
) -> dict[str, int]:
"""Process an HCS OME-ZARR file to count objects per well.
Iterates over every well and field in the plate, applies Otsu thresholding
followed by connected-component labelling, and returns the total object count
per well. Currently only 2D images (squeezed from the channel dimension) are
supported.
Args:
hcs_omezarr_path (str): Path to the HCS OME-ZARR file or directory.
channel2analyze (int): Index of the channel to analyse (default: 0).
measure_properties (tuple[str, ...]): regionprops properties to measure
(default: label/area/centroid/bbox).
Returns:
dict[str, int]: Dictionary mapping well path strings (e.g. ``"B/4"``) to
total object counts.
Raises:
Exception: Re-raises any validation error from ``nz.from_hcs_zarr``.
"""
try:
logger.info("Validating HCS-ZARR file against schema...")
hcs_plate = nz.from_hcs_zarr(hcs_omezarr_path, validate=True)
logger.info("Validation successful.")
except Exception as e:
logger.error(f"Validation failed: {e}")
raise
results_obj: dict[str, int] = {}
results_mean: dict[str, float] = {}
logger.info(f"Number of wells in metadata: {len(hcs_plate.metadata.wells)}")
logger.info(f"Wells in metadata: {[w.path for w in hcs_plate.metadata.wells]}")
for well_meta in hcs_plate.metadata.wells:
row, col = well_meta.path.split("/")
logger.info(f"\nProcessing well: {well_meta.path} (Row: {row}, Column: {col})")
well = hcs_plate.get_well(row, col)
if not well:
logger.warning(f" Well {well_meta.path} not found in plate, skipping")
continue
if not well.images or len(well.images) == 0:
logger.warning(f" Well {well_meta.path} has no images, skipping")
continue
field_intensities: list = []
field_num_objects: list = []
logger.info(f" Found {len(well.images)} field(s) in well {well_meta.path}")
for field_idx in range(len(well.images)):
image = well.get_image(field_idx)
if image:
data = image.images[0].data.compute()
logger.info(f" Processing field {field_idx}: shape={data.shape}, dtype={data.dtype}")
ap = ArrayProcessor(np.squeeze(data[:, channel2analyze, ...]))
pro2d = ap.apply_otsu_threshold()
ap = ArrayProcessor(pro2d)
pro2d, num_objects, _props = ap.label_objects(
min_size=100,
label_rgb=False,
orig_image=None,
bg_label=0,
measure_params=True,
measure_properties=measure_properties,
)
field_num_objects.append(int(num_objects))
field_intensities.append(float(np.mean(data)))
results_mean[f"{row}/{col}"] = float(np.mean(field_intensities)) if field_intensities else 0.0
results_obj[f"{row}/{col}"] = int(np.sum(field_num_objects))
logger.info(f"Total wells processed: {len(results_mean)}")
return results_obj
|