Skip to content

Morphology Features API

The IBSI reference manual defines these features in Morphological features. See Definitions.

pictologics.features.morphology

Morphology Feature Extraction Module

This module provides functions for calculating Morphological (Shape and Size) features from medical images. It implements the Image Biomarker Standardisation Initiative (IBSI) compliant algorithms.

Key Features:

  • Voxel-based: Volume (voxel counting).
  • Mesh-based: Surface Area, Volume (mesh), Compactness, Sphericity.
  • PCA-based: Major/Minor/Least Axis Length, Elongation, Flatness.
  • Convex Hull: Volume, Area, Max 3D Diameter.
  • Bounding Box: Oriented (OMBB) and Axis-Aligned (AABB) Bounding Boxes.
  • Minimum Volume Enclosing Ellipsoid (MVEE): Volume, Area.
  • Intensity-Weighted: Center of Mass Shift, Integrated Intensity.

Optimization:

Uses numba kernels for the area and the volume of the marching cubes mesh (from its corner cases, without the mesh), the moments, the exact convex hull and its candidates, the oriented bounding box and the Khachiyan algorithm of the MVEE.

Example

Calculate morphology features from a mask:

import numpy as np
from pictologics.loader import Image
from pictologics.features.morphology import calculate_morphology_features

# Create dummy mask
mask_arr = np.zeros((50, 50, 50), dtype=np.uint8)
mask_arr[10:40, 10:40, 10:40] = 1
mask = Image(mask_arr, spacing=(1.0, 1.0, 1.0), origin=(0,0,0))

# Calculate features
features = calculate_morphology_features(mask)
print(features["volume_voxel_counting_YEKZ"])

calculate_morphology_features(mask, image=None, intensity_mask=None, roi_bbox=None, grid_offset=None)

Calculate morphological features from the ROI mask. Includes both voxel-based and mesh-based features (IBSI compliant).

Parameters:

Name Type Description Default
mask Image

Image object containing the morphological mask. Nonzero values are treated as ROI membership.

required
image Optional[Image]

Optional Image object containing intensity data (required for some features).

None
intensity_mask Optional[Image]

Optional Image object containing the intensity mask (e.g. after outlier filtering). If provided, used for intensity-weighted features (99N0, KLMA). If None, defaults to mask.

None
roi_bbox Optional[tuple[slice, slice, slice]]

Optional precomputed tight bounding box of the mask's nonzero voxels (tuple of slices, as returned by an internal bbox scan). Skips rescanning the full mask volume. If None, computed internally.

None
grid_offset Optional[tuple[int, int, int]]

Optional index of the first voxel of the arrays in a larger grid, when they hold a region of it. The mesh is then made in the index frame of that grid, so the features are those of the whole grid bit for bit (the MVEE fit depends on the frame at about 1e-5).

None

Returns:

Type Description
dict[str, float]

Dictionary of calculated features.

Example
import numpy as np
from pictologics.loader import Image
from pictologics.features.morphology import calculate_morphology_features

mask_arr = np.zeros((50, 50, 50), dtype=np.uint8)
mask_arr[10:40, 10:40, 10:40] = 1
mask = Image(array=mask_arr, spacing=(1.0, 1.0, 1.0), origin=(0.0, 0.0, 0.0))

features = calculate_morphology_features(mask)
print(round(features["volume_voxel_counting_YEKZ"], 1))
# 27000.0
print(round(features["sphericity_QCFX"], 2))
# 0.82
Source code in pictologics/features/morphology.py
def calculate_morphology_features(
    mask: Image,
    image: Optional[Image] = None,
    intensity_mask: Optional[Image] = None,
    roi_bbox: Optional[tuple[slice, slice, slice]] = None,
    grid_offset: Optional[tuple[int, int, int]] = None,
) -> dict[str, float]:
    """
    Calculate morphological features from the ROI mask.
    Includes both voxel-based and mesh-based features (IBSI compliant).

    Args:
        mask: Image object containing the morphological mask. Nonzero values
            are treated as ROI membership.
        image: Optional Image object containing intensity data (required for some features).
        intensity_mask: Optional Image object containing the intensity mask (e.g. after outlier filtering).
                        If provided, used for intensity-weighted features (99N0, KLMA).
                        If None, defaults to `mask`.
        roi_bbox: Optional precomputed tight bounding box of the mask's nonzero voxels
                  (tuple of slices, as returned by an internal bbox scan). Skips
                  rescanning the full mask volume. If None, computed internally.
        grid_offset: Optional index of the first voxel of the arrays in a larger grid,
                  when they hold a region of it. The mesh is then made in the index frame
                  of that grid, so the features are those of the whole grid bit for bit
                  (the MVEE fit depends on the frame at about 1e-5).

    Returns:
        Dictionary of calculated features.

    Example:
        ```python
        import numpy as np
        from pictologics.loader import Image
        from pictologics.features.morphology import calculate_morphology_features

        mask_arr = np.zeros((50, 50, 50), dtype=np.uint8)
        mask_arr[10:40, 10:40, 10:40] = 1
        mask = Image(array=mask_arr, spacing=(1.0, 1.0, 1.0), origin=(0.0, 0.0, 0.0))

        features = calculate_morphology_features(mask)
        print(round(features["volume_voxel_counting_YEKZ"], 1))
        # 27000.0
        print(round(features["sphericity_QCFX"], 2))
        # 0.82
        ```
    """
    features, rest = _morphology_first(mask, image, intensity_mask, roi_bbox, grid_offset)
    if rest is None:
        return features
    return _morphology_merge(features, rest, _morphology_second(rest))