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
4 changes: 2 additions & 2 deletions .agents/agents/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ and the API map accurate and concise.
- Read the changed code before writing any docs.
- Keep docstrings factual — describe what the code does, not what you wish it did.
- State image/tensor shapes and axis orders explicitly:
e.g. `Returns an ITK image with shape (X, Y, Z, T) in RAS world space.`
e.g. `Returns an ITK image with shape (X, Y, Z, T) in LPS world space.`
- Double quotes for docstrings; single quotes for inline strings.
- Do **not** create new `.md` files unless explicitly asked.
- After any public API change, regenerate: `python utils/generate_api_map.py`
Expand All @@ -34,7 +34,7 @@ def register(self, moving_image: itk.Image) -> dict[str, Any]:
Parameters
----------
moving_image : itk.Image
3-D image in RAS world space, shape (X, Y, Z).
3-D image in LPS world space, shape (X, Y, Z).

Returns
-------
Expand Down
2 changes: 1 addition & 1 deletion .agents/agents/implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Key modules: `physiomotion4d_base.py`, `segment_chest_*.py`, `register_images_*.

## Data shapes — state them explicitly

- ITK images: axes X, Y, Z [, T] in RAS world space.
- ITK images: axes X, Y, Z [, T] in LPS world space (ITK's native frame).
- 4D time series: shape `(X, Y, Z, T)`. Never silently squeeze or permute.
- PyVista surfaces: RAS internally; Y-up only at USD export.
- Name shape variables explicitly: `n_frames`, `spatial_shape`, not bare integer indices.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ jobs:

- name: Run data conversion tests
run: |
pytest tests/test_convert_nrrd_4d_to_3d.py -v --cov=physiomotion4d --cov-append --cov-report=xml
pytest tests/test_convert_image_4d_to_3d.py -v --cov=physiomotion4d --cov-append --cov-report=xml
continue-on-error: true

- name: Run contour tools tests
Expand Down
7 changes: 5 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,12 @@ Consult `docs/API_MAP.md` for the full index of classes, methods, and signatures
Regenerate it after any public API change: `py utils/generate_api_map.py`

**Key data conventions:**
- Images: `itk.Image`, axes X, Y, Z [, T] in RAS world space
- Images: `itk.Image`, axes X, Y, Z [, T] in LPS world space (ITK's native
frame; `itk.imread` normalizes DICOM, NIfTI, MHA, and NRRD inputs to LPS)
- 4D time series: shape `(X, Y, Z, T)` — never silently squeeze or permute axes
- Surfaces: `pv.PolyData` in RAS; converted to Y-up only at USD export
- Surfaces: `pv.PolyData` in LPS (inherited from the source `itk.Image` via
`itk.vtk_image_from_image`); converted to USD right-handed Y-up only at USD
export by `vtk_to_usd.lps_points_to_usd` (USD +X=Left, +Y=Superior, +Z=Anterior)
- Masks: ITK images with integer labels; consistent anatomy group IDs across all segmenters
- Transforms: ITK composite transforms stored in `.hdf` files
- State axis order and shape explicitly in every docstring and comment that touches arrays
Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,18 @@ There is no need to install PyTorch separately.

```python
import physiomotion4d
from physiomotion4d import WorkflowConvertHeartGatedCTToUSD
from physiomotion4d import WorkflowConvertImageToUSD

print(f"PhysioMotion4D version: {physiomotion4d.__version__}")
print(WorkflowConvertHeartGatedCTToUSD.__name__)
print(WorkflowConvertImageToUSD.__name__)
```

## Package Architecture

### Core Components

- **Workflow Classes**: Complete end-to-end pipeline processors
- `WorkflowConvertHeartGatedCTToUSD`: Heart-gated CT to USD processing workflow
- `WorkflowConvertImageToUSD`: 3D/4D image to USD processing workflow
- `WorkflowCreateStatisticalModel`: Create PCA statistical shape model from sample meshes
- `WorkflowFitStatisticalModelToPatient`: Model-to-patient registration workflow
- **Segmentation Classes**: Multiple AI-based chest segmentation implementations
Expand Down Expand Up @@ -195,7 +195,7 @@ a CUDA-capable GPU are required for practical runtime.
```bash
python -c "from physiomotion4d import DataDownloadTools; DataDownloadTools.DownloadSlicerHeartCTData('data/test')"

physiomotion4d-heart-gated-ct data/test/TruncalValve_4DCT.seq.nrrd \
physiomotion4d-convert-image-to-usd data/test/TruncalValve_4DCT.seq.nrrd \
--registration-method ants \
--output-dir output/quickstart \
--project-name slicer_heart_quickstart
Expand All @@ -213,13 +213,13 @@ Process 4D cardiac CT images into dynamic USD models:

```bash
# Process a single 4D cardiac CT file
physiomotion4d-heart-gated-ct cardiac_4d.nrrd --contrast --output-dir ./results
physiomotion4d-convert-image-to-usd cardiac_4d.nrrd --contrast --output-dir ./results

# Process multiple time frames
physiomotion4d-heart-gated-ct frame_*.nrrd --contrast --project-name patient_001
physiomotion4d-convert-image-to-usd frame_*.nrrd --contrast --project-name patient_001

# With custom settings
physiomotion4d-heart-gated-ct cardiac.nrrd \
physiomotion4d-convert-image-to-usd cardiac.nrrd \
--contrast \
--reference-image ref.mha \
--registration-iterations 50 \
Expand Down Expand Up @@ -278,10 +278,10 @@ For implementation details and advanced usage, see the CLI modules in `src/physi
### Python API - Basic Heart-Gated CT Processing

```python
from physiomotion4d import WorkflowConvertHeartGatedCTToUSD
from physiomotion4d import WorkflowConvertImageToUSD

# Initialize processor
processor = WorkflowConvertHeartGatedCTToUSD(
processor = WorkflowConvertImageToUSD(
input_filenames=["path/to/cardiac_4d_ct.nrrd"],
contrast_enhanced=True,
output_directory="./results",
Expand Down Expand Up @@ -443,7 +443,7 @@ stage = convert_vtk_file(
```

Features:
- Automatic coordinate system conversion (RAS to Y-up)
- Automatic coordinate system conversion (LPS to USD right-handed Y-up)
- Material system with UsdPreviewSurface
- Preserves all VTK data arrays as USD primvars
- Supports VTP, VTK, and VTU file formats
Expand Down Expand Up @@ -721,7 +721,7 @@ Use `/impl` for end-to-end implementation: read → summarize → plan → diff
```

```text
/impl fix the RAS-to-Y-up transform being applied twice in vtk_to_usd/usd_utils.py
/impl fix the LPS-to-Y-up transform being applied twice in vtk_to_usd/usd_utils.py
```

The agent will read the affected module, propose a numbered plan, implement in the
Expand Down
6 changes: 3 additions & 3 deletions data/Slicer-Heart-CT/download_and_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
import shutil

from physiomotion4d.convert_nrrd_4d_to_3d import ConvertNRRD4DTo3D
from physiomotion4d.convert_image_4d_to_3d import ConvertImage4DTo3D
from physiomotion4d.data_download_tools import DataDownloadTools

# %%
Expand All @@ -19,8 +19,8 @@
input_image_filename = DataDownloadTools.DownloadSlicerHeartCTData(data_dir)

# %%
conv = ConvertNRRD4DTo3D()
conv.load_nrrd_4d(str(input_image_filename))
conv = ConvertImage4DTo3D()
conv.load_image_4d(str(input_image_filename))
conv.save_3d_images(output_dir, "slice")

# Save the mid-stroke slice as the fixed/reference image
Expand Down
65 changes: 34 additions & 31 deletions docs/API_MAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,13 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._

- `def main()` (line 26): CLI entry point for CT to VTK conversion.

## src/physiomotion4d/cli/convert_heart_gated_ct_to_usd.py
## src/physiomotion4d/cli/convert_image_4d_to_3d.py

- `def main()` (line 14): Command-line interface for Heart-gated CT processing.
- `def main()` (line 20): CLI entry point for 4D-to-3D image conversion.

## src/physiomotion4d/cli/convert_image_to_usd.py

- `def main()` (line 14): Command-line interface for the Image-to-USD workflow.

## src/physiomotion4d/cli/convert_vtk_to_usd.py

Expand Down Expand Up @@ -102,15 +106,14 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
- `def create_distance_map(self, mesh, reference_image, squared_distance=False, negative_inside=True, zero_inside=False, norm_to_max_distance=0.0)` (line 257)
- `def create_deformation_field(self, points, point_displacements, reference_image, blur_sigma=2.5, ptype=itk.D)` (line 324): Create a displacement map from model points and displacements.

## src/physiomotion4d/convert_nrrd_4d_to_3d.py
## src/physiomotion4d/convert_image_4d_to_3d.py

- **class ConvertNRRD4DTo3D** (line 13)
- `def __init__(self, log_level=logging.INFO)` (line 14): Initialize the NRRD 4D to 3D converter.
- `def load_nrrd_3d(self, filenames)` (line 24)
- `def load_nrrd_4d(self, filename)` (line 30)
- `def get_3d_image(self, index)` (line 64)
- `def get_number_of_3d_images(self)` (line 67)
- `def save_3d_images(self, directory, basename)` (line 70)
- **class ConvertImage4DTo3D** (line 34): Split a 3D/4D ITK image (X, Y, Z [, T]) into a list of 3D ITK images.
- `def __init__(self, log_level=logging.INFO)` (line 37): Initialize the 4D-to-3D image converter.
- `def load_image_4d(self, filename)` (line 62): Load a 3D or 4D image and populate ``self.img_3d`` with 3D frames.
- `def get_3d_image(self, index)` (line 269): Return the 3D ITK image at the given time index.
- `def get_number_of_3d_images(self)` (line 273): Return the number of 3D images currently held.
- `def save_3d_images(self, directory, basename, suffix='mha')` (line 277): Write each held 3D image to ``{directory}/{basename}_{i:03d}.{suffix}``.

## src/physiomotion4d/convert_vtk_to_usd.py

Expand Down Expand Up @@ -399,16 +402,16 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._

## src/physiomotion4d/vtk_to_usd/usd_utils.py

- `def ras_to_usd(point)` (line 20): Convert RAS (Right-Anterior-Superior) coordinates to USD's right-handed Y-up system.
- `def ras_points_to_usd(points)` (line 55): Convert array of RAS points (mm) to USD coordinates (m).
- `def ras_normals_to_usd(normals)` (line 78): Convert array of RAS normals to USD Y-up coordinates.
- `def numpy_to_vt_array(array, data_type)` (line 101): Convert numpy array to appropriate VtArray type.
- `def get_sdf_value_type(data_type, num_components)` (line 173): Get appropriate SDF value type for primvar creation.
- `def sanitize_primvar_name(name)` (line 220): Sanitize a name to be USD-compliant.
- `def create_primvar(geom, array, array_name_prefix='', time_code=None)` (line 255): Create a USD primvar from a GenericArray.
- `def triangulate_face(face_counts, face_indices)` (line 369): Triangulate polygonal faces using fan triangulation.
- `def compute_mesh_extent(points)` (line 420): Compute bounding box extent for a mesh.
- `def add_framing_camera(stage, *, parent_path='/World', name='Camera', bounds_min=None, bounds_max=None, focal_length_mm=50.0, horizontal_aperture_mm=36.0, distance_factor=3.0)` (line 432): Define a USD camera that frames stage geometry with tight clipping planes.
- `def lps_to_usd(point)` (line 20): Convert LPS (Left-Posterior-Superior) coordinates to USD's right-handed Y-up frame.
- `def lps_points_to_usd(points)` (line 58): Convert array of LPS points (mm) to USD Y-up coordinates (m).
- `def lps_normals_to_usd(normals)` (line 82): Convert array of LPS normals to USD Y-up coordinates.
- `def numpy_to_vt_array(array, data_type)` (line 105): Convert numpy array to appropriate VtArray type.
- `def get_sdf_value_type(data_type, num_components)` (line 177): Get appropriate SDF value type for primvar creation.
- `def sanitize_primvar_name(name)` (line 224): Sanitize a name to be USD-compliant.
- `def create_primvar(geom, array, array_name_prefix='', time_code=None)` (line 259): Create a USD primvar from a GenericArray.
- `def triangulate_face(face_counts, face_indices)` (line 373): Triangulate polygonal faces using fan triangulation.
- `def compute_mesh_extent(points)` (line 424): Compute bounding box extent for a mesh.
- `def add_framing_camera(stage, *, parent_path='/World', name='Camera', bounds_min=None, bounds_max=None, focal_length_mm=50.0, horizontal_aperture_mm=36.0, distance_factor=3.0)` (line 436): Define a USD camera that frames stage geometry with tight clipping planes.

## src/physiomotion4d/vtk_to_usd/vtk_reader.py

Expand All @@ -432,11 +435,11 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
- `def save_combined_surface(surfaces, output_dir, prefix='')` (line 397): Merge all group surfaces into a single VTP file.
- `def save_combined_mesh(meshes, output_dir, prefix='')` (line 432): Merge all group meshes into a single VTU file.

## src/physiomotion4d/workflow_convert_heart_gated_ct_to_usd.py
## src/physiomotion4d/workflow_convert_image_to_usd.py

- **class WorkflowConvertHeartGatedCTToUSD** (line 28): Complete workflow for Heart-gated CT images to dynamic USD models.
- `def __init__(self, input_filenames, contrast_enhanced, output_directory, project_name, reference_image_filename=None, number_of_registration_iterations=1, registration_method='icon', log_level=logging.INFO, save_registered_images=True, save_registration_transforms=True, save_labelmaps=True)` (line 36): Initialize the Heart-gated CT to USD workflow.
- `def process(self)` (line 177): Execute the complete workflow from 4D CT to dynamic USD models.
- **class WorkflowConvertImageToUSD** (line 30): Complete workflow for converting 4D CT images to dynamic USD models.
- `def __init__(self, input_filenames, contrast_enhanced, output_directory, project_name, reference_image_filename=None, number_of_registration_iterations=1, registration_method='icon', log_level=logging.INFO, save_registered_images=True, save_registration_transforms=True, save_labelmaps=True)` (line 38): Initialize the image-to-USD workflow.
- `def process(self)` (line 184): Execute the complete workflow from 4D CT to dynamic USD models.

## src/physiomotion4d/workflow_convert_vtk_to_usd.py

Expand Down Expand Up @@ -520,7 +523,7 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._

## tests/test_cli_smoke.py

- `def test_cli_help(module_name, monkeypatch, capsys)` (line 23): Each CLI module exits successfully for --help.
- `def test_cli_help(module_name, monkeypatch, capsys)` (line 24): Each CLI module exits successfully for --help.

## tests/test_contour_tools.py

Expand All @@ -535,13 +538,13 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
- `def test_transform_contours_with_deformation(self, contour_tools, test_labelmaps, test_directories)` (line 267): Test transforming contours with deformation magnitude calculation.
- `def test_contours_from_both_time_points(self, contour_tools, test_labelmaps, test_directories)` (line 316): Test extracting contours from both time points.

## tests/test_convert_nrrd_4d_to_3d.py
## tests/test_convert_image_4d_to_3d.py

- **class TestConvertNRRD4DTo3D** (line 17): Test suite for converting 4D NRRD to 3D time series.
- `def test_convert_4d_to_3d(self, download_test_data, test_directories)` (line 20): Test conversion of 4D NRRD to 3D time series.
- `def test_slice_files_created(self, download_test_data, test_directories)` (line 46): Test that all expected slice files are present after conversion.
- `def test_load_nrrd_4d(self, download_test_data)` (line 67): Test loading 4D NRRD file.
- `def test_save_3d_images(self, download_test_data, test_directories)` (line 80): Test saving 3D images from 4D NRRD.
- **class TestConvertImage4DTo3D** (line 17): Test suite for converting a 4D image to a 3D time series.
- `def test_convert_4d_to_3d(self, download_test_data, test_directories)` (line 20): Test conversion of 4D image to 3D time series.
- `def test_slice_files_created(self, download_test_data, test_directories)` (line 43): Test that all expected slice files are present after conversion.
- `def test_load_image_4d(self, download_test_data)` (line 66): Test loading a 4D image.
- `def test_save_3d_images(self, download_test_data, test_directories)` (line 77): Test saving 3D images from a 4D source.

## tests/test_convert_vtk_to_usd.py

Expand Down
8 changes: 4 additions & 4 deletions docs/PYPI_RELEASE_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ python -c "import physiomotion4d; print(physiomotion4d.__version__)"

# Test CLI commands
physiomotion4d --help
physiomotion4d-heart-gated-ct --help
physiomotion4d-convert-image-to-usd --help
```

## Building the Package
Expand Down Expand Up @@ -159,15 +159,15 @@ Note: `--extra-index-url` is needed because dependencies are on PyPI, not TestPy
```python
# Test imports
import physiomotion4d
from physiomotion4d import WorkflowConvertHeartGatedCTToUSD
from physiomotion4d import WorkflowConvertImageToUSD

print(f"Version: {physiomotion4d.__version__}")
print(WorkflowConvertHeartGatedCTToUSD.__name__)
print(WorkflowConvertImageToUSD.__name__)
```

```bash
# Test CLI
physiomotion4d-heart-gated-ct --help
physiomotion4d-convert-image-to-usd --help
```

## Publishing to PyPI
Expand Down
10 changes: 0 additions & 10 deletions docs/api/cli/convert_heart_gated_ct_to_usd.rst

This file was deleted.

10 changes: 10 additions & 0 deletions docs/api/cli/convert_image_to_usd.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
==========================
convert_image_to_usd (CLI)
==========================

.. automodule:: physiomotion4d.cli.convert_image_to_usd
:members:
:undoc-members:
:show-inheritance:

See :doc:`../../cli_scripts/heart_gated_ct` for usage examples.
2 changes: 1 addition & 1 deletion docs/api/cli/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Module Index.
.. toctree::
:maxdepth: 1

convert_heart_gated_ct_to_usd
convert_image_to_usd
convert_ct_to_vtk
convert_vtk_to_usd
create_statistical_model
Expand Down
2 changes: 1 addition & 1 deletion docs/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ By Category
* :class:`~physiomotion4d.PhysioMotion4DBase` - Base class for all components

**Workflows**
* :class:`~physiomotion4d.WorkflowConvertHeartGatedCTToUSD` - Heart CT to USD
* :class:`~physiomotion4d.WorkflowConvertImageToUSD` - Heart CT to USD
* :class:`~physiomotion4d.WorkflowCreateStatisticalModel` - Create PCA statistical shape model
* :class:`~physiomotion4d.WorkflowFitStatisticalModelToPatient` - Heart model registration

Expand Down
22 changes: 22 additions & 0 deletions docs/api/utilities/image_conversion.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
====================================
4D Image Conversion
====================================

.. currentmodule:: physiomotion4d

Utilities for converting 4D medical images into a 3D time-series sequence.
``.nrrd`` inputs (including Slicer ``.seq.nrrd`` heart sequences, whose
per-voxel vector dimension exceeds ITK Python's wrapped Vector sizes) are read
with ``pynrrd``; all other formats (NIfTI ``.nii.gz``, MHA, …) are read with
``itk.imread`` and must be true 4-dimensional images.

Module Reference
================

.. automodule:: physiomotion4d.convert_image_4d_to_3d
:members:
:undoc-members:

.. rubric:: Navigation

:doc:`contour_tools` | :doc:`index` | :doc:`../index`
6 changes: 3 additions & 3 deletions docs/api/utilities/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Utility modules provide low-level operations:
* **Image Tools**: Image I/O, preprocessing, manipulation
* **Transform Tools**: Coordinate transforms and warping
* **Contour Tools**: Contour extraction and processing
* **NRRD Conversion**: 4D NRRD to 3D conversion utilities
* **4D Image Conversion**: 4D image to 3D time-series conversion utilities

Quick Links
===========
Expand All @@ -23,7 +23,7 @@ Quick Links
* :doc:`image_tools` - Image processing utilities
* :doc:`transform_tools` - Transform operations
* :doc:`contour_tools` - Contour processing
* :doc:`nrrd_conversion` - NRRD file utilities
* :doc:`image_conversion` - 4D image to 3D time-series utilities
* :doc:`test_tools` - Baseline / result comparison helpers
* :doc:`data_download` - Optional dataset download helpers

Expand All @@ -36,7 +36,7 @@ Module Documentation
image_tools
transform_tools
contour_tools
nrrd_conversion
image_conversion
test_tools
data_download

Expand Down
18 changes: 0 additions & 18 deletions docs/api/utilities/nrrd_conversion.rst

This file was deleted.

Loading
Loading