diff --git a/.gitignore b/.gitignore index c18220f..903efe9 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ tests/data/ tests/results/ tests/baseline_staging/ test_output.txt + +# docs +docs_buildhtml diff --git a/AGENTS.md b/AGENTS.md index 821b2c4..293e4dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,43 +42,51 @@ Non-Python tools used by contributor workflows: ## Common Commands -Use `py` on this Windows system, not `python`. +Prefer the repository-local virtual environment at `.\venv`. Activate it before +issuing Python commands so `python`, console scripts, and `uv pip` all use that +environment. If activation is not possible, invoke +`.\venv\Scripts\python.exe -m ...` directly. Use `uv run ...` only when the +local `venv` is unavailable and you need uv to create or sync an environment. + +```powershell +# Create the repo-local environment if it does not already exist +uv venv venv +.\venv\Scripts\Activate.ps1 -```bash # Install in editable mode uv pip install -e . # Lint and format -ruff check . --fix && ruff format . +python -m ruff check . --fix && python -m ruff format . # Type checking -mypy src/ tests/ +python -m mypy src/ tests/ # All pre-commit hooks -pre-commit run --all-files +python -m pre_commit run --all-files # Fast tests -py -m pytest tests/ -v +python -m pytest tests/ -v # Single test file or test by name -py -m pytest tests/test_contour_tools.py -v -py -m pytest tests/test_contour_tools.py::test_extract_surface -v +python -m pytest tests/test_contour_tools.py -v +python -m pytest tests/test_contour_tools.py::test_extract_surface -v # Opt-in test buckets -py -m pytest tests/ -v --run-slow -py -m pytest tests/ -v --run-gpu -py -m pytest tests/ -v --run-simpleware -py -m pytest tests/ -v --run-experiments -py -m pytest tests/ -v --run-tutorials +python -m pytest tests/ -v --run-slow +python -m pytest tests/ -v --run-gpu +python -m pytest tests/ -v --run-simpleware +python -m pytest tests/ -v --run-experiments +python -m pytest tests/ -v --run-tutorials # Typical local GPU profile -py -m pytest tests/ -v --run-gpu --run-slow +python -m pytest tests/ -v --run-gpu --run-slow # Coverage -py -m pytest tests/ --cov=src/physiomotion4d --cov-report=html +python -m pytest tests/ --cov=src/physiomotion4d --cov-report=html # Create missing baselines -py -m pytest tests/ --create-baselines +python -m pytest tests/ --create-baselines ``` Version bumping: `bumpver update --patch`, `--minor`, or `--major`. @@ -95,7 +103,8 @@ Version bumping: `bumpver update --patch`, `--minor`, or `--major`. below 88 characters. - Full type hints are required under strict mypy. Use `Optional[X]`, not `X | None`. -- Run `py -m pytest tests/ -v` to verify changes. Slow, GPU, Simpleware, +- Run `python -m pytest tests/ -v` from the active `.\venv` to verify changes. + Slow, GPU, Simpleware, experiment, and tutorial tests are auto-skipped unless their opt-in flag is passed. - The `requires_data` marker no longer exists. Tests that need external data @@ -172,8 +181,8 @@ Version bumping: `bumpver update --patch`, `--minor`, or `--major`. - Update docstrings for every changed public method. Keep claims factual. - Document with docstrings and inline comments. - Do not create new `.md` files unless explicitly requested. -- Regenerate `docs/API_MAP.md` after any public API change: - `py utils/generate_api_map.py`. +- Regenerate `docs/API_MAP.md` after any public API change from the active + `.\venv`: `python utils/generate_api_map.py`. ## Architecture Role diff --git a/docs/API_MAP.md b/docs/API_MAP.md index 2bdc798..d434243 100644 --- a/docs/API_MAP.md +++ b/docs/API_MAP.md @@ -5,9 +5,9 @@ _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 222): Custom function to skip certain members during autodoc processing. -- `def setup(app)` (line 230): Custom setup function for Sphinx. +- **class Mock** (line 22) +- `def autodoc_skip_member(app, what, name, obj, skip, options)` (line 227): Custom function to skip certain members during autodoc processing. +- `def setup(app)` (line 239): Custom setup function for Sphinx. ## experiments/Colormap-VTK_To_USD/colormap_vtk_to_usd.py @@ -428,6 +428,10 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._ - `def main()` (line 19): Command-line interface for create statistical model workflow. +## src/physiomotion4d/cli/download_data.py + +- `def main(argv=None)` (line 16): Download a supported PhysioMotion4D example dataset. + ## src/physiomotion4d/cli/fit_statistical_model_to_patient.py - `def main()` (line 17): Command-line interface for heart model to patient registration. @@ -870,7 +874,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 24): Each CLI module exits successfully for --help. +- `def test_cli_help(module_name, monkeypatch, capsys)` (line 25): Each CLI module exits successfully for --help. ## tests/test_contour_tools.py @@ -916,6 +920,11 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._ - `def test_mask_ids_missing_boundary_labels_falls_back(self, tmp_path)` (line 562): Mesh without boundary_labels array falls back to a 'default' prim. - `def test_mask_ids_groups_by_segmenter_type(self, tmp_path)` (line 577): When a segmenter is supplied, labels are grouped under their +## tests/test_download_data_cli.py + +- `def test_download_data_cli_uses_default_dataset_and_directory(monkeypatch, capsys)` (line 14): Default CLI arguments route Slicer-Heart-CT to data/Slicer-Heart-CT. +- `def test_download_data_cli_uses_requested_directory(monkeypatch, tmp_path)` (line 34): The --directory option controls where Slicer-Heart-CT is stored. + ## tests/test_download_heart_data.py - **class TestDataDownloadTools** (line 16): Synthetic tests for dataset verification helpers. diff --git a/docs/_static/custom.css b/docs/_static/custom.css index d32439e..69f6eb8 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -268,13 +268,21 @@ dt.sig { line-height: 1.08; } -.pm4d-hero p:not(.pm4d-kicker) { +.pm4d-hero p:not(.pm4d-kicker):not(.pm4d-hero__version) { max-width: 760px; margin: 0; color: #d8dde6; font-size: 1.1rem; } +.pm4d-hero p.pm4d-hero__version { + max-width: 760px; + margin: 1.9rem 0 0; + color: #ffffff; + font-size: 0.78rem; + font-weight: 700; +} + .pm4d-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); diff --git a/docs/api/cli/download_data.rst b/docs/api/cli/download_data.rst new file mode 100644 index 0000000..409c1c5 --- /dev/null +++ b/docs/api/cli/download_data.rst @@ -0,0 +1,10 @@ +=================== +download_data (CLI) +=================== + +.. automodule:: physiomotion4d.cli.download_data + :members: + :undoc-members: + :show-inheritance: + +See :doc:`../../cli_scripts/download_data` for usage examples. diff --git a/docs/api/cli/index.rst b/docs/api/cli/index.rst index 1e57083..b6440d1 100644 --- a/docs/api/cli/index.rst +++ b/docs/api/cli/index.rst @@ -22,6 +22,7 @@ Module Index. convert_image_to_vtk convert_vtk_to_usd create_statistical_model + download_data fit_statistical_model_to_patient reconstruct_highres_4d_ct visualize_pca_modes diff --git a/docs/architecture.rst b/docs/architecture.rst index 7524cd8..58d09aa 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -9,8 +9,8 @@ configuration. .. warning:: - PhysioMotion4D 2026.05.07 beta is not validated for clinical use. It is a - research and visualization toolkit, not a medical device. + PhysioMotion4D {{ pm4d_project_version }} beta is not validated for clinical + use. It is a research and visualization toolkit, not a medical device. Data Flow ========= diff --git a/docs/cli_scripts/best_practices.rst b/docs/cli_scripts/best_practices.rst index 532839e..2bd2a4c 100644 --- a/docs/cli_scripts/best_practices.rst +++ b/docs/cli_scripts/best_practices.rst @@ -242,7 +242,7 @@ Ensuring Reproducible Results # processing_metadata.yaml patient_id: patient_001 - physiomotion4d_version: 2026.05.07 + physiomotion4d_version: {{ pm4d_project_version }} script: heart-gated-ct date: 2026-01-08 diff --git a/docs/cli_scripts/byod_tutorials.rst b/docs/cli_scripts/byod_tutorials.rst index 22881f2..ec43132 100644 --- a/docs/cli_scripts/byod_tutorials.rst +++ b/docs/cli_scripts/byod_tutorials.rst @@ -1,19 +1,18 @@ .. _byod_tutorials: -Bring Your Own Data — DICOM & VTK to USD -========================================= +Bring Your Own Data - DICOM, Images & VTK to USD +================================================ -PhysioMotion4D lets you convert your own medical imaging data — whether -DICOM-derived NIfTI volumes or VTK surface meshes — into OpenUSD for -interactive visualization in NVIDIA Omniverse. Both 3D (single -volume/mesh) and 4D (time-series) inputs are supported. The CLI and -Python API are **identical** for 3D and 4D inputs; the only difference -is how many files you pass in. +PhysioMotion4D lets you convert your own medical imaging data into OpenUSD for +interactive visualization in NVIDIA Omniverse. Image inputs may be a directory +of 3D or 4D DICOM data, a single 3D or 4D file in a common medical image format +such as MHA, NRRD, or NIfTI, or a list of 3D image files representing a time +series. VTK inputs may be one mesh file or a mesh sequence. .. note:: PhysioMotion4D is a research tool and has **not** been validated for - clinical use. Outputs must not be used for diagnostic or therapeutic + clinical use. Outputs must not be used for diagnostic or therapeutic decisions without independent validation. Installation @@ -24,41 +23,66 @@ or the CPU-only variant: .. code-block:: bash - # Recommended — CUDA-enabled + # Recommended - CUDA-enabled pip install physiomotion4d[cuda13] # CPU-only pip install physiomotion4d -Verify that both relevant CLI entry-points are available after installation: +Verify that all three relevant CLI entry-points are available after installation: .. code-block:: bash + physiomotion4d-download-data --help physiomotion4d-convert-image-to-usd --help physiomotion4d-convert-vtk-to-usd --help See :doc:`/installation` for prerequisites, CUDA version requirements, and source-based installation. -DICOM to USD ------------- +Download Demonstration Data +--------------------------- + +Use the installed download CLI to fetch the public Slicer-Heart 4D CT sample: + +.. code-block:: bash + + physiomotion4d-download-data -Raw DICOM images must first be converted to NIfTI with a tool such as -`dcm2niix `_ before being passed -to PhysioMotion4D. +This stores ``TruncalValve_4DCT.seq.nrrd`` under +``data/Slicer-Heart-CT``. To choose a different location: -3D — Single Volume -~~~~~~~~~~~~~~~~~~ +.. code-block:: bash -Pass a single ``.nii.gz`` file to produce a static USD scene. + physiomotion4d-download-data Slicer-Heart-CT \ + --directory path/to/Slicer-Heart-CT + +DICOM and Medical Images to USD +------------------------------- + +Image-to-USD conversion accepts DICOM directories directly. It also accepts +3D and 4D image files readable by ITK, including common formats such as +``.mha``, ``.nrrd``, ``.nii``, and ``.nii.gz``. + +3D - Single DICOM Directory or Image File +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pass a DICOM series directory or a single 3D image file to produce a static USD +scene. **CLI:** .. code-block:: bash physiomotion4d-convert-image-to-usd \ - patient_ct.nii.gz \ - --output patient_heart.usd + patient_dicom_dir \ + --output-dir ./results \ + --project-name patient_heart + + physiomotion4d-convert-image-to-usd \ + patient_ct.mha \ + --output-dir ./results \ + --project-name patient_heart **Python API:** @@ -66,71 +90,83 @@ Pass a single ``.nii.gz`` file to produce a static USD scene. import physiomotion4d as pm4d - wf = pm4d.WorkflowConvertImageToUSD() - wf.input_image = "patient_ct.nii.gz" - wf.output_file = "patient_heart.usd" - wf.run_workflow() - -.. note:: + workflow = pm4d.WorkflowConvertImageToUSD( + input_filenames=["patient_dicom_dir"], + contrast_enhanced=False, + output_directory="./results", + project_name="patient_heart", + ) + workflow.process() - If your source data is raw DICOM, run ``dcm2niix -z y -o output_dir - dicom_dir/`` first to produce the ``.nii.gz`` input file expected by - this command. +The workflow writes ``.dynamic_painted.usd``, +``.static_painted.usd``, and ``.all_painted.usd`` +inside ``--output-dir``. -4D — Gated CT Time Series -~~~~~~~~~~~~~~~~~~~~~~~~~~ +4D - DICOM Directory, 4D Image File, or 3D Image List +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Pass multiple per-phase volumes (glob or explicit list) to produce an -animated USD scene. Use ``--fps`` to control playback rate and -``--reference-frame`` to choose the registration anchor phase (0-indexed). +Pass a 4D DICOM directory, a single 4D image file, or an explicit list of 3D +image files to produce an animated USD scene. The image-to-USD CLI does not +provide an ``--fps`` option. Use ``--reference-image`` only when you need to +provide a separate fixed image for registration; otherwise the workflow selects +its default reference frame internally. **CLI:** .. code-block:: bash physiomotion4d-convert-image-to-usd \ - phase_*.nii.gz \ - --output heart_animated.usd \ - --fps 25 \ - --reference-frame 0 + gated_ct_dicom_dir \ + --output-dir ./results \ + --project-name heart_animated + + physiomotion4d-convert-image-to-usd \ + gated_ct_4d.nrrd \ + --output-dir ./results \ + --project-name heart_animated + + physiomotion4d-convert-image-to-usd \ + phase_000.mha phase_001.mha phase_002.mha \ + --output-dir ./results \ + --project-name heart_animated **Python API:** .. code-block:: python - import glob import physiomotion4d as pm4d - wf = pm4d.WorkflowConvertImageToUSD() - wf.input_images = sorted(glob.glob("phase_*.nii.gz")) - wf.output_file = "heart_animated.usd" - wf.fps = 25 - wf.reference_frame = 0 - wf.run_workflow() + workflow = pm4d.WorkflowConvertImageToUSD( + input_filenames=["phase_000.mha", "phase_001.mha", "phase_002.mha"], + contrast_enhanced=False, + output_directory="./results", + project_name="heart_animated", + ) + workflow.process() -The resulting USD file contains a time-sampled mesh sequence that plays -back when you press **Play** in Omniverse USD Composer. +The resulting USD file contains a time-sampled mesh sequence that plays back +when you press **Play** in Omniverse USD Composer. VTK to USD ---------- -3D — Single Mesh +3D - Single Mesh ~~~~~~~~~~~~~~~~ -Pass a single ``.vtp`` file. Use ``--appearance`` to control material -style and ``--no-split`` to skip the default connected-component split. +Pass a single ``.vtp`` file. Use ``--appearance`` to control material style and +``--no-split`` to skip the default connected-component split. **CLI:** .. code-block:: bash - # Default — split by connected component, anatomy material + # Default - split by connected component, anatomy material physiomotion4d-convert-vtk-to-usd heart.vtp \ --output heart.usd \ --appearance anatomy \ --anatomy-type heart - # Solid colour, no splitting + # Solid color, no splitting physiomotion4d-convert-vtk-to-usd mesh.vtp \ --output mesh_red.usd \ --appearance solid \ @@ -143,18 +179,21 @@ style and ``--no-split`` to skip the default connected-component split. import physiomotion4d as pm4d - wf = pm4d.WorkflowConvertVTKToUSD() - wf.input_files = ["heart.vtp"] - wf.output_file = "heart.usd" - wf.appearance = "anatomy" - wf.anatomy_type = "heart" - wf.run() + workflow = pm4d.WorkflowConvertVTKToUSD( + vtk_files=["heart.vtp"], + output_usd="heart.usd", + appearance="anatomy", + anatomy_type="heart", + ) + workflow.run() -4D — Mesh Time Series +4D - Mesh Time Series ~~~~~~~~~~~~~~~~~~~~~ -Pass a glob of per-frame files. The ``--fps`` flag controls playback -rate. For scalar colormaps, combine ``--primvar``, ``--cmap``, and +Pass per-frame VTK files. The default workflow treats multiple files as a time +series when their names match ``.t.vtk``, ``.t.vtp``, or +``.t.vtu``. The VTK-to-USD CLI supports ``--fps`` to control playback +rate. For scalar colormaps, combine ``--primvar``, ``--cmap``, and ``--intensity-range``. **CLI:** @@ -162,12 +201,12 @@ rate. For scalar colormaps, combine ``--primvar``, ``--cmap``, and .. code-block:: bash # Animated mesh sequence - physiomotion4d-convert-vtk-to-usd frame_*.vtp \ + physiomotion4d-convert-vtk-to-usd heart.t0.vtp heart.t1.vtp heart.t2.vtp \ --output heart_animation.usd \ --fps 30 # Animated with scalar colormap (e.g. wall stress) - physiomotion4d-convert-vtk-to-usd frame_*.vtk \ + physiomotion4d-convert-vtk-to-usd stress.t0.vtk stress.t1.vtk stress.t2.vtk \ --output stress_animation.usd \ --fps 30 \ --appearance colormap \ @@ -179,20 +218,20 @@ rate. For scalar colormaps, combine ``--primvar``, ``--cmap``, and .. code-block:: python - import glob import physiomotion4d as pm4d - wf = pm4d.WorkflowConvertVTKToUSD() - wf.input_files = sorted(glob.glob("frame_*.vtk")) - wf.output_file = "stress_animation.usd" - wf.fps = 30 - wf.appearance = "colormap" - wf.primvar = "vtk_point_stress_c0" - wf.cmap = "viridis" - wf.intensity_range = (0, 500) - wf.run() + workflow = pm4d.WorkflowConvertVTKToUSD( + vtk_files=["stress.t0.vtk", "stress.t1.vtk", "stress.t2.vtk"], + output_usd="stress_animation.usd", + times_per_second=30, + appearance="colormap", + colormap_primvar="vtk_point_stress_c0", + colormap_name="viridis", + colormap_intensity_range=(0, 500), + ) + workflow.run() -**Lower-level in-memory conversion with** ``ConvertVTKToUSD``**:** +**Lower-level in-memory conversion with ConvertVTKToUSD:** For programmatic pipelines where meshes are already in memory, use the lower-level :class:`physiomotion4d.ConvertVTKToUSD` class directly: @@ -205,28 +244,34 @@ lower-level :class:`physiomotion4d.ConvertVTKToUSD` class directly: # Load or construct meshes in memory meshes = [pv.read(f"frame_{i:04d}.vtp") for i in range(10)] - converter = pm4d.ConvertVTKToUSD(output_file="output.usd", fps=30) - for i, mesh in enumerate(meshes): - converter.add_frame(mesh, frame_index=i) - converter.write() + converter = pm4d.ConvertVTKToUSD( + data_basename="HeartAnimation", + input_polydata=meshes, + times_per_second=30, + ) + converter.convert("output.usd") Viewing Results --------------- -**Quick preview with PyVista (no Omniverse required):** +**Programmatic inspection:** .. code-block:: python - import pyvista as pv + import physiomotion4d as pm4d - mesh = pv.read("output.usd") - mesh.plot() + mesh = pm4d.USDTools().load_usd_as_vtk("output.usd") + print(mesh.n_points, mesh.n_cells) + +PyVista reads the VTK input files used above, but local validation with +PyVista 0.48.4 shows that ``pyvista.read()`` / ``pyvista.get_reader()`` do not +support ``.usd``, ``.usda``, or ``.usdc`` output files directly. **In NVIDIA Omniverse:** -Open **Omniverse USD Composer**, drag your ``.usd`` file onto the -viewport, then press **Play** (spacebar) to watch the animation. For -4D cardiac data, use the **Timeline** panel to scrub through phases. +Open **Omniverse USD Composer**, drag your ``.usd`` file onto the viewport, +then press **Play** (spacebar) to watch the animation. For 4D cardiac data, +use the **Timeline** panel to scrub through phases. See Also -------- @@ -238,3 +283,27 @@ See Also - :doc:`/api/workflows` - :doc:`/examples` - :doc:`/troubleshooting` + +.. _isaac_for_healthcare_assets: + +4D Isaac for Healthcare Assets +------------------------------ + +PhysioMotion4D has been used to generate a number of 4D anatomic models for +Isaac for Healthcare. These datasets are intended to support visualization and +workflow development with time-varying anatomy in OpenUSD. + +.. list-table:: + :header-rows: 1 + :widths: 45 55 + + * - Asset + - Download link + * - Chest with cardiac motion + - + * - Chest with respiratory motion + - + * - Heart with cardiac motion + - + * - Lungs with respiratory motion + - diff --git a/docs/cli_scripts/download_data.rst b/docs/cli_scripts/download_data.rst new file mode 100644 index 0000000..dc7f800 --- /dev/null +++ b/docs/cli_scripts/download_data.rst @@ -0,0 +1,69 @@ +===================== +Download Example Data +===================== + +The ``physiomotion4d-download-data`` command downloads example datasets used by +PhysioMotion4D tutorials and demos. + +Supported Datasets +================== + +.. list-table:: + :widths: 35 65 + :header-rows: 1 + + * - Data name + - Description + * - ``Slicer-Heart-CT`` + - Public 4D cardiac CT sample from SlicerHeart. This is currently the + only dataset downloaded automatically by PhysioMotion4D. + +Basic Usage +=========== + +Download the default dataset into the default location: + +.. code-block:: bash + + physiomotion4d-download-data + +This is equivalent to: + +.. code-block:: bash + + physiomotion4d-download-data Slicer-Heart-CT \ + --directory data/Slicer-Heart-CT + +Options +======= + +.. code-block:: bash + + physiomotion4d-download-data [Slicer-Heart-CT] [--directory DIRECTORY] + +``data_name`` + Dataset to download. The only accepted value is ``Slicer-Heart-CT``. + +``--directory`` + Directory where the dataset is stored. Defaults to + ``data/Slicer-Heart-CT``. + +Output +====== + +For ``Slicer-Heart-CT``, the command downloads or reuses: + +.. code-block:: text + + data/Slicer-Heart-CT/TruncalValve_4DCT.seq.nrrd + +The command uses +:meth:`physiomotion4d.data_download_tools.DataDownloadTools.DownloadSlicerHeartCTData`, +so repeated runs reuse the existing non-empty file. + +See Also +======== + +* :doc:`byod_tutorials` +* :doc:`heart_gated_ct` +* :doc:`overview` diff --git a/docs/cli_scripts/overview.rst b/docs/cli_scripts/overview.rst index 2ed59a4..75d0a8d 100644 --- a/docs/cli_scripts/overview.rst +++ b/docs/cli_scripts/overview.rst @@ -49,6 +49,8 @@ Current Scripts * - Script - Description + * - :doc:`download_data` + - Download supported PhysioMotion4D example datasets * - :doc:`heart_gated_ct` - Process cardiac gated CT to animated heart models with physiological motion * - ``physiomotion4d-convert-image-to-vtk`` @@ -80,6 +82,7 @@ After installation, scripts are available as command-line tools with the prefix .. code-block:: bash physiomotion4d-convert-image-to-usd --help + physiomotion4d-download-data --help General Workflow ================ diff --git a/docs/conf.py b/docs/conf.py index 2e28d12..9a7cfaf 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,8 +4,10 @@ import os import sys +import tomllib import warnings from datetime import datetime +from pathlib import Path from unittest.mock import MagicMock @@ -32,10 +34,13 @@ def __getattr__(cls, name): project = "PhysioMotion4D" copyright = f"{datetime.now().year}, Stephen R. Aylward, NVIDIA Corporation" author = "Stephen R. Aylward" +_repo_root = Path(__file__).resolve().parents[1] +_pyproject = tomllib.loads((_repo_root / "pyproject.toml").read_text(encoding="utf-8")) +_project_version = _pyproject["project"]["version"] # The full version, including alpha/beta/rc tags -release = "2026.05.07" -version = "2026.05.07" +release = _project_version +version = _project_version # -- General configuration --------------------------------------------------- extensions = [ @@ -227,8 +232,14 @@ def autodoc_skip_member(app, what, name, obj, skip, options): return skip +def _replace_project_version_token(_app, _docname, source): + source[0] = source[0].replace("{{ pm4d_project_version }}", release) + + def setup(app): """Custom setup function for Sphinx.""" + app.connect("source-read", _replace_project_version_token) + # Connect the autodoc-skip-member event app.connect("autodoc-skip-member", autodoc_skip_member) diff --git a/docs/contributing.rst b/docs/contributing.rst index 95854c6..a37d0c2 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -410,7 +410,7 @@ PhysioMotion4D uses calendar versioning: ``YYYY.0M.PATCH`` * **0M**: Zero-padded month * **PATCH**: Patch number within month -Example: ``2026.05.07`` +Example: ``{{ pm4d_project_version }}`` Making a Release ---------------- diff --git a/docs/index.rst b/docs/index.rst index 729c460..458de99 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,13 +16,8 @@ documentation sections below for installation, CLI workflows, API references, developer notes, and contribution guidance.

- -
- Not validated for clinical use. - PhysioMotion4D 2026.05.07 beta is a research and visualization toolkit, - not a medical device. Do not use it for diagnosis, treatment planning, or - clinical decision-making. +

Version {{ pm4d_project_version }}

@@ -86,6 +81,10 @@

CLI Workflows

Use production command-line workflows for conversion, reconstruction, modeling, and USD export.

+ +

Isaac for Healthcare

+

Find PhysioMotion4D workflows and assets for Isaac for Healthcare use cases.

+

API Reference

Browse classes and modules for workflows, segmentation, registration, USD, and utilities.

@@ -136,6 +135,7 @@ per-tutorial implementation details. :hidden: cli_scripts/overview + cli_scripts/download_data cli_scripts/heart_gated_ct cli_scripts/create_statistical_model cli_scripts/fit_statistical_model_to_patient @@ -173,6 +173,14 @@ per-tutorial implementation details. contributing testing +.. toctree:: + :maxdepth: 2 + :caption: Isaac for Healthcare + :hidden: + + isaac_for_healthcare + cli_scripts/byod_tutorials + .. toctree:: :maxdepth: 1 :caption: Additional Resources @@ -183,6 +191,13 @@ per-tutorial implementation details. references changelog +Clinical Use +============ + +Not validated for clinical use. PhysioMotion4D {{ pm4d_project_version }} beta +is a research and visualization toolkit, not a medical device. Do not use it +for diagnosis, treatment planning, or clinical decision-making. + Indices and tables ================== diff --git a/docs/installation.rst b/docs/installation.rst index 8fa03d8..f63ce96 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -171,7 +171,7 @@ Expected output: .. code-block:: text - PhysioMotion4D version: 2026.05.07 + PhysioMotion4D version: {{ pm4d_project_version }} WorkflowConvertImageToUSD Command-Line Tools diff --git a/docs/isaac_for_healthcare.rst b/docs/isaac_for_healthcare.rst new file mode 100644 index 0000000..b860a96 --- /dev/null +++ b/docs/isaac_for_healthcare.rst @@ -0,0 +1,20 @@ +Isaac for Healthcare +==================== + +Use these resources to create, inspect, and download PhysioMotion4D assets for +Isaac for Healthcare workflows. + +.. raw:: html + +
+ +
diff --git a/docs/quickstart.rst b/docs/quickstart.rst index d719f87..182a26c 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -6,9 +6,10 @@ This guide will help you get started with PhysioMotion4D quickly. .. warning:: - **Not validated for clinical use.** PhysioMotion4D 2026.05.07 beta is a - research and visualization toolkit, not a medical device. Do not use it for - diagnosis, treatment planning, or clinical decision-making. + **Not validated for clinical use.** PhysioMotion4D + {{ pm4d_project_version }} beta is a research and visualization toolkit, not + a medical device. Do not use it for diagnosis, treatment planning, or + clinical decision-making. .. _tutorials: diff --git a/pyproject.toml b/pyproject.toml index aa79a2f..1131183 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -165,6 +165,7 @@ physiomotion4d-convert-image-4d-to-3d = "physiomotion4d.cli.convert_image_4d_to_ physiomotion4d-convert-image-to-usd = "physiomotion4d.cli.convert_image_to_usd:main" physiomotion4d-convert-vtk-to-usd = "physiomotion4d.cli.convert_vtk_to_usd:main" physiomotion4d-create-statistical-model = "physiomotion4d.cli.create_statistical_model:main" +physiomotion4d-download-data = "physiomotion4d.cli.download_data:main" physiomotion4d-fit-statistical-model-to-patient = "physiomotion4d.cli.fit_statistical_model_to_patient:main" physiomotion4d-reconstruct-highres-4d-ct = "physiomotion4d.cli.reconstruct_highres_4d_ct:main" physiomotion4d-visualize-pca-modes = "physiomotion4d.cli.visualize_pca_modes:main" diff --git a/src/physiomotion4d/cli/__init__.py b/src/physiomotion4d/cli/__init__.py index ca3f06d..d498484 100644 --- a/src/physiomotion4d/cli/__init__.py +++ b/src/physiomotion4d/cli/__init__.py @@ -5,6 +5,7 @@ "convert_image_4d_to_3d", "convert_vtk_to_usd", "create_statistical_model", + "download_data", "fit_statistical_model_to_patient", "visualize_pca_modes", ] diff --git a/src/physiomotion4d/cli/download_data.py b/src/physiomotion4d/cli/download_data.py new file mode 100644 index 0000000..eaf9399 --- /dev/null +++ b/src/physiomotion4d/cli/download_data.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +"""Command-line interface for downloading PhysioMotion4D example data.""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path +from typing import Optional + +from physiomotion4d.data_download_tools import DataDownloadTools + +SLICER_HEART_CT = "Slicer-Heart-CT" + + +def main(argv: Optional[list[str]] = None) -> int: + """Download a supported PhysioMotion4D example dataset.""" + parser = argparse.ArgumentParser( + description="Download PhysioMotion4D example data", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=f""" +Examples: + %(prog)s + %(prog)s {SLICER_HEART_CT} --directory data/Slicer-Heart-CT + """, + ) + parser.add_argument( + "data_name", + nargs="?", + choices=[SLICER_HEART_CT], + default=SLICER_HEART_CT, + help=f"Dataset to download (default: {SLICER_HEART_CT})", + ) + parser.add_argument( + "--directory", + default=f"data/{SLICER_HEART_CT}", + help=f"Directory where data will be stored (default: data/{SLICER_HEART_CT})", + ) + + args = parser.parse_args(argv) + output_dir = Path(args.directory) + + if args.data_name == SLICER_HEART_CT: + data_file = DataDownloadTools.DownloadSlicerHeartCTData(output_dir) + print(f"Downloaded {SLICER_HEART_CT} to: {data_file}") + return 0 + + parser.error(f"Unsupported dataset: {args.data_name}") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_cli_smoke.py b/tests/test_cli_smoke.py index dcb236a..5b49fd5 100644 --- a/tests/test_cli_smoke.py +++ b/tests/test_cli_smoke.py @@ -14,6 +14,7 @@ "physiomotion4d.cli.convert_image_to_usd", "physiomotion4d.cli.convert_vtk_to_usd", "physiomotion4d.cli.create_statistical_model", + "physiomotion4d.cli.download_data", "physiomotion4d.cli.fit_statistical_model_to_patient", "physiomotion4d.cli.reconstruct_highres_4d_ct", "physiomotion4d.cli.visualize_pca_modes", diff --git a/tests/test_download_data_cli.py b/tests/test_download_data_cli.py new file mode 100644 index 0000000..df6c567 --- /dev/null +++ b/tests/test_download_data_cli.py @@ -0,0 +1,50 @@ +"""Tests for the dataset download CLI wrapper.""" + +from __future__ import annotations + +from pathlib import Path +from typing import Union + +import pytest + +from physiomotion4d.cli import download_data +from physiomotion4d.data_download_tools import DataDownloadTools + + +def test_download_data_cli_uses_default_dataset_and_directory( + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + """Default CLI arguments route Slicer-Heart-CT to data/Slicer-Heart-CT.""" + calls: list[Path] = [] + + def fake_download(dirname: Union[str, Path]) -> Path: + calls.append(Path(dirname)) + return Path(dirname) / DataDownloadTools.SLICER_HEART_CT_FILENAME + + monkeypatch.setattr(DataDownloadTools, "DownloadSlicerHeartCTData", fake_download) + + result = download_data.main([]) + + assert result == 0 + assert calls == [Path("data/Slicer-Heart-CT")] + assert "Downloaded Slicer-Heart-CT" in capsys.readouterr().out + + +def test_download_data_cli_uses_requested_directory( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + """The --directory option controls where Slicer-Heart-CT is stored.""" + calls: list[Path] = [] + + def fake_download(dirname: Union[str, Path]) -> Path: + calls.append(Path(dirname)) + return Path(dirname) / DataDownloadTools.SLICER_HEART_CT_FILENAME + + monkeypatch.setattr(DataDownloadTools, "DownloadSlicerHeartCTData", fake_download) + + result = download_data.main(["Slicer-Heart-CT", "--directory", str(tmp_path)]) + + assert result == 0 + assert calls == [tmp_path]