From 04efe9e10560ae79e216bffa7a77ac9330ac53e1 Mon Sep 17 00:00:00 2001 From: Stephen Aylward Date: Wed, 13 May 2026 00:01:20 -0400 Subject: [PATCH 01/11] ENH: AnatomyTaxonomy, USD pipeline hardening, tutorials + docs Replaces the per-organ flat dicts on SegmentAnatomyBase and USDAnatomyTools with a shared AnatomyTaxonomy data type so new segmenters can register custom groups via taxonomy.add_organ without subclassing either class. Adds anatomy-type grouping to ConvertVTKToUSD's labeled output, ships tutorials 07-09 (DirLab PCA + PhysicsNeMo), and fixes several USD- pipeline issues that prevented clean rendering in Omniverse Kit. Anatomy / segmentation - AnatomyTaxonomy + AnatomyGroup (new). SegmentAnatomyBase owns one; subclasses populate via taxonomy.add_organ and call _finalize_other_group. Eight legacy *_mask_ids attributes and set_other_and_all_mask_ids removed; label_to_type delegates to the taxonomy. segment() now returns only the groups the segmenter registered (caller-checks contract; tests updated). - SegmentHeartSimpleware: graceful fallback when ASCardio omits landmarks.csv; only the groups it actually populates are returned. USD generation - ConvertVTKToUSD: new segmenter= parameter groups labeled prims under /World/{basename}/{type}/{label} and materials under /World/Looks/{type}/{label}_material. Preserves cell-data primvars; new compute_von_mises_stress helper for 9-component stress tensors; framing camera with tight near-clip; stale USD-layer eviction; triangulation face-map propagated to uniform primvars so per-cell arrays survive quad-to-triangle splits. - USDAnatomyTools: DEFAULT_RENDER_PARAMS promoted to a module-level constant so CLIs/tests can enumerate types without a stage; per- instance deep copy keeps mutations isolated; enhance_meshes now a single taxonomy loop with organ-level overrides as data instead of imperative carve-outs. MaterialBindingAPI.Apply() so Omniverse RTX consumes the binding (was rendering white). - CLI convert_vtk_to_usd: ANATOMY_TYPES sourced from DEFAULT_RENDER_PARAMS (auto-syncs with future additions); "kidney" alias preserved. Tutorials / experiments - Tutorials 07-09 added (DirLab PCA lung-lobe model, time-series propagation, PhysicsNeMo mesh-stage training). - Tutorials 02-09 gain if __name__ == "__main__" guards (Windows nnUNet spawn) matching tutorial_01; experiments gain the same guards plus Path(__file__)-anchored paths; valve scripts wire compute_von_mises_stress. - notebook_utils.py removed (last consumers gone); experiment scripts and READMEs scrubbed of .ipynb references; test runner already on *.py glob. Tests, docs, packaging - New test_anatomy_taxonomy (12 tests); mask_ids / Simpleware assertions updated to the caller-checks contract; tutorial test harness rewritten. - Segmentation .rst pages, developer/segmentation.rst, and developer/ usd_generation.rst updated to document AnatomyTaxonomy, the type-grouped layout, von Mises helper, framing camera, and the three ways to register a new group's look. - New CLI .rst pages, simpleware/greedy API pages, vtk_to_usd_lib page, data_download / test_tools utility pages; API_MAP.md regenerated. - mypy: 7 errors fixed (Optional render-params indexing, redundant np.asarray casts, pyvista Plotter binding via Any cast, matplotlib colormap return typing). Full src/ tree clean. Other - data_download_tools.py extracted (was notebook_utils.py glue). - pyproject.toml mypy overrides tightened. - Stale baseline PNGs removed (regenerated by test_tools at runtime). --- .agents/.gitignore | 1 + .agents/skills/skills/caveman/SKILL.md | 66 +++ README.md | 106 ++-- data/README.md | 31 +- data/Slicer-Heart-CT/download_and_convert.py | 11 +- data/test/.gitignore | 5 +- docs/API_MAP.md | 435 +++++++------- docs/api/base.rst | 1 + docs/api/cli/convert_ct_to_vtk.rst | 10 + .../api/cli/convert_heart_gated_ct_to_usd.rst | 10 + docs/api/cli/convert_vtk_to_usd.rst | 10 + docs/api/cli/create_statistical_model.rst | 10 + .../cli/fit_statistical_model_to_patient.rst | 10 + docs/api/cli/index.rst | 33 ++ docs/api/cli/reconstruct_highres_4d_ct.rst | 10 + docs/api/cli/visualize_pca_modes.rst | 10 + docs/api/index.rst | 1 + docs/api/model_registration/distance_maps.rst | 1 + docs/api/model_registration/icp.rst | 1 + docs/api/model_registration/icp_itk.rst | 1 + docs/api/model_registration/pca.rst | 1 + docs/api/registration/ants.rst | 1 + docs/api/registration/base.rst | 1 + docs/api/registration/greedy.rst | 56 ++ docs/api/registration/icon.rst | 1 + docs/api/registration/index.rst | 1 + docs/api/registration/time_series.rst | 1 + docs/api/segmentation/base.rst | 75 ++- docs/api/segmentation/index.rst | 14 +- docs/api/segmentation/simpleware.rst | 68 +++ docs/api/segmentation/totalsegmentator.rst | 13 +- docs/api/usd/index.rst | 4 +- docs/api/usd/vtk_conversion.rst | 1 + docs/api/usd/vtk_to_usd_lib.rst | 86 +++ docs/api/utilities/contour_tools.rst | 2 +- docs/api/utilities/data_download.rst | 20 + docs/api/utilities/index.rst | 4 + docs/api/utilities/test_tools.rst | 20 + docs/api/workflows.rst | 6 + docs/contributing.rst | 13 +- docs/developer/segmentation.rst | 78 ++- docs/developer/usd_generation.rst | 117 ++++ docs/examples.rst | 10 +- docs/quickstart.rst | 39 +- docs/tutorials.rst | 129 +++- .../colormap_vtk_to_usd.py | 2 + .../convert_chop_alterra_valve_to_usd.py | 76 ++- .../convert_chop_tpv25_valve_to_usd.py | 54 +- .../1-input_meshes_to_input_surfaces.py | 4 +- .../2-input_surfaces_to_surfaces_aligned.py | 6 +- .../3-registration_based_correspondence.py | 8 +- ...rfaces_aligned_correspond_to_pca_inputs.py | 6 +- .../5-compute_pca_model.py | 6 +- .../Heart-Create_Statistical_Model/README.md | 35 +- .../0-download_and_convert_4d_to_3d.py | 30 +- .../Heart-GatedCT_To_USD/1-register_images.py | 302 +++++----- .../2-generate_segmentation.py | 271 ++++----- ...3-transform_dynamic_and_static_contours.py | 209 +++---- .../test_compare_registration_speed.py | 4 +- .../Heart-Simpleware_Segmentation/README.md | 17 +- .../simpleware_heart_segmentation.py | 24 +- .../heart_model_to_model_icp_itk.py | 4 +- .../heart_model_to_model_registration_pca.py | 8 +- .../heart_model_to_patient-CHOPValve.py | 6 +- .../heart_model_to_patient.py | 546 ++++++++--------- .../0-download_and_convert_4d_to_3d.py | 11 +- .../1-heart_vtkseries_to_usd.py | 105 ++-- .../0-register_dirlab_4dct.py | 502 ++++++++-------- .../1-make_dirlab_models.py | 194 ++++--- .../2-paint_dirlab_models.py | 29 +- .../Lung-GatedCT_To_USD/Experiment_SegReg.py | 67 ++- experiments/README.md | 2 +- .../reconstruct_4d_ct_class.py | 4 +- pyproject.toml | 8 +- src/physiomotion4d/__init__.py | 6 + src/physiomotion4d/anatomy_taxonomy.py | 147 +++++ src/physiomotion4d/cli/convert_vtk_to_usd.py | 18 +- src/physiomotion4d/convert_vtk_to_usd.py | 208 ++++++- src/physiomotion4d/data_download_tools.py | 107 ++++ src/physiomotion4d/notebook_utils.py | 27 - src/physiomotion4d/register_images_ants.py | 5 +- src/physiomotion4d/segment_anatomy_base.py | 219 +++---- .../segment_chest_total_segmentator.py | 317 +++++----- .../segment_heart_simpleware.py | 106 ++-- src/physiomotion4d/test_tools.py | 112 +++- src/physiomotion4d/transform_tools.py | 18 +- src/physiomotion4d/usd_anatomy_tools.py | 549 ++++++++---------- src/physiomotion4d/usd_tools.py | 173 +++++- src/physiomotion4d/vtk_to_usd/__init__.py | 2 + src/physiomotion4d/vtk_to_usd/converter.py | 13 +- .../vtk_to_usd/material_manager.py | 5 +- .../vtk_to_usd/primvar_derivations.py | 160 +++++ .../vtk_to_usd/usd_mesh_converter.py | 54 +- src/physiomotion4d/vtk_to_usd/usd_utils.py | 134 ++++- .../workflow_convert_ct_to_vtk.py | 7 +- .../workflow_convert_heart_gated_ct_to_usd.py | 144 +++-- tests/EXPERIMENT_FLAG_USAGE.md | 2 +- tests/EXPERIMENT_TESTS_GUIDE.md | 50 +- tests/EXPERIMENT_TESTS_SUMMARY.md | 2 +- tests/PARALLEL_EXECUTION_GUIDE.md | 54 +- .../contours_3d.png | Bin 163349 -> 0 bytes .../reference_frame_axial.png | Bin 100177 -> 0 bytes .../segmentation_overlay.png | Bin 166172 -> 0 bytes .../segmentation_overlay.png | Bin 166686 -> 0 bytes .../tutorial_02_ct_to_vtk/vtk_surfaces.png | Bin 174467 -> 0 bytes .../usd_mesh_rendering.png | Bin 165998 -> 0 bytes tests/conftest.py | 70 +-- tests/test_anatomy_taxonomy.py | 130 +++++ tests/test_convert_vtk_to_usd.py | 53 +- tests/test_download_heart_data.py | 66 +++ tests/test_segment_chest_total_segmentator.py | 32 +- tests/test_segment_heart_simpleware.py | 42 +- tests/test_tutorials.py | 238 ++------ tests/test_vtk_to_usd_library.py | 33 +- tutorials/README.md | 39 +- .../tutorial_01_heart_gated_ct_to_usd.py | 285 +++------ tutorials/tutorial_02_ct_to_vtk.py | 262 +++------ .../tutorial_03_create_statistical_model.py | 246 +++----- ...ial_04_fit_statistical_model_to_patient.py | 275 ++++----- tutorials/tutorial_05_vtk_to_usd.py | 198 ++----- .../tutorial_06_reconstruct_highres_4d_ct.py | 237 +++----- tutorials/tutorial_07_dirlab_pca_model.py | 304 ++++++++++ .../tutorial_08_dirlab_pca_time_series.py | 186 ++++++ 123 files changed, 5624 insertions(+), 3775 deletions(-) create mode 100644 .agents/skills/skills/caveman/SKILL.md create mode 100644 docs/api/cli/convert_ct_to_vtk.rst create mode 100644 docs/api/cli/convert_heart_gated_ct_to_usd.rst create mode 100644 docs/api/cli/convert_vtk_to_usd.rst create mode 100644 docs/api/cli/create_statistical_model.rst create mode 100644 docs/api/cli/fit_statistical_model_to_patient.rst create mode 100644 docs/api/cli/index.rst create mode 100644 docs/api/cli/reconstruct_highres_4d_ct.rst create mode 100644 docs/api/cli/visualize_pca_modes.rst create mode 100644 docs/api/registration/greedy.rst create mode 100644 docs/api/segmentation/simpleware.rst create mode 100644 docs/api/usd/vtk_to_usd_lib.rst create mode 100644 docs/api/utilities/data_download.rst create mode 100644 docs/api/utilities/test_tools.rst create mode 100644 src/physiomotion4d/anatomy_taxonomy.py create mode 100644 src/physiomotion4d/data_download_tools.py delete mode 100644 src/physiomotion4d/notebook_utils.py create mode 100644 src/physiomotion4d/vtk_to_usd/primvar_derivations.py delete mode 100644 tests/baselines/tutorial_01_heart_gated_ct_to_usd/contours_3d.png delete mode 100644 tests/baselines/tutorial_01_heart_gated_ct_to_usd/reference_frame_axial.png delete mode 100644 tests/baselines/tutorial_01_heart_gated_ct_to_usd/segmentation_overlay.png delete mode 100644 tests/baselines/tutorial_02_ct_to_vtk/segmentation_overlay.png delete mode 100644 tests/baselines/tutorial_02_ct_to_vtk/vtk_surfaces.png delete mode 100644 tests/baselines/tutorial_05_vtk_to_usd/usd_mesh_rendering.png create mode 100644 tests/test_anatomy_taxonomy.py create mode 100644 tutorials/tutorial_07_dirlab_pca_model.py create mode 100644 tutorials/tutorial_08_dirlab_pca_time_series.py 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/.agents/skills/skills/caveman/SKILL.md b/.agents/skills/skills/caveman/SKILL.md new file mode 100644 index 0000000..574acde --- /dev/null +++ b/.agents/skills/skills/caveman/SKILL.md @@ -0,0 +1,66 @@ +--- +name: "caveman" +description: "Ultra-compressed communication mode. Cuts output token usage ~75% by dropping filler, articles, and hedging while keeping full technical accuracy. Supports intensity levels: lite, full, ultra. Use when you want terse, fast responses for coding tasks." +license: "MIT" +compatibility: "Any AI coding assistant (Claude Code, Cursor, Copilot, etc.)" +metadata: + author: "Julius Brussee (upstream), Vaibhav Phutane (NVIDIA adaptation)" + tags: + - productivity + - token-optimization + - communication + - terse + domain: general +--- + +# Caveman + +Terse communication mode. All technical substance stays. Only fluff dies. + +## Instructions + +Activate with `/caveman` (defaults to full). Switch levels: `/caveman lite`, `/caveman full`, `/caveman ultra`. Deactivate with "stop caveman" or "normal mode". + +### Rules + +Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). Technical terms exact. Code blocks unchanged. Errors quoted exact. + +Pattern: `[thing] [action] [reason]. [next step].` + +Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..." +Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:" + +### Intensity + +| Level | What change | +|-------|------------| +| **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight | +| **full** | Drop articles, fragments OK, short synonyms. Classic caveman | +| **ultra** | Abbreviate (DB/auth/config/req/res/fn/impl), strip conjunctions, arrows for causality (X → Y), one word when one word enough | + +## Examples + +**"Why React component re-render?"** +- lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`." +- full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`." +- ultra: "Inline obj prop → new ref → re-render. `useMemo`." + +**"Explain database connection pooling."** +- lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead." +- full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead." +- ultra: "Pool = reuse DB conn. Skip handshake → fast under load." + +## Auto-Clarity + +Drop caveman for: security warnings, irreversible action confirmations, multi-step sequences where fragment order risks misread, user confused. Resume caveman after clear part done. + +Example — destructive op: +> **Warning:** This will permanently delete all rows in the `users` table and cannot be undone. +> ```sql +> DROP TABLE users; +> ``` +> Caveman resume. Verify backup exist first. + +## Boundaries + +Code/commits/PRs: write normal. "stop caveman" or "normal mode": revert. Level persist until changed or session end. diff --git a/README.md b/README.md index 8a605e3..36804db 100644 --- a/README.md +++ b/README.md @@ -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 \ @@ -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 ) @@ -465,27 +477,32 @@ Classes that inherit from `PhysioMotion4DBase` provide: ## 📊 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