Skip to content

Morphology Features API

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 for optimizing the Khachiyan algorithm for MVEE calculation.

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)

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 binary mask (Morphological Mask).

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

Returns:

Type Description
dict[str, float]

Dictionary of calculated features.

Source code in pictologics/features/morphology.py
def calculate_morphology_features(
    mask: Image, image: Optional[Image] = None, intensity_mask: Optional[Image] = 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 binary mask (Morphological Mask).
        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`.

    Returns:
        Dictionary of calculated features.
    """
    features: dict[str, float] = {}
    i_mask = intensity_mask if intensity_mask is not None else mask

    # 1. Voxel Based Features
    voxel_volume = np.prod(mask.spacing)
    n_voxels = np.sum(mask.array)
    features["volume_voxel_counting_YEKZ"] = float(n_voxels * voxel_volume)

    # 2. Mesh Based Features
    mesh_feats, verts, faces = _get_mesh_features(mask)
    features.update(mesh_feats)

    if verts is None or faces is None:
        return features

    mesh_volume = features.get("volume_RNU0", 0.0)
    surface_area = features.get("surface_area_C0JK", 0.0)

    # 3. Shape Features
    features.update(_get_shape_features(surface_area, mesh_volume))

    # Pre-compute mask moments once (used by PCA and intensity morphology features)
    mask_moments = _accumulate_moments_from_mask_numba(mask.array)

    # 4. PCA Based Features
    pca_feats, evals, evecs = _get_pca_features(
        mask, mesh_volume, surface_area, mask_moments=mask_moments
    )
    features.update(pca_feats)

    # 5. Convex Hull Features
    hull_feats, hull = _get_convex_hull_features(verts, mesh_volume, surface_area)
    features.update(hull_feats)

    # 6. Bounding Box Features
    features.update(_get_bounding_box_features(verts, evecs, mesh_volume, surface_area))

    # 7. MVEE Features
    features.update(_get_mvee_features(hull, verts, mesh_volume, surface_area))

    # 8. Intensity Based Features
    if image is not None:
        features.update(
            _get_intensity_morphology_features(
                mask, image, i_mask, mesh_volume, mask_moments=mask_moments
            )
        )

    return features