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
2 changes: 0 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ Version bumping: `bumpver update --patch`, `--minor`, or `--major`.
- Masks are ITK images with integer labels. Keep anatomy group IDs consistent
across segmenters.
- Transforms are ITK composite transforms stored in compressed `.hdf` files.
- State axis order and shape explicitly in every docstring and comment that
touches arrays.

## Implementation Role

Expand Down
1 change: 0 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ Regenerate it after any public API change: `py utils/generate_api_map.py`
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 with compression
- State axis order and shape explicitly in every docstring and comment that touches arrays

## Testing

Expand Down
5 changes: 2 additions & 3 deletions docs/api/registration/time_series.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ Time-Series Registration
.. currentmodule:: physiomotion4d

``RegisterTimeSeriesImages`` registers ordered 3D image phases to a reference
frame using ANTs, Greedy, ICON, or combined ``ANTS_ICON`` / ``greedy_ICON``
methods.
frame using Greedy, ICON, or combined ``Greedy_ICON`` methods.

Class Reference
===============
Expand All @@ -29,7 +28,7 @@ Basic Usage

images = [itk.imread(f"phase_{idx:02d}.mha") for idx in range(10)]

registrar = RegisterTimeSeriesImages(registration_method="ANTS")
registrar = RegisterTimeSeriesImages(registration_method="Greedy")
registrar.set_fixed_image(images[0])

result = registrar.register_time_series(
Expand Down
4 changes: 2 additions & 2 deletions docs/api/workflows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Convert Image to USD
output_directory="./results",
project_name="patient_001",
segmentation_method="ChestTotalSegmentator",
registration_method="ANTS",
registration_method="ICON",
)

final_usd = workflow.process()
Expand Down Expand Up @@ -157,7 +157,7 @@ High-Resolution 4D CT Reconstruction
time_series_images=time_series_images,
fixed_image=time_series_images[0],
reference_frame=0,
registration_method="ANTS",
registration_method="Greedy_ICON",
)

result = workflow.run_workflow(upsample_to_fixed_resolution=True)
Expand Down
4 changes: 2 additions & 2 deletions docs/cli_scripts/4dct_reconstruction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ Registration Options
physiomotion4d-reconstruct-highres-4d-ct \
--time-series-images frame_*.mha \
--fixed-image highres_reference.mha \
--registration-method ANTS \
--ANTS-iterations 30 15 7 3 \
--registration-method Greedy_ICON \
--Greedy-iterations 30 15 7 3 \
--prior-weight 0.5 \
--output-dir ./results

Expand Down
2 changes: 1 addition & 1 deletion docs/cli_scripts/best_practices.rst
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ Registration Failure
**Solutions**:
* Select better reference image (less motion, better quality)
* Increase ``--registration-iterations``
* Try alternative registration method (``--registration-method ANTS``)
* Try alternative registration method (``--registration-method Greedy``)
* Verify phases are temporally ordered correctly

Memory Errors
Expand Down
10 changes: 5 additions & 5 deletions docs/cli_scripts/heart_gated_ct.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ Optional Arguments
- Number of registration refinement iterations
* - ``--registration-method``
- ``ICON``
- Registration method: ``ICON`` or ``ANTS``
- Registration method: ``ICON`` or ``Greedy``

Processing Pipeline
===================
Expand Down Expand Up @@ -215,14 +215,14 @@ Research Dataset Processing
--project-name ${case_name}
done

With ANTs Registration
----------------------
With Greedy Registration
------------------------

.. code-block:: bash

physiomotion4d-convert-image-to-usd cardiac.nrrd \
--contrast \
--registration-method ANTS \
--registration-method Greedy \
--registration-iterations 50

Best Practices
Expand Down Expand Up @@ -289,7 +289,7 @@ Registration Failures
**Registration not converging**
* Try different reference image (better quality phase)
* Increase ``--registration-iterations``
* Switch registration method (``--registration-method ANTS``)
* Switch registration method (``--registration-method Greedy``)

**Excessive deformation**
* Verify sufficient temporal overlap between phases
Expand Down
2 changes: 1 addition & 1 deletion docs/developer/registration_images.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Time Series

images = [itk.imread(f"phase_{idx:02d}.mha") for idx in range(10)]

registrar = RegisterTimeSeriesImages(registration_method="ANTS")
registrar = RegisterTimeSeriesImages(registration_method="Greedy")
registrar.set_fixed_image(images[0])
result = registrar.register_time_series(
moving_images=images,
Expand Down
2 changes: 1 addition & 1 deletion docs/developer/workflows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Workflow Example
contrast_enhanced=True,
output_directory="./results",
project_name="patient_001",
registration_method="ANTS",
registration_method="ICON",
)

final_usd = workflow.process()
Expand Down
8 changes: 4 additions & 4 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Complete end-to-end cardiac CT processing:
contrast_enhanced=True,
output_directory="./results",
project_name="patient_001",
registration_method="ANTS",
registration_method="ICON",
)

# Run complete workflow
Expand Down Expand Up @@ -66,7 +66,7 @@ phase images:
time_series_images=time_series_images,
fixed_image=fixed_image,
reference_frame=0,
registration_method="ANTS",
registration_method="Greedy_ICON",
)

result = workflow.run_workflow(upsample_to_fixed_resolution=True)
Expand Down Expand Up @@ -411,7 +411,7 @@ Batch process multiple datasets:
contrast_enhanced=True,
output_directory=f"results/{patient_id}",
project_name=patient_id,
registration_method="ANTS",
registration_method="ICON",
)

try:
Expand Down Expand Up @@ -481,7 +481,7 @@ Run the supported end-to-end workflow API:
contrast_enhanced=True,
output_directory="./results",
project_name="cardiac_model",
registration_method="ANTS",
registration_method="ICON",
)

final_usd = workflow.process()
Expand Down
8 changes: 4 additions & 4 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ CUDA-capable GPU are required for practical runtime.
python -c "from physiomotion4d import DataDownloadTools; DataDownloadTools.DownloadSlicerHeartCTData('data/test')"

physiomotion4d-convert-image-to-usd data/test/TruncalValve_4DCT.seq.nrrd \
--registration-method ANTS \
--registration-method ICON \
--output-dir output/quickstart \
--project-name slicer_heart_quickstart

Expand Down Expand Up @@ -116,7 +116,7 @@ For more control, use the Python API:
contrast_enhanced=True,
output_directory="./results",
project_name="cardiac_model",
registration_method="ANTS",
registration_method="ICON",
)

**Step 3: Run the workflow**
Expand Down Expand Up @@ -151,7 +151,7 @@ For more control over individual steps:
contrast_enhanced=True,
output_directory="./results",
project_name="cardiac_model",
registration_method="ANTS",
registration_method="ICON",
)

final_usd = workflow.process()
Expand Down Expand Up @@ -314,7 +314,7 @@ Common Issues

* Resample or crop the input image before running the workflow
* Process fewer frames at once
* Use ANTs registration with ``--registration-method ANTS`` when CUDA is unavailable
* Use Greedy registration with ``--registration-method Greedy`` when CUDA is unavailable

**Segmentation quality issues**

Expand Down
4 changes: 2 additions & 2 deletions docs/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ CUDA Out of Memory
**Solutions**:

1. Resample or crop the input image before running the workflow.
2. Use ``--registration-method ANTS`` when CUDA is unavailable.
2. Use ``--registration-method Greedy`` when CUDA is unavailable.
3. Process fewer frames per run.

CUDA Version Mismatch
Expand Down Expand Up @@ -93,7 +93,7 @@ Registration Not Converging

.. code-block:: bash

physiomotion4d-convert-image-to-usd cardiac_4d.nrrd --registration-method ANTS
physiomotion4d-convert-image-to-usd cardiac_4d.nrrd --registration-method Greedy

3. Check image orientation and spacing

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ Script
``tutorials/tutorial_08_dirlab_pca_time_series.py``

Workflow
``RegisterTimeSeriesImages`` with ``registration_method='ANTS_ICON'`` and
``RegisterTimeSeriesImages`` with ``registration_method='Greedy_ICON'`` and
``TransformTools``

Dataset
Expand Down
22 changes: 11 additions & 11 deletions experiments/Reconstruct4DCT/reconstruct_4d_ct_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,22 @@
num_files = len(files)
reference_image_num = num_files // 2

# Registration parameters - only ANTs for quick run
registration_methods = ["ANTS", "ICON", "ANTS_ICON"]
number_of_iterations_list = [[8, 4, 1], 5, [[8, 4, 1], 5]] # For ANTs and ICON
# Registration parameters - only Greedy for quick run
registration_methods = ["Greedy", "ICON", "Greedy_ICON"]
number_of_iterations_list = [[8, 4, 1], 5, [[8, 4, 1], 5]] # For Greedy and ICON
Comment on lines +56 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify comment to match the actual registration methods list.

The comment says "only Greedy for quick run" but line 57 includes ["Greedy", "ICON", "Greedy_ICON"], which is more than just Greedy.

📝 Suggested fix
-    # Registration parameters - only Greedy for quick run
+    # Registration parameters - Greedy-based methods for quick run
     registration_methods = ["Greedy", "ICON", "Greedy_ICON"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experiments/Reconstruct4DCT/reconstruct_4d_ct_class.py` around lines 56 - 58,
Update the misleading comment above the registration configuration so it
accurately describes the contents of registration_methods and
number_of_iterations_list: either change the comment to list the three methods
("Greedy", "ICON", "Greedy_ICON") and note that number_of_iterations_list
corresponds to each method, or reduce registration_methods to only "Greedy" and
adjust number_of_iterations_list accordingly; refer to the variables
registration_methods and number_of_iterations_list to locate and update the
comment.

else:
print("=== FULL RUN MODE ===")
num_files = len(files)
files_indx = list(range(num_files))
reference_image_num = 7

# Registration parameters - both ANTs and ICON for full run
registration_methods = ["ANTS"] # , "ICON", "ANTS_ICON"]
# Registration parameters - Greedy and ICON for full run
registration_methods = ["Greedy"] # , "ICON", "Greedy_ICON"]
number_of_iterations_list = [
[30, 15, 7, 3],
] # For ANTs
] # For Greedy
# 20, # For ICON
# [[30, 15, 7, 3], 20], # For ANTS_ICON
# [[30, 15, 7, 3], 20], # For Greedy_ICON
# ]
Comment on lines +65 to 72

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update comment to match the active registration methods.

The comment mentions "Greedy and ICON for full run" but only ["Greedy"] is uncommented on line 66, with ICON and Greedy_ICON commented out.

📝 Suggested fix
-    # Registration parameters - Greedy and ICON for full run
+    # Registration parameters - Greedy for full run (ICON options commented out)
     registration_methods = ["Greedy"]  # , "ICON", "Greedy_ICON"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Registration parameters - Greedy and ICON for full run
registration_methods = ["Greedy"] # , "ICON", "Greedy_ICON"]
number_of_iterations_list = [
[30, 15, 7, 3],
] # For ANTs
] # For Greedy
# 20, # For ICON
# [[30, 15, 7, 3], 20], # For ANTS_ICON
# [[30, 15, 7, 3], 20], # For Greedy_ICON
# ]
# Registration parameters - Greedy for full run (ICON options commented out)
registration_methods = ["Greedy"] # , "ICON", "Greedy_ICON"]
number_of_iterations_list = [
[30, 15, 7, 3],
] # For Greedy
# 20, # For ICON
# [[30, 15, 7, 3], 20], # For Greedy_ICON
# ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@experiments/Reconstruct4DCT/reconstruct_4d_ct_class.py` around lines 65 - 72,
Update the inline comment above the registration configuration to reflect the
active methods: change the comment "Registration parameters - Greedy and ICON
for full run" to indicate only Greedy is enabled (e.g., "Registration parameters
- Greedy for full run") and ensure the explanatory comment next to
number_of_iterations_list aligns (remove or adjust references to
ICON/Greedy_ICON) so the comments match the current registration_methods and
number_of_iterations_list variables.


# Common parameters
Expand Down Expand Up @@ -144,12 +144,12 @@
registrar.set_fixed_image(fixed_image)

# Set iterations based on registration method
if registration_method == "ANTS":
registrar.set_number_of_iterations_ANTS(number_of_iterations)
if registration_method == "Greedy":
registrar.set_number_of_iterations_greedy(number_of_iterations)
elif registration_method == "ICON":
registrar.set_number_of_iterations_ICON(number_of_iterations)
elif registration_method == "ANTS_ICON":
registrar.set_number_of_iterations_ANTS(number_of_iterations[0])
elif registration_method == "Greedy_ICON":
registrar.set_number_of_iterations_greedy(number_of_iterations[0])
registrar.set_number_of_iterations_ICON(number_of_iterations[1])

# Perform registration
Expand Down
4 changes: 2 additions & 2 deletions src/physiomotion4d/cli/convert_image_to_usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ def main() -> int:
)
parser.add_argument(
"--registration-method",
choices=["ANTS", "ICON"],
choices=["Greedy", "ICON"],
default="ICON",
help="Registration method to use: ANTS or ICON (default: ICON)",
help="Registration method to use: Greedy or ICON (default: ICON)",
)
parser.add_argument(
"--fps",
Expand Down
4 changes: 1 addition & 3 deletions src/physiomotion4d/contour_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,7 @@ def create_mask_from_mesh(
voxel_grid_origin_lps[2] + voxel_pitch * binary_array.shape[2]
)

# Create ITK image from the voxel array
# ITK uses ZYX ordering (numpy array convention), trimesh uses XYZ
# Need to transpose: (X, Y, Z) -> (Z, Y, X)
# transpose to match trimesh XYZ convention
binary_array_zyx = np.transpose(binary_array, (2, 1, 0))
binary_array_flip = np.flip(binary_array_zyx, axis=0)
binary_image = itk.GetImageFromArray(binary_array_flip)
Expand Down
4 changes: 2 additions & 2 deletions src/physiomotion4d/convert_image_4d_to_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@


class ConvertImage4DTo3D(PhysioMotion4DBase):
"""Split a 3D/4D ITK image (X, Y, Z [, T]) into a list of 3D ITK images."""
"""Split a 3D/4D ITK image into a list of 3D ITK images."""

def __init__(self, log_level: int | str = logging.INFO) -> None:
"""Initialize the 4D-to-3D image converter.
Expand Down Expand Up @@ -178,7 +178,7 @@ def _build_frames(
spacing_3d: np.ndarray,
direction_3d: np.ndarray,
) -> None:
"""Materialize ``self.img_3d`` from a (T, Z, Y, X) array + geometry."""
"""Materialize ``self.img_3d`` from a time-series array + geometry."""
direction_matrix = itk.matrix_from_array(np.ascontiguousarray(direction_3d))
self.img_3d = []
for t in range(arr_4d.shape[0]):
Expand Down
15 changes: 0 additions & 15 deletions src/physiomotion4d/image_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,11 @@ def convert_itk_image_to_sitk(self, itk_image: itk.Image) -> sitk.Image:
Returns:
SimpleITK image with identical data and metadata

Note:
Memory layout is preserved during conversion. Both ITK and SimpleITK
use (z, y, x) ordering for 3D images in numpy arrays.

Example:
>>> tools = ImageTools()
>>> itk_image = itk.imread('image.nii.gz')
>>> sitk_image = tools.convert_itk_image_to_sitk(itk_image)
"""
# Get numpy array from ITK image
# ITK array is in (z, y, x) or (z, y, x, components) format
array = itk.array_from_image(itk_image)

# Get image metadata
Expand All @@ -129,11 +123,8 @@ def convert_itk_image_to_sitk(self, itk_image: itk.Image) -> sitk.Image:
is_vector = n_components > 1

if is_vector:
# For vector images, SimpleITK expects (z, y, x, components) which is what we have
# Create SimpleITK image from numpy array
sitk_image = sitk.GetImageFromArray(array, isVector=True)
else:
# For scalar images, array is (z, y, x)
sitk_image = sitk.GetImageFromArray(array, isVector=False)

# Set metadata
Expand Down Expand Up @@ -162,17 +153,11 @@ def convert_sitk_image_to_itk(self, sitk_image: sitk.Image) -> itk.Image:
Returns:
ITK image with identical data and metadata

Note:
Memory layout is preserved during conversion. Both SimpleITK and ITK
use (z, y, x) ordering for 3D images in numpy arrays.

Example:
>>> tools = ImageTools()
>>> sitk_image = sitk.ReadImage('image.nii.gz')
>>> itk_image = tools.convert_sitk_image_to_itk(sitk_image)
"""
# Get numpy array from SimpleITK image
# SimpleITK array is in (z, y, x) or (z, y, x, components) format
array = sitk.GetArrayFromImage(sitk_image)

# Get image metadata
Expand Down
10 changes: 0 additions & 10 deletions src/physiomotion4d/labelmap_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@ def convert_labelmap_to_mask(
even on anisotropic grids; each per-axis count is clamped to at least
1 voxel when ``dilation_in_mm > 0``.

Axis ordering: the labelmap is a scalar 3D ``itk.Image`` in ITK
world-axis order (X, Y, Z). All thresholding is performed on the numpy
view (Z, Y, X) and written back through ``CopyInformation``, so origin,
spacing, and direction are preserved.

Args:
labelmap: Multi-label or binary ``itk.Image``. Any non-zero voxel
that is not excluded is treated as foreground.
Expand Down Expand Up @@ -132,11 +127,6 @@ def create_distance_map(
this continuous encoding gives every region a smoothly varying signal
while preserving label identity.

Axis ordering: ``labelmap`` is a scalar 3D ``itk.Image`` in ITK
world-axis order (X, Y, Z). All work is done on the numpy view
(Z, Y, X) and written back through ``CopyInformation``, so origin,
spacing, and direction are preserved.

Args:
labelmap: Multi-label (or binary) ``itk.Image`` of integer labels.
max_distance_mm: Distance clip, in millimeters. Default 20.0.
Expand Down
1 change: 0 additions & 1 deletion src/physiomotion4d/register_images_ants.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ def _itk_to_ants_image(
raise ValueError(f"Unsupported dtype: {dtype}")

if is_vector:
# Vector images: ITK gives (z,y,x,components) or (y,x,components)
spatial_shape = data.shape[:-1] # drop components
else:
spatial_shape = data.shape
Expand Down
Loading
Loading