Skip to content

chromatin_feats

Extracts chromatin features from the HE image and instance segmentation mask.

Note

This function extracts features related to the chromatin distribution within nuclei. These features include the total pixel area occupied by chromatin clumps within each nucleus, proportion of chromatin area to total nucleus area, number of distinct connected components (clumps) of chromatin within each nucleus, and proportion of chromatin that intersects with nucleus boundary.

Parameters:

Name Type Description Default
img ndarray

Image to extract chromatin clumps from. Shape (H, W, 3).

required
label ndarray

Label map of the cells/nuclei. Shape (H, W).

required
metrics Tuple[str, ...]

Metrics to compute. Options are:

- "chrom_area"
- "chrom_nuc_prop"
- "n_chrom_clumps"
- "chrom_boundary_prop"
- "manders_coloc_coeff"
('chrom_area', 'chrom_nuc_prop')
mean float

Mean intensity of the image.

0
std float

Standard deviation of the image.

1
erode bool

Whether to apply erosion to the chromatin clumps.

False
mask ndarray

Optional binary mask to apply to the image to restrict the region of interest. Shape (H, W).

None
device str

Device to use for computation. "cpu" or "cuda".

'cpu'

Raises:

Type Description
ValueError

If the shape of img and label do not match.

Returns:

Type Description
DataFrame

pd.DataFrame: A DataFrame containing the extracted chromatin features.

Examples:

>>> from histolytics.data import hgsc_cancer_he, hgsc_cancer_nuclei
>>> from histolytics.utils.raster import gdf2inst
>>> from histolytics.nuc_feats.chromatin import chromatin_feats
>>> import matplotlib.pyplot as plt
>>>
>>> # Load example data
>>> he_image = hgsc_cancer_he()
>>> nuclei = hgsc_cancer_nuclei()
>>>
>>> # Filter for a specific cell type if needed
>>> neoplastic_nuclei = nuclei[nuclei["class_name"] == "neoplastic"]
>>>
>>> # Convert nuclei GeoDataFrame to instance segmentation mask
>>> inst_mask = gdf2inst(neoplastic_nuclei, width=he_image.shape[1], height=he_image.shape[0])
>>> # Extract chromatin clumps
>>>
>>> metrics = ("chrom_area", "chrom_nuc_prop", "n_chrom_clumps", "chrom_boundary_prop")
>>> chrom_feats = chromatin_feats(he_image, inst_mask, metrics=metrics)
>>>
>>> print(chrom_feats.head(3))
        chrom_area  chrom_nuc_prop  n_chrom_clumps  chrom_boundary_prop
292         155        0.210027             3.0             0.163043
316         421        0.990588             1.0             0.625641
340         334        0.582897             2.0             0.527027
Source code in src/histolytics/nuc_feats/chromatin.py
def chromatin_feats(
    img: np.ndarray,
    label: np.ndarray,
    metrics: Tuple[str, ...] = ("chrom_area", "chrom_nuc_prop"),
    mean: float = 0,
    std: float = 1,
    erode: bool = False,
    mask: np.ndarray = None,
    device: str = "cpu",
) -> pd.DataFrame:
    """Extracts chromatin features from the HE image and instance segmentation mask.

    Note:
        This function extracts features related to the chromatin distribution within
        nuclei. These features include the total pixel area occupied by chromatin clumps within
        each nucleus, proportion of chromatin area to total nucleus area, number of distinct
        connected components (clumps) of chromatin within each nucleus, and proportion
        of chromatin that intersects with nucleus boundary.

    Parameters:
        img (np.ndarray):
            Image to extract chromatin clumps from. Shape (H, W, 3).
        label (np.ndarray):
            Label map of the cells/nuclei. Shape (H, W).
        metrics (Tuple[str, ...]):
            Metrics to compute. Options are:

                - "chrom_area"
                - "chrom_nuc_prop"
                - "n_chrom_clumps"
                - "chrom_boundary_prop"
                - "manders_coloc_coeff"
        mean (float):
            Mean intensity of the image.
        std (float):
            Standard deviation of the image.
        erode (bool):
            Whether to apply erosion to the chromatin clumps.
        mask (np.ndarray):
            Optional binary mask to apply to the image to restrict the region of interest.
            Shape (H, W).
        device (str):
            Device to use for computation. "cpu" or "cuda".

    Raises:
        ValueError: If the shape of `img` and `label` do not match.

    Returns:
        pd.DataFrame: A DataFrame containing the extracted chromatin features.

    Examples:
        >>> from histolytics.data import hgsc_cancer_he, hgsc_cancer_nuclei
        >>> from histolytics.utils.raster import gdf2inst
        >>> from histolytics.nuc_feats.chromatin import chromatin_feats
        >>> import matplotlib.pyplot as plt
        >>>
        >>> # Load example data
        >>> he_image = hgsc_cancer_he()
        >>> nuclei = hgsc_cancer_nuclei()
        >>>
        >>> # Filter for a specific cell type if needed
        >>> neoplastic_nuclei = nuclei[nuclei["class_name"] == "neoplastic"]
        >>>
        >>> # Convert nuclei GeoDataFrame to instance segmentation mask
        >>> inst_mask = gdf2inst(neoplastic_nuclei, width=he_image.shape[1], height=he_image.shape[0])
        >>> # Extract chromatin clumps
        >>>
        >>> metrics = ("chrom_area", "chrom_nuc_prop", "n_chrom_clumps", "chrom_boundary_prop")
        >>> chrom_feats = chromatin_feats(he_image, inst_mask, metrics=metrics)
        >>>
        >>> print(chrom_feats.head(3))
                chrom_area  chrom_nuc_prop  n_chrom_clumps  chrom_boundary_prop
        292         155        0.210027             3.0             0.163043
        316         421        0.990588             1.0             0.625641
        340         334        0.582897             2.0             0.527027
    """
    allowed = (
        "chrom_area",
        "chrom_nuc_prop",
        "n_chrom_clumps",
        "chrom_boundary_prop",
        "manders_coloc_coeff",
    )
    if not all(m in allowed for m in metrics):
        raise ValueError(f"Invalid metrics: {metrics}. Allowed: {allowed}")

    if device == "cuda" and not _has_cp:
        raise RuntimeError(
            "CuPy and cucim are required for GPU acceleration (device='cuda'). "
            "Please install them with:\n"
            "  pip install cupy cucim\n"
            "or set device='cpu'."
        )

    chrom_clumps = extract_chromatin_clumps(img, label, mask, mean, std, device=device)

    if chrom_clumps is None or np.max(chrom_clumps) == 0:
        return pd.DataFrame([], columns=metrics)

    # Apply erosion if requested (cpu side due to some cupy bug)
    if erode:
        chrom_clumps = erosion(chrom_clumps, disk(2))

    if _has_cp and device == "cuda":
        return _chrom_feats_cp(img, chrom_clumps, label, metrics)
    else:
        return _chrom_feats_np(img, chrom_clumps, label, metrics)