Segmentation Base Class

SegmentAnatomyBase defines the shared chest-anatomy segmentation contract used by PhysioMotion4D segmentation implementations. It owns an AnatomyTaxonomy instance that subclasses populate to declare which anatomy groups (and which organ labels within each group) they produce.

Class Reference

class physiomotion4d.SegmentAnatomyBase(log_level=20)[source]

Bases: PhysioMotion4DBase

Base class for anatomy segmentation that provides common functionality for segmenting anatomy in CT images.

This class implements preprocessing, postprocessing, and mask creation methods that are shared across different anatomy segmentation implementations. It owns an AnatomyTaxonomy instance that captures the group→organ structure (e.g. heart contains atrial_appendage_left at id 61); subclasses populate it via self.taxonomy.add_organ(...) and call _finalize_other_group() once they’re done.

Extensibility

Each segmenter is free to define its own group names — the taxonomy does not hard-code a fixed set. A new subclass adds groups by calling self.taxonomy.add_organ(group_name, label_id, organ_name) for each organ; the group is created lazily on first use. To assign a custom OmniSurface look to a new group, register it in physiomotion4d.usd_anatomy_tools.DEFAULT_RENDER_PARAMS (see that module’s docstring). Groups without a registered look fall back to the "other" entry, so they still render.

target_spacing

Target isotropic spacing for resampling.

Type:

float

rescale_intensity_range

Whether to rescale intensity values.

Type:

bool

contrast_threshold

Threshold for contrast agent detection.

Type:

int

taxonomy

Group→organ mapping shared with physiomotion4d.USDAnatomyTools.

Type:

AnatomyTaxonomy

__init__(log_level=20)[source]

Initialize the SegmentAnatomyBase class.

Sets up default parameters for image preprocessing and seeds the anatomy taxonomy with the two base-class default organs (contrast and soft_tissue). Subclasses should:

  1. Add their organ groups via self.taxonomy.add_organ(...).

  2. Call _finalize_other_group() to fill in unclaimed ids.

Parameters:

log_level (int | str) – Logging level (default: logging.INFO).

label_to_type(label_name)[source]

Return the anatomy group (‘heart’, ‘lung’, etc.) for a label name.

Used by physiomotion4d.ConvertVTKToUSD to group label-mode mesh prims under per-type Xforms (e.g. /World/{basename}/heart/{label_name}). Delegates to the taxonomy.

Parameters:

label_name (str) – Organ name (a value in the taxonomy’s group organ dicts).

Return type:

str

Returns:

The anatomy group name. Falls back to "other" for any label the segmenter doesn’t recognize.

set_target_spacing(target_spacing)[source]

Set the target isotropic spacing for image resampling.

Parameters:

target_spacing (float) – Target spacing in millimeters for all three spatial dimensions. Set to 0.0 to disable resampling.

Return type:

None

Example

>>> segmenter.set_target_spacing(1.0)  # 1mm isotropic spacing
preprocess_input(input_image)[source]

Preprocess the input image for segmentation.

Performs image preprocessing including resampling to isotropic spacing and optional intensity rescaling. The preprocessing ensures consistent image characteristics for reliable segmentation.

Parameters:

input_image (itk.image) – The input 3D CT image to preprocess

Returns:

The preprocessed image with isotropic spacing and

optionally rescaled intensities

Return type:

itk.image

Raises:

Example

>>> preprocessed = segmenter.preprocess_input(ct_image)
postprocess_labelmap(labelmap_image, input_image)[source]

Resample the labelmap to match the input image spacing.

Ensures the segmentation labelmap has the same spatial properties as the original input image by resampling using label-specific interpolation that preserves discrete label values.

Parameters:
  • labelmap_image (itk.image) – The segmentation labelmap to resample

  • input_image (itk.image) – The original input image providing target spacing and geometry

Returns:

The resampled labelmap matching input image properties

Return type:

itk.image

Example

>>> final_labels = segmenter.postprocess_labelmap(labels, original_image)
segment_connected_component(preprocessed_image, labelmap_image, lower_threshold, upper_threshold, labelmap_ids=None, mask_id=0, use_mid_slice=True, hole_fill=2)[source]

Segment connected components based on intensity thresholding.

Identifies connected regions within intensity thresholds and existing anatomical masks, then selects the largest component. This is useful for segmenting structures like contrast-enhanced blood or specific tissue types.

Parameters:
  • preprocessed_image (itk.image) – The preprocessed input image

  • labelmap_image (itk.image) – Existing labelmap to constrain search

  • lower_threshold (int) – Lower intensity threshold

  • upper_threshold (int) – Upper intensity threshold

  • labelmap_ids (None | list[int]) – List of label IDs to search within. If None, searches within all existing labels

  • mask_id (int) – ID to assign to the segmented component

  • use_mid_slice (bool) – If True, find largest component in middle slice only; if False, use entire 3D volume

  • hole_fill (int) – Number of pixels to dilate/erode for hole filling

Returns:

Updated labelmap with new component labeled as mask_id

Return type:

itk.image

Example

>>> # Segment contrast-enhanced blood
>>> updated_labels = segmenter.segment_connected_component(
...     preprocessed_image, labels, 700, 4000, mask_id=135
... )
segment_contrast_agent(preprocessed_image, labelmap_image)[source]

Include contrast-enhanced blood in the labelmap.

Segments high-intensity regions corresponding to contrast-enhanced blood vessels and cardiac chambers. Uses connected component analysis focused on the middle slice where the heart is typically located.

Parameters:
  • preprocessed_image (itk.image) – The preprocessed CT image

  • labelmap_image (itk.image) – Existing segmentation labelmap

Returns:

Updated labelmap with contrast-enhanced regions labeled

Return type:

itk.image

Note

Assumes the mid-z slice of the data contains the heart.

Example

>>> contrast_labels = segmenter.segment_contrast_agent(preprocessed_image, base_labels)
create_anatomy_group_masks(labelmap_image)[source]

Create binary masks for different anatomical groups from the labelmap.

Generates separate binary masks for major anatomical systems by grouping related anatomical structures from the detailed labelmap. This is useful for motion analysis and visualization.

Parameters:

labelmap_image (itk.image) – The detailed segmentation labelmap

Returns:

Dictionary of binary masks keyed by group

name. Exactly one entry per group registered in taxonomy (plus "other"). The returned key set is segmenter-specific — callers that need a particular group should check membership ("lung" in masks) rather than assume a fixed schema.

Return type:

dict[str, itk.image]

Example

>>> masks = segmenter.create_anatomy_group_masks(labelmap)
>>> if "lung" in masks:
...     lung_mask = masks["lung"]
segmentation_method(preprocessed_image)[source]

Abstract method for image segmentation - must be implemented by subclasses.

This method should contain the core segmentation algorithm specific to each implementation (e.g., TotalSegmentator).

Parameters:

preprocessed_image (itk.image) – The preprocessed input image

Returns:

The segmentation labelmap

Return type:

itk.image

Raises:

NotImplementedError – If called on the base class

Note

This method must be implemented by subclasses to provide the specific segmentation algorithm.

dilate_mask(mask, dilation)[source]

Dilate a binary mask using morphological operations.

Expands the mask regions by the specified number of pixels to create larger regions of interest. Useful for creating candidate regions or ensuring complete coverage of anatomical structures.

Parameters:
  • mask (itk.image) – The binary mask to dilate

  • dilation (int) – Number of pixels to dilate in each direction

Returns:

The dilated binary mask

Return type:

itk.image

Example

>>> dilated_heart = segmenter.dilate_mask(heart_mask, 5)
segment(input_image, contrast_enhanced_study=False)[source]

Perform complete chest CT segmentation.

This is the main segmentation method that coordinates preprocessing, segmentation, contrast agent detection (if applicable), postprocessing, and anatomical group mask creation.

Parameters:
  • input_image (itk.image) – The input 3D CT image to segment

  • contrast_enhanced_study (bool) – Whether the study uses contrast enhancement. If True, performs additional contrast agent segmentation to identify enhanced blood vessels

Returns:

Dictionary containing:
  • ”labelmap”: Detailed segmentation labelmap

  • ”lung”: Binary mask of pulmonary structures

  • ”heart”: Binary mask of cardiac structures

  • ”major_vessels”: Binary mask of major blood vessels

  • ”bone”: Binary mask of skeletal structures

  • ”soft_tissue”: Binary mask of soft tissue organs

  • ”other”: Binary mask of remaining structures

  • ”contrast”: Binary mask of contrast-enhanced regions

Return type:

dict[str, itk.image]

Example

>>> result = segmenter.segment(ct_image, contrast_enhanced_study=True)
>>> labelmap = result['labelmap']
>>> heart_mask = result['heart']

Segmentation Contract

Concrete segmenters accept an ITK image and return a dictionary of ITK images:

import itk

from physiomotion4d import SegmentChestTotalSegmentator

image = itk.imread("chest_ct.nrrd")
segmenter = SegmentChestTotalSegmentator()
masks = segmenter.segment(image, contrast_enhanced_study=True)

labelmap = masks["labelmap"]
if "heart" in masks:
    heart = masks["heart"]

The returned dictionary always contains "labelmap" plus one entry per anatomy group the segmenter registered in its taxonomy (and "other" for unclassified labels). The exact key set is segmenter-specific — callers must check membership ("lung" in masks) rather than assume a fixed schema. For example, SegmentChestTotalSegmentator returns the full heart, lung, bone, major_vessels, soft_tissue, contrast, other set, while SegmentHeartSimpleware returns only the groups its ASCardio module actually populates (heart, major_vessels, soft_tissue, contrast, other).

Anatomy Taxonomy

The group-to-organ mapping is held by AnatomyTaxonomy, a small data class shared between the segmenter and downstream renderers (USDAnatomyTools, ConvertVTKToUSD). It is independent of ITK and OpenUSD so segmentation code can be reasoned about without pulling in the rendering stack.

class physiomotion4d.AnatomyGroup(name, organs=<factory>)[source]

One named anatomy group together with the organ labels it contains.

name

Group name (e.g. "heart", "lung").

organs

Maps integer label id to organ name within this group.

name: str
organs: dict[int, str]
__init__(name, organs=<factory>)
class physiomotion4d.AnatomyTaxonomy[source]

Mapping of anatomical groups to the organs each group contains.

Groups are added in insertion order, which determines the order returned by group_names() and the iteration order of all_labels().

Example

>>> tax = AnatomyTaxonomy()
>>> tax.add_organ("heart", 51, "heart")
>>> tax.add_organ("heart", 61, "atrial_appendage_left")
>>> tax.add_organ("lung", 10, "lung_upper_lobe_left")
>>> tax.group_for_label("atrial_appendage_left")
'heart'
>>> tax.labels_in_group("lung")
{10: 'lung_upper_lobe_left'}
OTHER_GROUP = 'other'

Sentinel group name used by fill_other_group() for unclaimed ids.

__init__()[source]
add_group(name)[source]

Ensure a group exists and return it.

Parameters:

name (str) – Group name.

Return type:

AnatomyGroup

Returns:

The (new or existing) AnatomyGroup.

add_organ(group, label_id, organ_name)[source]

Add one organ label to the named group.

Creates the group if it does not yet exist. Reassigning the same label id within the same group is silently allowed (last write wins). If the same id is already registered in a different group, a warning is logged and the new assignment is dropped, so the first registration wins and group_for_id() remains deterministic.

Parameters:
  • group (str) – Target group name.

  • label_id (int) – Integer label id (e.g. TotalSegmentator class index).

  • organ_name (str) – Human-readable organ name.

Return type:

None

group_names()[source]

Return group names in the order they were first added.

Return type:

list[str]

labels_in_group(group)[source]

Return {label_id: organ_name} for group; empty dict if absent.

Return type:

dict[int, str]

all_labels()[source]

Return the union of every group’s organs as a single id→name dict.

Return type:

dict[int, str]

group_for_label(label_name)[source]

Return the group containing label_name.

Falls back to OTHER_GROUP if no group contains the name; this keeps physiomotion4d.ConvertVTKToUSD happy when it encounters labels the segmenter did not classify.

Return type:

str

group_for_id(label_id)[source]

Return the group containing label_id; OTHER_GROUP if absent.

Return type:

str

fill_other_group(id_range=range(1, 256), name_template='other_{id}')[source]

Populate the other group with any ids not already claimed.

Called by physiomotion4d.SegmentAnatomyBase subclasses at the end of __init__ to mark every id in the segmenter’s class index space that no specific group claimed.

Parameters:
  • id_range (range) – Inclusive-lower / exclusive-upper id space to scan.

  • name_template (str) – str.format template for synthetic organ names; receives the unclaimed id as {id}.

Return type:

None

Typical usage from a subclass __init__:

class SegmentMySite(SegmentAnatomyBase):
    def __init__(self):
        super().__init__()
        self.taxonomy.add_organ("heart", 51, "myocardium")
        self.taxonomy.add_organ("heart", 61, "atrial_appendage_left")
        self.taxonomy.add_organ("lung", 10, "lung_upper_lobe_left")
        # ...
        self._finalize_other_group()

Downstream callers can introspect what a segmenter produces without running it:

tax = segmenter.taxonomy
print(tax.group_names())                       # ['heart', 'lung', ...]
print(tax.labels_in_group("heart"))            # {51: 'myocardium', ...}
print(tax.group_for_label("myocardium"))       # 'heart'
print(tax.all_labels())                        # full id -> name dict

Extending Segmentation

New runtime segmentation classes should:

  1. Inherit from SegmentAnatomyBase (or another PhysioMotion4DBase subclass if no anatomy taxonomy is needed).

  2. Populate self.taxonomy with add_organ calls in __init__.

  3. Call self._finalize_other_group() once all groups have been registered.

  4. Use log_info() / log_debug() instead of print.

  5. Document the key set the segmenter produces; downstream callers should check membership rather than assume a fixed schema.

Keep synthetic tests small and mark real-data tests with requires_data.

See Also