Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Runs on every push and pull request to main branches. Includes:
- Manually triggered via workflow_dispatch, OR
- PR has the `run-gpu-tests` label
- Requires self-hosted runner with `[self-hosted, linux, gpu]` labels
- Uses PyTorch with CUDA 12.6 support
- Uses PyTorch with CUDA 13.0 support
- Timeout: 30 minutes

- **code-quality**: Static code analysis
Expand Down Expand Up @@ -87,7 +87,7 @@ To run GPU tests, you must either:

GPU tests require self-hosted runners with:
- Linux OS
- NVIDIA GPU with CUDA 12.6+ support
- NVIDIA GPU with CUDA 13.0 support
- Runner labels: `[self-hosted, linux, gpu]`

**Why are GPU tests disabled by default?**
Expand All @@ -107,13 +107,18 @@ GPU tests require self-hosted runners with:
# Install NVIDIA drivers
sudo apt-get install nvidia-driver-535

# Install CUDA toolkit 12.6
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.0-1_all.deb
sudo dpkg -i cuda-keyring_1.0-1_all.deb
# Install CUDA toolkit 13.0
# CUDA 13.0 requires Ubuntu 22.04 LTS or later. These cuda-keyring_1.1-1_all.deb
# and cuda-toolkit-13 commands fail on Ubuntu 20.04 runners.
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
sudo apt-get install cuda-toolkit-12-6
sudo apt-get install cuda-toolkit-13
```

Self-hosted GPU runners should be upgraded to Ubuntu 22.04 LTS or later
before installing CUDA 13.0.

3. **Configure Runner Labels**:
- Add labels: `self-hosted`, `linux`, `gpu`
- Verify GPU is accessible: `nvidia-smi`
Expand All @@ -140,8 +145,7 @@ GPU tests require self-hosted runners with:
**Option 3: Run Locally**
```bash
# Install with CUDA support
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install -e ".[test]"
uv pip install -e ".[test,cuda13]"

# Run GPU tests
pytest tests/ -v -m "not slow"
Expand Down Expand Up @@ -214,8 +218,7 @@ pytest tests/ -m "unit and not requires_gpu" --cov=physiomotion4d
### GPU Tests
```bash
# Install with CUDA support
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126
pip install -e ".[test]"
uv pip install -e ".[test,cuda13]"

# Run all tests (including GPU)
pytest tests/ -m "not slow"
Expand Down
52 changes: 37 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,28 +26,24 @@ PhysioMotion4D is a comprehensive medical imaging package that converts 4D CT sc
### Prerequisites

- Python 3.10+ (Python 3.10, 3.11, or 3.12 recommended)
- NVIDIA GPU with CUDA 13 (default) or CUDA 12 — recommended for production use; CPU-only installation is supported but slow
- 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)
- **Git LFS** (required for running tests: baseline files in `tests/baselines/` are stored with Git LFS; install from [git-lfs.github.com](https://git-lfs.github.com), then run `git lfs install` and `git lfs pull` after cloning)

### Installation from PyPI

```bash
# CPU-only install — works out of the box; a runtime warning points to the GPU extras
# CPU-only PyPI install — works out of the box; a runtime warning points to the GPU extra
pip install physiomotion4d

# CUDA 13 install (recommended for production)
uv pip install "physiomotion4d[cuda13]"

# CUDA 12 install
uv pip install "physiomotion4d[cuda12]"
```

The `[cuda13]` and `[cuda12]` extras install both CuPy and the matching
CUDA-built PyTorch wheel in one step — there is no need to install PyTorch
separately. PyTorch is listed in the extras so that uv's dependency resolver
fetches the GPU wheel from the PyTorch index instead of the CPU wheel from PyPI.
The `[cuda13]` extra installs CuPy. In uv-managed source environments, PyTorch,
torchvision, and torchaudio resolve from the CUDA 13.0 PyTorch wheel index.
There is no need to install PyTorch separately.

For development with NVIDIA NIM cloud services:
```bash
Expand Down Expand Up @@ -77,14 +73,11 @@ pip install physiomotion4d[nim]

4. **Install PhysioMotion4D**:
```bash
# CPU-only (evaluation / no GPU)
# CUDA 13 PyTorch is the default for uv-managed source environments
uv pip install -e "."

# CUDA 13 (recommended for production)
# Add CuPy for CUDA 13 GPU acceleration
uv pip install -e ".[cuda13]"

# CUDA 12
uv pip install -e ".[cuda12]"
```

### Verify Installation
Expand Down Expand Up @@ -136,11 +129,40 @@ print(f"PhysioMotion4D version: {physiomotion4d.__version__}")
### Key Dependencies

- **Medical Imaging**: ITK, TubeTK, MONAI, nibabel, PyVista
- **AI/ML**: PyTorch, CuPy (CUDA 13 default; CUDA 12 via `[cuda12]` extra), transformers, MONAI
- **AI/ML**: PyTorch, CuPy (CUDA 13), transformers, MONAI
- **Registration**: icon-registration, unigradicon
- **Visualization**: USD-core, PyVista
- **Segmentation**: TotalSegmentator

## Getting Started: Tutorials

The `tutorials/` directory contains six end-to-end Python scripts, one for each
major workflow. They are the recommended starting point for new users.

| # | Script | Workflow | Dataset |
|---|--------|----------|---------|
| 1 | `tutorials/tutorial_01_heart_gated_ct_to_usd.py` | Heart-gated CT to animated USD | Slicer-Heart-CT (prepare first) |
| 2 | `tutorials/tutorial_02_ct_to_vtk.py` | CT to VTK surfaces | Slicer-Heart-CT (prepare first) |
| 3 | `tutorials/tutorial_03_fit_statistical_model_to_patient.py` | Fit statistical model to patient | KCL-Heart-Model (manual) |
| 4 | `tutorials/tutorial_04_create_statistical_model.py` | Build PCA shape model | KCL-Heart-Model (manual) |
| 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) |

Each script is runnable directly:

```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

# Tutorial 2 (CT to VTK)
python tutorials/tutorial_02_ct_to_vtk.py \
--data-dir ./data --output-dir ./output/tutorial_02
```

See `tutorials/README.md` for the full tutorial index, dataset preparation
instructions, recommended run order, and experiment-test instructions.

## 🎯 Quick Start

### Command-Line Interface
Expand Down
77 changes: 62 additions & 15 deletions docs/API_MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
## docs/conf.py

- **class Mock** (line 20)
- `def autodoc_skip_member(app, what, name, obj, skip, options)` (line 215): Custom function to skip certain members during autodoc processing.
- `def setup(app)` (line 223): Custom setup function for Sphinx.
- `def autodoc_skip_member(app, what, name, obj, skip, options)` (line 216): Custom function to skip certain members during autodoc processing.
- `def setup(app)` (line 224): Custom setup function for Sphinx.

## experiments/Colormap-VTK_To_USD/colormap_vtk_to_usd.py

Expand Down Expand Up @@ -286,18 +286,20 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
## src/physiomotion4d/test_tools.py

- `def set_create_baseline_if_missing(value)` (line 28): Set whether to create baseline files when missing (used by pytest conftest).
- **class TestTools** (line 34): Utilities for pytest image comparison: baseline directory, results directory,
- `def __init__(self, results_dir, baselines_dir, class_name, *, log_level=logging.INFO)` (line 44)
- `def image_pass_fail_and_pixels_above_tolerance(self)` (line 78): Return (pass, value) for number of pixels above tolerance from the most
- `def image_pass_fail_and_total_absolute_error(self)` (line 93): Return (pass, value) for total absolute error from the most recent
- `def image_difference(self)` (line 108): Return the difference image (itk.Image) from the most recent
- `def transform_pass_fail_and_number_of_values_above_tolerance(self)` (line 115): Return (pass, value) for number of values above tolerance from the most recent compare_result_to_baseline_transform call.
- `def transform_pass_fail_and_total_absolute_error(self)` (line 131): Return (pass, value) for total absolute error from the most recent compare_result_to_baseline_transform call.
- `def transform_difference(self)` (line 145): Return the difference transform (itk.Transform) from the most recent compare_result_to_baseline_transform call.
- `def write_result_image(self, image, filename)` (line 151): Write the image to the results directory.
- `def write_result_transform(self, transform, filename)` (line 155): Write the transform to the results directory.
- `def compare_result_to_baseline_transform(self, filename, *, per_value_absolute_error_tol=0.0, max_number_of_values_above_tol=0, total_absolute_error_tol=0.0)` (line 161): Compare the transform to the baseline transform.
- `def compare_result_to_baseline_image(self, filename, *, per_pixel_absolute_error_tol=0.0, max_number_of_pixels_above_tol=0, total_absolute_error_tol=0.0)` (line 239): Load a 3D result image and a 3D baseline image (.mha), compare the full
- **class TestTools** (line 34): Utilities for pytest image comparison: baseline directory, result directory,
- `def __init__(self, results_dir, baselines_dir, class_name, *, results_output_dir=None, log_level=logging.INFO)` (line 44): Initialize test helpers.
- `def image_pass_fail_and_pixels_above_tolerance(self)` (line 95): Return (pass, value) for number of pixels above tolerance from the most
- `def image_pass_fail_and_total_absolute_error(self)` (line 110): Return (pass, value) for total absolute error from the most recent
- `def image_difference(self)` (line 125): Return the difference image (itk.Image) from the most recent
- `def transform_pass_fail_and_number_of_values_above_tolerance(self)` (line 132): Return (pass, value) for number of values above tolerance from the most recent compare_result_to_baseline_transform call.
- `def transform_pass_fail_and_total_absolute_error(self)` (line 148): Return (pass, value) for total absolute error from the most recent compare_result_to_baseline_transform call.
- `def transform_difference(self)` (line 162): Return the difference transform (itk.Transform) from the most recent compare_result_to_baseline_transform call.
- `def write_result_image(self, image, filename)` (line 168): Write the image to the configured result artifact directory.
- `def write_result_transform(self, transform, filename)` (line 172): Write the transform to the configured result artifact directory.
- `def compare_result_to_baseline_transform(self, filename, *, per_value_absolute_error_tol=0.0, max_number_of_values_above_tol=0, total_absolute_error_tol=0.0)` (line 178): Compare the transform to the baseline transform.
- `def compare_result_to_baseline_image(self, filename, *, per_pixel_absolute_error_tol=0.0, max_number_of_pixels_above_tol=0, total_absolute_error_tol=0.0)` (line 256): Load a 3D result image and a 3D baseline image (.mha), compare the full
- `def save_screenshot_mesh(self, mesh, filename, *, camera_position='iso', window_size=(800, 600), color='pink', opacity=0.9)` (line 363): Render a PyVista mesh off-screen and save a PNG.
- `def save_screenshot_image_slice(self, image, filename, *, axis=0, slice_fraction=0.5, colormap='gray', vmin=None, vmax=None, overlay_mask=None, overlay_alpha=0.4)` (line 412): Extract one slice from an ITK image and save a PNG via matplotlib.

## src/physiomotion4d/transform_tools.py

Expand Down Expand Up @@ -378,7 +380,7 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
- **class UsdMeshConverter** (line 25): Converts MeshData to UsdGeomMesh with full feature support.
- `def __init__(self, stage, settings, material_mgr)` (line 36): Initialize mesh converter.
- `def create_mesh(self, mesh_data, mesh_path, time_code=None, bind_material=True)` (line 53): Create a UsdGeomMesh from MeshData.
- `def create_time_varying_mesh(self, mesh_data_sequence, mesh_path, time_codes, bind_material=True)` (line 288): Create a mesh with time-varying attributes.
- `def create_time_varying_mesh(self, mesh_data_sequence, mesh_path, time_codes, bind_material=True)` (line 289): Create a mesh with time-varying attributes.

## src/physiomotion4d/vtk_to_usd/usd_utils.py

Expand Down Expand Up @@ -684,6 +686,27 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
- `def test_multiple_transform_applications(self, transform_tools, test_transforms, test_images)` (line 628): Test applying multiple transforms in sequence.
- `def test_identity_transform(self, transform_tools, test_images)` (line 656): Test that identity transform doesn't change the image.

## tests/test_tutorials.py

- `def test_testtools_results_output_dir_override(tmp_path)` (line 78): Store result artifacts in an explicit directory when requested.
- `def test_tutorial_01_contour_png_mesh_uses_current_run_results()` (line 103): Select current in-memory contours instead of disk VTP outputs.
- `def test_tutorial_01_reference_png_uses_workflow_fixed_image(tmp_path)` (line 119): Select the actual workflow reference image over cached slice images.
- `def test_tutorial_01_overlay_uses_workflow_fixed_segmentation(tmp_path)` (line 134): Select the current fixed labelmap over cached slice labelmaps.
- `def test_tutorial_01_overlay_falls_back_to_fixed_image_mask(tmp_path)` (line 149): Read fixed_image_mask.mha before stale slice labelmap files.
- **class TestTutorial01HeartGatedCTToUSD** (line 176): End-to-end test for tutorial_01_heart_gated_ct_to_usd.py.
- `def test_run(self, test_directories)` (line 181)
- `def test_tutorial_03_extract_surface_uses_dataset_surface()` (line 207): Use the robust dataset_surface algorithm for VTK surface extraction.
- **class TestTutorial02CTToVTK** (line 227): End-to-end test for tutorial_02_ct_to_vtk.py.
- `def test_run(self, test_directories)` (line 232)
- **class TestTutorial03FitStatisticalModelToPatient** (line 260): End-to-end test for tutorial_03_fit_statistical_model_to_patient.py.
- `def test_run(self, test_directories)` (line 265)
- **class TestTutorial04CreateStatisticalModel** (line 298): End-to-end test for tutorial_04_create_statistical_model.py.
- `def test_run(self, test_directories)` (line 303)
- **class TestTutorial05VTKToUSD** (line 339): End-to-end test for tutorial_05_vtk_to_usd.py.
- `def test_run(self, test_directories)` (line 344)
- **class TestTutorial06ReconstructHighres4DCT** (line 389): End-to-end test for tutorial_06_reconstruct_highres_4d_ct.py.
- `def test_run(self, test_directories)` (line 394)

## tests/test_usd_merge.py

- `def analyze_usd_file(filepath)` (line 17): Analyze a USD file for materials and time samples.
Expand Down Expand Up @@ -738,6 +761,30 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
- `def test_normals_remain_unit_length(self, tmp_path)` (line 342): Normal vectors must not be scaled.
- `def test_stage_meters_per_unit(self, tmp_path)` (line 362): Stage metersPerUnit metadata must be 1.0.

## tutorials/tutorial_01_heart_gated_ct_to_usd.py

- `def run_tutorial(data_dir, output_dir, *, registration_method='ants', log_level=logging.INFO)` (line 164): Run Tutorial 1: Heart-Gated CT to Animated USD.

## tutorials/tutorial_02_ct_to_vtk.py

- `def run_tutorial(data_dir, output_dir, *, log_level=logging.INFO)` (line 90): Run Tutorial 2: CT Segmentation to VTK Surfaces.

## tutorials/tutorial_03_fit_statistical_model_to_patient.py

- `def run_tutorial(data_dir, output_dir, *, log_level=logging.INFO)` (line 99): Run Tutorial 3: Fit Statistical Shape Model to Patient Data.

## tutorials/tutorial_04_create_statistical_model.py

- `def run_tutorial(data_dir, output_dir, *, pca_components=10, max_samples=20, log_level=logging.INFO)` (line 93): Run Tutorial 4: Create a PCA Statistical Shape Model.

## tutorials/tutorial_05_vtk_to_usd.py

- `def run_tutorial(data_dir, output_dir, *, vtk_file=None, log_level=logging.INFO)` (line 80): Run Tutorial 5: VTK Surface Series to Animated USD.

## tutorials/tutorial_06_reconstruct_highres_4d_ct.py

- `def run_tutorial(data_dir, output_dir, *, case=1, max_frames=4, registration_method='ants', log_level=logging.INFO)` (line 97): Run Tutorial 6: Reconstruct High-Resolution 4D CT.

## utils/claude_github_reviews.py

- `def git_fetch(repo_root, remote, branch)` (line 67): Run ``git fetch <remote> <branch>``, printing progress.
Expand Down
Loading
Loading