Skip to content

rgb_intensity_feats

Computes rgb-intensity features of labeled objects in img.

Parameters:

Name Type Description Default
img ndarray

Image to compute properties from. Shape (H, W, 3).

required
label ndarray

Label image. Shape (H, W).

required
metrics Tuple[str, ...]

Metrics to compute for each object. Options are:

- "max"
- "min"
- "mean"
- "median"
- "std"
- "quantiles"
- "meanmediandiff"
- "mad"
- "iqr"
- "skewness"
- "kurtosis"
- "histenergy"
- "histentropy"
('mean', 'std', 'quantiles')
quantiles Tuple[float, ...]

Quantiles to compute for each object. Ignored if metrics does not include "quantiles".

(0.25, 0.5, 0.75)
n_bins int

Number of bins to use for histogram-based features. Ignored if metrics does not include "histenergy" or "histentropy".

32
hist_range Tuple[float, float]

Range of pixel values to use for histogram-based features. Ignored if metrics does not include "histenergy" or "histentropy".

None
mask ndarray

Optional binary mask to apply to the image to restrict the region of interest. Shape (H, W). For example, it can be used to mask out tissues that are not of interest.

None
device str

Device to use for computation. Options are 'cpu' or 'cuda'. If set to 'cuda', CuPy and cucim will be used for GPU acceleration.

'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 computed features for each RGB-channel for each object.

Examples:

>>> from histolytics.data import hgsc_cancer_he, hgsc_cancer_nuclei
>>> from histolytics.utils.raster import gdf2inst
>>> from histolytics.nuc_feats.intensity import rgb_intensity_feats
>>>
>>> he_image = hgsc_cancer_he()
>>> nuclei = hgsc_cancer_nuclei()
>>> neoplastic_nuclei = nuclei[nuclei["class_name"] == "neoplastic"]
>>> inst_mask = gdf2inst(
...     neoplastic_nuclei, width=he_image.shape[1], height=he_image.shape[0]
... )
>>> # Extract RGB intensity features from the neoplastic nuclei
>>> feats = rgb_intensity_feats(he_image, inst_mask)
>>> print(feats.iloc[..., 0:3].head(3))
            R_mean     R_std  R_quantile_0.25
    292    0.390361  0.071453         0.349138
    316    0.279746  0.032215         0.254310
    340    0.319236  0.071267         0.267241
Source code in src/histolytics/nuc_feats/intensity.py
def rgb_intensity_feats(
    img: np.ndarray,
    label: np.ndarray,
    metrics: Tuple[str, ...] = ("mean", "std", "quantiles"),
    quantiles: Tuple[float, ...] = (0.25, 0.5, 0.75),
    n_bins: int = 32,
    hist_range: Tuple[float, float] = None,
    mask: np.ndarray = None,
    device: str = "cpu",
) -> pd.DataFrame:
    """Computes rgb-intensity features of labeled objects in `img`.

    Parameters:
        img (np.ndarray):
            Image to compute properties from. Shape (H, W, 3).
        label (np.ndarray):
            Label image. Shape (H, W).
        metrics (Tuple[str, ...]):
            Metrics to compute for each object. Options are:

                - "max"
                - "min"
                - "mean"
                - "median"
                - "std"
                - "quantiles"
                - "meanmediandiff"
                - "mad"
                - "iqr"
                - "skewness"
                - "kurtosis"
                - "histenergy"
                - "histentropy"
        quantiles (Tuple[float, ...]):
            Quantiles to compute for each object. Ignored if `metrics` does not include
            "quantiles".
        n_bins (int):
            Number of bins to use for histogram-based features. Ignored if `metrics`
            does not include "histenergy" or "histentropy".
        hist_range (Tuple[float, float]):
            Range of pixel values to use for histogram-based features. Ignored if `metrics`
            does not include "histenergy" or "histentropy".
        mask (np.ndarray):
            Optional binary mask to apply to the image to restrict the region of interest.
            Shape (H, W). For example, it can be used to mask out tissues that are not
            of interest.
        device (str):
            Device to use for computation. Options are 'cpu' or 'cuda'. If set to 'cuda',
            CuPy and cucim will be used for GPU acceleration.

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

    Returns:
        pd.DataFrame:
            A DataFrame containing the computed features for each RGB-channel for each object.

    Examples:
        >>> from histolytics.data import hgsc_cancer_he, hgsc_cancer_nuclei
        >>> from histolytics.utils.raster import gdf2inst
        >>> from histolytics.nuc_feats.intensity import rgb_intensity_feats
        >>>
        >>> he_image = hgsc_cancer_he()
        >>> nuclei = hgsc_cancer_nuclei()
        >>> neoplastic_nuclei = nuclei[nuclei["class_name"] == "neoplastic"]
        >>> inst_mask = gdf2inst(
        ...     neoplastic_nuclei, width=he_image.shape[1], height=he_image.shape[0]
        ... )
        >>> # Extract RGB intensity features from the neoplastic nuclei
        >>> feats = rgb_intensity_feats(he_image, inst_mask)
        >>> print(feats.iloc[..., 0:3].head(3))
                    R_mean     R_std  R_quantile_0.25
            292    0.390361  0.071453         0.349138
            316    0.279746  0.032215         0.254310
            340    0.319236  0.071267         0.267241
    """
    if label is not None and img.shape[:2] != label.shape:
        raise ValueError(
            f"Shape mismatch: img.shape[:2]={img.shape[:2]}, label.shape={label.shape}"
        )

    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-cuda12x cucim-cu12\n"
            "or set device='cpu'."
        )

    if _has_cp and device == "cuda":
        img, label = _norm_cp(img, label, mask, out_range=(0, 1))
    else:
        img, label = _norm_np(img, label, mask, out_range=(0, 1))

    channel_codes = ["R", "G", "B"]
    channel_feat_dfs = []
    for c in range(3):
        if _has_cp and device == "cuda":
            channel_feat_df = _intensity_features_cp(
                img[..., c],
                label,
                metrics=metrics,
                quantiles=quantiles,
                n_bins=n_bins,
                hist_range=hist_range,
            )
        else:
            channel_feat_df = _intensity_features_np(
                img[..., c],
                label,
                metrics=metrics,
                quantiles=quantiles,
                n_bins=n_bins,
                hist_range=hist_range,
            )
        # Prefix columns with channel code
        channel_code = channel_codes[c]
        channel_feat_df = channel_feat_df.add_prefix(f"{channel_code}_")
        channel_feat_dfs.append(channel_feat_df)

    # Concatenate along columns (axis=1)
    feats_df = pd.concat(channel_feat_dfs, axis=1)

    return feats_df