diff --git a/.agents/.gitignore b/.agents/.gitignore index 93c0f73..2cf3fac 100644 --- a/.agents/.gitignore +++ b/.agents/.gitignore @@ -1 +1,2 @@ settings.local.json +.nicc* diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 5e21570..dd77756 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -10,7 +10,7 @@ Runs on every push and pull request to main branches. Includes: - **unit-tests**: Cross-platform unit tests - Runs on Ubuntu and Windows - - Python 3.10, 3.11, and 3.12 + - Python 3.11 and 3.12 - Uses PyTorch CPU version to avoid GPU dependencies - Excludes slow tests and tests requiring external data - Generates coverage reports @@ -83,7 +83,7 @@ The workflows use multiple caching layers to speed up builds: ### Important: GPU Tests Are Disabled by Default -⚠️ **GPU tests do NOT run automatically** to prevent jobs from waiting indefinitely in the queue when no runner is available. +**GPU tests do NOT run automatically** to prevent jobs from waiting indefinitely in the queue when no runner is available. To run GPU tests, you must either: 1. **Manually trigger the workflow**: Go to Actions > CI > Run workflow diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ded2fa..4e656ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.11', '3.12'] steps: - name: Checkout code @@ -79,7 +79,9 @@ jobs: libxrender1 \ libxext6 \ libxrandr2 \ - libxi6 + libxi6 \ + xvfb \ + libosmesa6 sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* @@ -99,13 +101,20 @@ jobs: run: | pip list - - name: Run unit tests (fast, no external data) + - name: Run unit tests (fast, no external data) - Ubuntu + if: matrix.os == 'ubuntu-latest' + run: | + xvfb-run -a --server-args="-screen 0 1024x768x24" \ + pytest tests/ -v -m "not slow and not requires_data and not experiment" --cov=physiomotion4d --cov-report=xml --cov-report=term --cov-report=html + + - name: Run unit tests (fast, no external data) - Windows + if: matrix.os == 'windows-latest' run: | pytest tests/ -v -m "not slow and not requires_data and not experiment" --cov=physiomotion4d --cov-report=xml --cov-report=term --cov-report=html - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10' + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.xml @@ -115,7 +124,7 @@ jobs: - name: Upload coverage artifacts uses: actions/upload-artifact@v4 - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10' + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' with: name: coverage-report-unit-tests path: htmlcov/ @@ -148,10 +157,10 @@ jobs: echo "Disk space after cleanup:" df -h - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.11' cache: 'pip' cache-dependency-path: pyproject.toml @@ -185,7 +194,9 @@ jobs: libxrender1 \ libxext6 \ libxrandr2 \ - libxi6 + libxi6 \ + xvfb \ + libosmesa6 sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* @@ -218,17 +229,20 @@ jobs: - name: Run USD conversion tests run: | - pytest tests/test_convert_vtk_to_usd_polymesh.py -v -m "not slow" --cov=physiomotion4d --cov-append --cov-report=xml + xvfb-run -a --server-args="-screen 0 1024x768x24" \ + pytest tests/test_convert_vtk_to_usd_polymesh.py -v -m "not slow" --cov=physiomotion4d --cov-append --cov-report=xml continue-on-error: true - name: Run USD utility tests run: | - pytest tests/test_usd_merge.py tests/test_usd_time_preservation.py -v --cov=physiomotion4d --cov-append --cov-report=xml + xvfb-run -a --server-args="-screen 0 1024x768x24" \ + pytest tests/test_usd_merge.py tests/test_usd_time_preservation.py -v --cov=physiomotion4d --cov-append --cov-report=xml continue-on-error: true - name: Run all integration tests run: | - pytest tests/ -v -m "not slow and not experiment and not requires_gpu" + xvfb-run -a --server-args="-screen 0 1024x768x24" \ + pytest tests/ -v -m "not slow and not experiment and not requires_gpu" continue-on-error: true - name: Upload coverage to Codecov @@ -345,10 +359,10 @@ jobs: with: lfs: true - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.11" cache: 'pip' - name: Install dev dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c750323..2ea46a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 with: - python-version: '3.10' + python-version: '3.11' - name: Install build tools run: | diff --git a/CLAUDE.md b/CLAUDE.md index b045f66..7ec2ae5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ py -m pytest tests/ --ignore=tests/test_segment_chest_total_segmentator.py \ # With coverage py -m pytest tests/ --cov=src/physiomotion4d --cov-report=html -# Experiment notebook tests (very slow, opt-in) +# Experiment script tests (very slow, opt-in) py -m pytest tests/ --run-experiments # Create missing baselines diff --git a/README.md b/README.md index 8a605e3..b33d13b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ includes: > research and visualization toolkit. It is not a medical device and must not > be used for diagnosis, treatment planning, or clinical decision-making. -## 🚀 Key Features +## Key Features - **Complete 4D Medical Imaging Pipeline**: End-to-end processing from 4D CT data to animated USD models - **Multiple AI Segmentation Methods**: TotalSegmentator and Simpleware cardiac segmentation @@ -37,7 +37,7 @@ includes: - **Physiological Motion Analysis**: Capture and visualize cardiac and respiratory motion - **Flexible Workflow Control**: Step-based processing with checkpoint management -## 📋 Supported Applications +## Supported Applications - **Cardiac Imaging**: Heart-gated CT processing with cardiac motion analysis - **Pulmonary Imaging**: Lung 4D-CT processing with respiratory motion tracking @@ -45,11 +45,11 @@ includes: - **Research Visualization**: Advanced medical imaging research in Omniverse - **Clinical Planning**: Dynamic anatomical models for treatment planning -## 🛠️ Installation +## Installation ### Prerequisites -- Python 3.10+ (Python 3.10, 3.11, or 3.12 recommended) +- Python 3.11+ (Python 3.11 or 3.12 recommended) - NVIDIA GPU with CUDA 13 — recommended for production use; CPU-only installation is supported but slow - 16GB+ RAM (32GB+ recommended for large datasets) - NVIDIA Omniverse (for USD visualization) @@ -109,7 +109,7 @@ print(f"PhysioMotion4D version: {physiomotion4d.__version__}") print(WorkflowConvertHeartGatedCTToUSD.__name__) ``` -## 🏗️ Package Architecture +## Package Architecture ### Core Components @@ -156,7 +156,7 @@ print(WorkflowConvertHeartGatedCTToUSD.__name__) ## Getting Started: Tutorials -The `tutorials/` directory contains six end-to-end Python scripts, one for each +The `tutorials/` directory contains nine end-to-end Python scripts, one for each major workflow. They are the recommended starting point for new users. | # | Script | Workflow | Dataset | @@ -167,17 +167,21 @@ major workflow. They are the recommended starting point for new users. | 4 | `tutorials/tutorial_04_fit_statistical_model_to_patient.py` | Fit statistical model to patient | KCL-Heart-Model plus Tutorial 3 output | | 5 | `tutorials/tutorial_05_vtk_to_usd.py` | VTK surfaces to animated USD | output of tutorial 2 | | 6 | `tutorials/tutorial_06_reconstruct_highres_4d_ct.py` | Reconstruct high-res 4D CT | DirLab-4DCT (manual) | +| 7 | `tutorials/tutorial_07_dirlab_pca_model.py` | Build a surface PCA lung-lobe model and fit all cases | DirLab-4DCT (manual) | +| 8 | `tutorials/tutorial_08_dirlab_pca_time_series.py` | Propagate PCA-fitted lung-lobe meshes through DirLab time series | DirLab-4DCT plus Tutorial 7 output | +| 9 | `tutorials/tutorial_09_physicsnemo_mesh_stage_model.py` | Train a PhysicsNeMo mesh stage model | Tutorial 8 output | -Each script is runnable directly: +Each tutorial is a `# %%` percent-cell Python script. Paths are defined near +the top of the script; edit those constants for custom data/output locations, +or use the installed `physiomotion4d-*` CLI commands when you want path +arguments. ```bash # Tutorial 1 (CPU-safe ANTs registration; requires Slicer-Heart-CT data) -python tutorials/tutorial_01_heart_gated_ct_to_usd.py \ - --data-dir ./data --output-dir ./output/tutorial_01 +python tutorials/tutorial_01_heart_gated_ct_to_usd.py # Tutorial 2 (CT to VTK) -python tutorials/tutorial_02_ct_to_vtk.py \ - --data-dir ./data --output-dir ./output/tutorial_02 +python tutorials/tutorial_02_ct_to_vtk.py ``` See `tutorials/README.md` for the full tutorial index, dataset preparation @@ -189,7 +193,7 @@ This quickstart uses the public Slicer-Heart 4D CT sample. Data downloading and a CUDA-capable GPU are required for practical runtime. ```bash -python -c "import pathlib, urllib.request; pathlib.Path('data/test').mkdir(parents=True, exist_ok=True); urllib.request.urlretrieve('https://github.com/SlicerHeart/SlicerHeart/releases/download/TestingData/TruncalValve_4DCT.seq.nrrd', 'data/test/TruncalValve_4DCT.seq.nrrd')" +python -c "from physiomotion4d import DataDownloadTools; DataDownloadTools.DownloadSlicerHeartCTData('data/test')" physiomotion4d-heart-gated-ct data/test/TruncalValve_4DCT.seq.nrrd \ --registration-method ants \ @@ -197,7 +201,7 @@ physiomotion4d-heart-gated-ct data/test/TruncalValve_4DCT.seq.nrrd \ --project-name slicer_heart_quickstart ``` -## 🎯 Quick Start +## Quick Start ### Command-Line Interface @@ -326,11 +330,14 @@ segmenter = SegmentChestTotalSegmentator() image = itk.imread("chest_ct.nrrd") masks = segmenter.segment(image, contrast_enhanced_study=True) -# Extract individual anatomy masks by key -heart_mask = masks["heart"] -vessels_mask = masks["major_vessels"] -lungs_mask = masks["lung"] +# Result always contains "labelmap" plus one entry per anatomy group the +# segmenter registered (heart, lung, bone, major_vessels, soft_tissue, +# contrast, other for SegmentChestTotalSegmentator). The exact key set is +# segmenter-specific; check membership when targeting multiple segmenters. labelmap = masks["labelmap"] +heart_mask = masks["heart"] +if "lung" in masks: + lungs_mask = masks["lung"] ``` ### Image Registration @@ -371,17 +378,22 @@ PhysioMotion4D provides two APIs for converting VTK data to USD for NVIDIA Omniv #### Option 1: High-Level ConvertVTKToUSD (for PyVista/VTK objects) ```python -from physiomotion4d import ConvertVTKToUSD +from physiomotion4d import ConvertVTKToUSD, SegmentChestTotalSegmentator import pyvista as pv # Load VTK data meshes = [pv.read(f"cardiac_frame_{i:03d}.vtp") for i in range(20)] -# Convert to animated USD with anatomical labels +# Convert to animated USD with anatomical labels. Pass `segmenter` so the +# converter groups labeled prims by anatomy type: +# /World/CardiacModel/heart/, /World/CardiacModel/lung/, ... +# Without `segmenter`, all labeled prims land under /World/CardiacModel/Anatomy. +seg = SegmentChestTotalSegmentator() converter = ConvertVTKToUSD( data_basename='CardiacModel', input_polydata=meshes, - mask_ids={1: 'ventricle', 2: 'atrium', 3: 'vessels'}, + mask_ids=seg.taxonomy.all_labels(), + segmenter=seg, compute_normals=True ) @@ -463,98 +475,103 @@ Classes that inherit from `PhysioMotion4DBase` provide: - Class-based log filtering - Unified logging interface across the package -## 📊 Experiments and Examples +## Experiments and Examples -The `experiments/` directory contains comprehensive Jupyter notebooks demonstrating the complete PhysioMotion4D pipeline: +The `experiments/` directory contains research scripts that shaped the +toolkit. They are `# %%` percent-cell Python scripts that can be run +top-to-bottom (`python