Automated pelvimetry and body composition analysis from CT segmentations
Project description
๐ ctpelvimetry
The first open-source Python package for fully automated CT pelvimetry and body composition analysis.
ctpelvimetry turns the manual, ~15-minute-per-scan process of measuring mid-pelvic dimensions and body composition into a fully automated 2-minute pipeline. Built for surgical data science, preoperative risk assessment, and large-scale ML training datasets, it integrates with TotalSegmentator to extract anatomical metrics directly from raw CT scans โ eliminating inter-observer variability.
๐ Contents
- Quick Start
- Installation
- CLI Usage
- Python API
- Output Structure
- Measured Metrics
- Quality Control
- Hardware Requirements
- Body Composition License (Optional)
- Architecture
- Troubleshooting
- Citation
๐ Quick Start
# Install
pip install "ctpelvimetry[seg]"
# Process a single DICOM folder
ctpelvimetry pelv \
--dicom_dir /path/to/Patient_001 \
--patient Patient_001 \
--output_root ./output
# Or a directory full of NIfTI files (Medical Decathlon, KiTS, etc.)
ctpelvimetry pelv \
--nifti_root /path/to/niftis \
--output_root ./output
You get back a CSV with ISD, inlet/outlet AP, sacral metrics, plus QC images. ~2 minutes per scan on a GPU.
๐ฆ Installation
# Basic โ for analysis when you already have segmentations
pip install ctpelvimetry
# Full โ includes TotalSegmentator for end-to-end automation
pip install "ctpelvimetry[seg]"
Requires Python 3.10+.
For body composition (VAT/SAT/muscle), one extra step is required โ see Body Composition License. Pelvimetry works without it.
๐ฅ๏ธ CLI Usage
ctpelvimetry ships two subcommands:
| Subcommand | Purpose |
|---|---|
pelv |
Pelvimetry: mid-pelvic dimensions (ISD, inlet/outlet AP, sacrum) |
body-comp |
Body composition: VAT, SAT, skeletal muscle area at L3 and ISD levels |
pelv โ Pelvimetry
The pelv subcommand has 5 input modes. Pick the one that matches your data layout:
| Mode | Flags | Use when |
|---|---|---|
| 1. Existing seg | --seg_folder (+ --nifti_path) |
You already have TotalSegmentator output |
| 2. Single DICOM | --dicom_dir + --patient |
One patient's DICOM folder on disk |
| 3. Single NIfTI | --nifti_path + --patient |
One NIfTI file |
| 4. DICOM batch | --dicom_root (+ --start / --end) |
Folder of Patient_001/, Patient_002/, ... |
| 5. NIfTI batch | --nifti_root (+ optional --pattern) |
Folder of *.nii.gz files (one per patient) |
Examples
# Mode 1 โ analysis only (skip segmentation)
ctpelvimetry pelv \
--seg_folder /path/to/seg \
--nifti_path /path/to/ct.nii.gz \
--patient Patient_001 \
--output_root ./output
# Mode 2 โ single patient, full pipeline from DICOM
ctpelvimetry pelv \
--dicom_dir /path/to/Patient_001 \
--patient Patient_001 \
--output_root ./output
# Mode 3 โ single patient, full pipeline from NIfTI
ctpelvimetry pelv \
--nifti_path /path/to/ct.nii.gz \
--patient Patient_001 \
--output_root ./output
# Mode 4 โ batch DICOM (expects ./DICOMs/Patient_001/, ./DICOMs/Patient_002/, ...)
ctpelvimetry pelv \
--dicom_root /path/to/DICOMs \
--output_root ./output \
--start 1 --end 250
# Mode 5 โ batch NIfTI (expects *.nii.gz directly under nifti_root)
ctpelvimetry pelv \
--nifti_root /path/to/niftis \
--output_root ./output
# Custom glob pattern (e.g. uncompressed NIfTI, or non-default naming)
ctpelvimetry pelv \
--nifti_root /path/to/niftis \
--pattern "case_*_ct.nii" \
--output_root ./output
Common flags
| Flag | Default | Description |
|---|---|---|
--output_root |
./pelvimetry_output |
Root directory for all outputs |
--output |
combined_pelvimetry_report.csv |
Output CSV filename |
--fast |
off | Use TotalSegmentator --fast mode (faster, slightly less accurate) |
--no-tissue |
off | Skip the body composition (tissue_types) task |
--qc / --no-qc |
on | Generate QC images |
Run ctpelvimetry pelv --help for the full list.
body-comp โ Body Composition
Computes VAT, SAT, and skeletal muscle area at the L3 and mid-ISD levels. Requires pelvimetry to have already run (so it knows where the ISD level is).
# Single patient
ctpelvimetry body-comp \
--patient Patient_001 \
--seg_root ./output/Segmentation \
--nifti_root ./output/NIfTI \
--pelvimetry_csv ./output/combined_pelvimetry_report.csv \
--output ./output/body_comp_001.csv \
--qc
# Batch
ctpelvimetry body-comp \
--seg_root ./output/Segmentation \
--nifti_root ./output/NIfTI \
--pelvimetry_csv ./output/combined_pelvimetry_report.csv \
--output ./output/body_composition_report.csv \
--start 1 --end 250 \
--qc_root ./output/body_comp_qc
Run ctpelvimetry body-comp --help for all flags.
๐ Python API
For custom pipelines or integration into existing data-science workflows.
Public API surface
| Function | Use case |
|---|---|
run_combined_pelvimetry |
Existing segmentation โ measurements |
run_full_pipeline |
DICOM folder โ NIfTI โ seg โ measurements (one patient) |
run_nifti_pipeline |
NIfTI file โ seg โ measurements (one patient) |
run_pelvimetry_batch |
DICOM batch (parent dir of Patient_xxx/ folders) |
run_pelvimetry_nifti_batch |
NIfTI batch (directory of *.nii.gz) |
process_single_patient |
Body composition for one patient |
PelvicConfig |
Tuneable detection thresholds |
Single-patient examples
from ctpelvimetry import (
run_combined_pelvimetry,
run_full_pipeline,
run_nifti_pipeline,
)
# A. Already have segmentation (fastest path; just measurement + QC)
result = run_combined_pelvimetry(
patient_id="Patient_001",
seg_folder="/path/to/segmentation_masks",
nifti_path="/path/to/ct.nii.gz",
qc_dir="./output/QC", # optional
)
print(f"ISD: {result['ISD_mm']} mm")
print(f"Inlet AP: {result['Inlet_AP_mm']} mm")
# B. From a DICOM folder (end-to-end)
result = run_full_pipeline(
patient_id="Patient_001",
dicom_path="/path/to/Patient_001",
output_root="./output",
use_fast=False, # set True for quick previews
skip_tissue=False, # set True to skip body comp
)
# C. From a NIfTI file (end-to-end, skips DICOM conversion)
result = run_nifti_pipeline(
patient_id="Patient_001",
nifti_path="/path/to/ct.nii.gz",
output_root="./output",
)
Batch examples
from ctpelvimetry.batch import (
run_pelvimetry_batch,
run_pelvimetry_nifti_batch,
)
# DICOM batch โ expects /data/DICOMs/Patient_001/, /data/DICOMs/Patient_002/, ...
df = run_pelvimetry_batch(
dicom_root="/data/DICOMs",
output_root="./output",
output_csv="./output/results.csv",
start=1, end=250,
)
# NIfTI batch โ expects /data/niftis/case_001.nii.gz, case_002.nii.gz, ...
df = run_pelvimetry_nifti_batch(
nifti_root="/data/niftis",
output_root="./output",
output_csv="./output/results.csv",
pattern="*.nii.gz", # default
use_fast=False,
skip_tissue=False,
)
# Filter to fully successful patients
success = df[df["Status"] == "Success"]
print(success[["Patient_ID", "ISD_mm", "Inlet_AP_mm"]].describe())
Both batch functions return a pandas.DataFrame and write a CSV to output_csv. Per-patient errors are isolated โ one failure won't abort the run.
Body composition
from ctpelvimetry import process_single_patient
body_comp = process_single_patient(
patient_id="Patient_001",
seg_root="./output/Segmentation",
nifti_path="./output/NIfTI/Patient_001/Patient_001.nii.gz",
pelvimetry_csv="./output/combined_pelvimetry_report.csv",
qc_dir="./output/body_comp_qc", # optional
)
print(f"VAT @ L3: {body_comp['L3_VAT_cm2']} cmยฒ")
print(f"V/S ratio @ L3: {body_comp['L3_VS_ratio']}")
print(f"SMA @ L3: {body_comp['L3_SMA_cm2']} cmยฒ")
Custom thresholds
from ctpelvimetry import run_combined_pelvimetry, PelvicConfig
config = PelvicConfig(
rotation_warn_deg=3.0, # tighter QA than the default 5ยฐ
tilt_warn_deg=3.0,
sacrum_offset_warn_mm=3.0,
)
result = run_combined_pelvimetry(
patient_id="P001",
seg_folder="./seg",
nifti_path="./ct.nii.gz",
config=config,
)
๐ Output Structure
After running the pelvimetry pipeline, --output_root looks like:
output/
โโโ NIfTI/ # DICOM โ NIfTI conversions (DICOM modes only)
โ โโโ Patient_001/
โ โโโ Patient_001.nii.gz
โโโ Segmentation/ # TotalSegmentator masks
โ โโโ Patient_001/
โ โโโ hip_left.nii.gz
โ โโโ hip_right.nii.gz
โ โโโ sacrum.nii.gz
โ โโโ femur_left.nii.gz
โ โโโ femur_right.nii.gz
โ โโโ vertebrae_S1.nii.gz
โ โโโ ...
โ โโโ subcutaneous_fat.nii.gz # only with TOTALSEG_LICENSE_KEY
โ โโโ torso_fat.nii.gz
โ โโโ skeletal_muscle.nii.gz
โโโ QC/ # Visual QC images (PNG)
โ โโโ Patient_001_Sagittal_QC.png
โ โโโ Patient_001_Extended_QC.png
โโโ combined_pelvimetry_report.csv # The aggregated results
CSV schema (key columns)
| Column | Type | Description |
|---|---|---|
Patient_ID |
str | Patient identifier |
Status |
str | Success, Partial_N/6, Failure, or Error |
Error_Log |
str | Per-metric error codes (semicolon-separated), if any |
ISD_mm |
float | Inter-Spinous Distance |
Inlet_AP_mm |
float | Pelvic inlet AP diameter |
Outlet_AP_mm |
float | Pelvic outlet AP diameter |
Outlet_Transverse_mm |
float | Intertuberous diameter |
Sacral_Length_mm |
float | Sacral length (promontory โ coccygeal apex) |
Sacral_Depth_mm |
float | Maximum sacral concavity depth |
Pelvic_Rotation_deg |
float | Axial rotation (quality flag) |
Pelvic_Tilt_deg |
float | Coronal tilt (quality flag) |
Promontory_x/y/z |
float | RAS world coordinates (mm) of detected landmarks |
Upper_Symphysis_x/y/z |
float | (and similar columns for all detected landmarks) |
CT_NIfTI, Seg_* |
str | File paths to inputs/intermediates for traceability |
Status semantics:
Successโ all 6 pelvimetric metrics computedPartial_N/6โ N of 6 metrics computed; the rest inError_LogFailureโ 0 metrics computed (segmentation produced but unusable)Fail_NIfTIโ DICOM โ NIfTI conversion failed (DICOM modes only)Fail_Segโ TotalSegmentator failed to produce required masksFail_NIfTI_Missingโ input NIfTI file does not exist (NIfTI modes only)Errorโ uncaught exception during processing (batch mode only); full message inError_Message
๐ฌ Measured Metrics
Pelvimetry (Mid-Pelvic Workspace)
| Metric | Description |
|---|---|
| ISD (mm) | Inter-Spinous Distance โ narrowest mid-pelvic width, critical for deep pelvic dissection |
| Inlet AP (mm) | Promontory โ upper symphysis distance |
| Outlet AP (mm) | Coccygeal apex โ lower symphysis distance |
| Outlet Transverse (mm) | Intertuberous diameter (between ischial tuberosities) |
| Sacral Length (mm) | Promontory โ coccygeal apex along the sacral curve |
| Sacral Depth (mm) | Maximum concavity depth from the inlet-outlet chord |
Body Composition (requires license)
| Metric | Description |
|---|---|
| VAT / SAT (cmยฒ) | Visceral / subcutaneous adipose tissue area |
| V/S Ratio | VAT / SAT ratio โ indicator of visceral obesity |
| SMA (cmยฒ) | Skeletal Muscle Area at L3 and mid-pelvis levels |
๐๏ธ Quality Control
ctpelvimetry generates two QC panels per scan so you can spot-check landmark detection visually before trusting the numbers.
Sagittal QC: sacral length (magenta), inlet AP (green), outlet AP (orange), sacral depth (cyan).
Extended QC: outlet transverse diameter, ISD, and tabular measurement summary.
The pipeline also writes quality flags to the CSV:
Pelvic_Rotation_Flag/Pelvic_Tilt_Flagโok,warn,high(axial rotation and coronal tilt of the patient on the table)Sacrum_Offset_Flagโ sacrum-to-symphysis midline offset in mm
These flags don't block measurement but signal when manual review is wise.
โ๏ธ Hardware Requirements
End-to-end automation runs deep-learning segmentation. A GPU is strongly recommended.
| Setup | Time per scan | Notes |
|---|---|---|
| NVIDIA GPU 8GB+ VRAM (T4, RTX 3060+, A100) | < 2 min | Recommended. 16GB+ for high-res CTs. |
| CPU only | 10โ30+ min | Will work but slow; may OOM on large series |
| Google Colab (free T4) | < 2 min | Easy cloud option for clinical researchers |
If you only run analysis on pre-existing segmentations (Mode 1), a standard CPU is fine โ no GPU needed.
๐ Body Composition License (Optional)
Body composition (VAT/SAT/muscle) requires TotalSegmentator's tissue_types task, which needs a free academic license. Pelvimetry works without it.
# 1. Register (free, takes 30 seconds): https://backend.totalsegmentator.com/license-academic/
# 2. Set the env var (add to ~/.bashrc or ~/.zshrc to persist):
export TOTALSEG_LICENSE_KEY=aca_xxxxxxxxxxxx
If TOTALSEG_LICENSE_KEY is unset, the pipeline silently skips body composition and reports pelvimetry only. No errors, no warnings beyond a one-line info message.
Note: Versions โค 1.4.1 shipped with a hard-coded license key. That key has been removed (see CHANGELOG); please register your own.
๐๏ธ Architecture
ctpelvimetry/
โโโ cli.py # Unified CLI entry point (pelv + body-comp subcommands)
โโโ pipeline.py # End-to-end orchestration:
โ # - run_combined_pelvimetry (seg โ measurements)
โ # - run_full_pipeline (DICOM โ measurements)
โ # - run_nifti_pipeline (NIfTI โ measurements)
โโโ batch.py # Batch processors with per-patient error isolation:
โ # - run_pelvimetry_batch
โ # - run_pelvimetry_nifti_batch
โ # - run_body_composition_batch
โโโ conversion.py # DICOM โ NIfTI conversion
โโโ segmentation.py # TotalSegmentator integration wrapper
โโโ landmarks.py # 3D geometric landmark detection
โโโ metrics.py # Pelvimetric calculations (ISD, AP, sacral, etc.)
โโโ body_composition.py # Fat / muscle area quantification
โโโ qc.py # QC image generation (matplotlib)
โโโ io.py # Mask I/O with canonical (RAS) reorientation
โโโ config.py # PelvicConfig dataclass: tuneable thresholds
๐ ๏ธ Troubleshooting
Patient_xxx: Status = Failure, Error_Log = ALL: ISD_NO_HIP_MASK
TotalSegmentator didn't produce hip masks. Usually the CT FOV doesn't include the pelvis, or the scan is corrupted. Inspect the input NIfTI in a viewer (3D Slicer, ITK-SNAP).
Tissue Types: Requires license
You're trying to run body composition without a license. Either set TOTALSEG_LICENSE_KEY (see Body Composition License) or pass --no-tissue to skip explicitly.
Segmentation is extremely slow
Either you don't have a GPU or PyTorch isn't seeing it. Verify with:
import torch; print(torch.cuda.is_available())
If False, reinstall PyTorch with the CUDA build for your driver.
Status = Partial_5/6 with Sacral_Depth: SACRAL_NO_LANDMARKS
Promontory or coccygeal apex wasn't detected. The other 5 metrics are still valid. Check the Sagittal QC PNG.
Segmentation results look wrong despite no error
Patient is probably rotated or tilted on the table. Check Pelvic_Rotation_deg and Pelvic_Tilt_deg columns โ the pipeline flags values > 5ยฐ (default) as warn, and these often indicate unreliable measurement. Tighten the thresholds via PelvicConfig if you want stricter automatic flagging.
CSV Status column is missing or empty
You're probably reading a body-composition CSV (different schema). Pelvimetry results are in combined_pelvimetry_report.csv.
๐ค Contributing
PRs welcome from both the surgical and data-science communities. The package follows semantic versioning; tag pushes (v*) auto-trigger PyPI publishing via GitHub Actions.
git checkout -b feature/your-feature
# ... make changes, add tests in tests/
pytest # all should pass
git commit -am "feat: ..."
git push origin feature/your-feature
# Open a PR
๐ Citation
If ctpelvimetry enables your research, please cite:
@software{huang2026ctpelvimetry,
author = {Huang, Shih-Feng},
title = {ctpelvimetry: Automated CT Pelvimetry and Body Composition Analysis},
year = {2026},
url = {https://github.com/odafeng/ctpelvimetry},
version = {1.6.0},
}
A peer-reviewed manuscript on the clinical validation of this pipeline is in preparation.
License: Apache License 2.0 | Author: Shih-Feng Huang, MD (@odafeng)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ctpelvimetry-1.6.1.tar.gz.
File metadata
- Download URL: ctpelvimetry-1.6.1.tar.gz
- Upload date:
- Size: 60.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a9ff014ede0b4dc8624ae936eb05297ccc723f3e1c17da8a86373f78f8bc002
|
|
| MD5 |
17f66e280a178e4c335f00eb508a5281
|
|
| BLAKE2b-256 |
2f36847c8694b7e988fb6e815a211722e3fc873712125fe7b392a389beb5ed68
|
Provenance
The following attestation bundles were made for ctpelvimetry-1.6.1.tar.gz:
Publisher:
release.yml on odafeng/ctpelvimetry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ctpelvimetry-1.6.1.tar.gz -
Subject digest:
7a9ff014ede0b4dc8624ae936eb05297ccc723f3e1c17da8a86373f78f8bc002 - Sigstore transparency entry: 1429418366
- Sigstore integration time:
-
Permalink:
odafeng/ctpelvimetry@7f358c5152dde04f0092af42efb765fbe44f084a -
Branch / Tag:
refs/tags/v1.6.1 - Owner: https://github.com/odafeng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7f358c5152dde04f0092af42efb765fbe44f084a -
Trigger Event:
push
-
Statement type:
File details
Details for the file ctpelvimetry-1.6.1-py3-none-any.whl.
File metadata
- Download URL: ctpelvimetry-1.6.1-py3-none-any.whl
- Upload date:
- Size: 52.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
778301ae7e154e2cbeb8c403a68503a2da8241d37a834c67eefe961f724d6bb2
|
|
| MD5 |
d514187cf42e5184c4bef17ceddaa339
|
|
| BLAKE2b-256 |
b56bb66d54d8466808ee7832cee506c8ca6b08665aa7e340221cda429572ce19
|
Provenance
The following attestation bundles were made for ctpelvimetry-1.6.1-py3-none-any.whl:
Publisher:
release.yml on odafeng/ctpelvimetry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ctpelvimetry-1.6.1-py3-none-any.whl -
Subject digest:
778301ae7e154e2cbeb8c403a68503a2da8241d37a834c67eefe961f724d6bb2 - Sigstore transparency entry: 1429418369
- Sigstore integration time:
-
Permalink:
odafeng/ctpelvimetry@7f358c5152dde04f0092af42efb765fbe44f084a -
Branch / Tag:
refs/tags/v1.6.1 - Owner: https://github.com/odafeng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7f358c5152dde04f0092af42efb765fbe44f084a -
Trigger Event:
push
-
Statement type: