Skip to content

ENH: Provides volumetric meshing.#78

Merged
aylward merged 3 commits into
Project-MONAI:mainfrom
aylward:meshing
Jul 7, 2026
Merged

ENH: Provides volumetric meshing.#78
aylward merged 3 commits into
Project-MONAI:mainfrom
aylward:meshing

Conversation

@aylward

@aylward aylward commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added optional surface and mesh reduction controls to the image-to-mesh workflow and CLI.
    • Introduced tetrahedral volume mesh generation from closed surfaces, with helpers to save surfaces and meshes (split and combined).
  • Bug Fixes

    • Updated mesh output descriptions and filenames to match the actual generated VTP/VTU file types.
    • Improved handling of meshing edge cases (e.g., no volume elements).
  • Documentation

    • Refreshed API docs, troubleshooting samples, and tutorials to use the updated workflow process(...) flow and new parameters.

Copilot AI review requested due to automatic review settings July 6, 2026 21:44
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Walkthrough

This PR adds netgen-based tetrahedral mesh generation to the VTK workflow, exposes new surface/mesh reduction controls in the CLI and tutorial, updates downstream callers to process, refreshes related docs, and aligns some public type hints and dependency configuration.

Changes

VTK netgen mesh conversion

Layer / File(s) Summary
Mesh generation helpers
pyproject.toml, src/physiomotion4d/contour_tools.py
Adds netgen-mesher, updates mypy ignores, and introduces ContourTools helpers for mesh extraction and saving surfaces/meshes.
Workflow surface and mesh processing
src/physiomotion4d/workflow_convert_image_to_vtk.py
Changes process to build per-group surfaces, optionally decimate them, generate tetrahedral meshes from surfaces, and return the updated surface/mesh outputs.
CLI, tutorial, and API docs
src/physiomotion4d/cli/convert_image_to_vtk.py, tutorials/tutorial_02_ct_to_vtk.py, docs/api/workflows.rst, docs/troubleshooting.rst
Updates VTK CLI options, output descriptions, example calls, and documentation snippets to use process and ContourTools.
Patient model call site and test
src/physiomotion4d/workflow_fit_statistical_model_to_patient.py, tests/test_workflow_fit_statistical_model_to_patient.py
Switches the patient-model conversion path to convert_image_to_vtk.process and updates the corresponding test double and assertion.
Contrast segmentator typing
src/physiomotion4d/segment_chest_total_segmentator_with_contrast.py
Cleans up itk.Image annotations and matching docstring types in the contrast segmentator API.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant convert_image_to_vtk CLI
  participant WorkflowConvertImageToVTK
  participant ContourTools

  User->>convert_image_to_vtk CLI: run with reduction options
  convert_image_to_vtk CLI->>WorkflowConvertImageToVTK: process(input_image, surface_target_reduction, mesh_target_reduction)
  loop per anatomy group
    WorkflowConvertImageToVTK->>ContourTools: extract_mesh(base_surface, mesh_target_reduction)
    WorkflowConvertImageToVTK->>ContourTools: save_surfaces / save_meshes or combined variants
  end
  WorkflowConvertImageToVTK-->>convert_image_to_vtk CLI: surfaces and meshes
  convert_image_to_vtk CLI-->>User: written VTP and VTU files
Loading

Possibly related PRs

  • Project-MONAI/physiomotion4d#53: Both PRs touch workflow_fit_statistical_model_to_patient.py, including the path that builds patient models from VTK conversion output.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding volumetric meshing support to the workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/physiomotion4d/segment_chest_total_segmentator_with_contrast.py (1)

55-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Base class hook still typed with lowercase itk.image, creating an inconsistent override signature.

postprocess_after_labelmap now uses itk.Image here, but the upstream hook it overrides — SegmentAnatomyBase.postprocess_after_labelmap in segment_anatomy_base.py:330-349 — is still typed with the lowercase itk.image. Web search confirms ITK's Python wrapping only exposes the capitalized itk.Image template class; there is no documented itk.image symbol, so the base class's annotation was likely relying on an undefined/incorrect name all along. Now that this file fixes the type to itk.Image, the override diverges from its base declaration, which under mypy strict mode (disallow_untyped_defs = true, per coding guidelines) can surface as an incompatible-override warning once the base signature is checked against an actual symbol.

Since the base class file isn't part of this cohort, consider tracking a follow-up to update SegmentAnatomyBase.postprocess_after_labelmap's hints to itk.Image for consistency.

Also applies to: 90-91, 102-102

🤖 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 `@src/physiomotion4d/segment_chest_total_segmentator_with_contrast.py` around
lines 55 - 80, The override signature for postprocess_after_labelmap is now
using itk.Image, but the base hook SegmentAnatomyBase.postprocess_after_labelmap
still needs the same annotation to keep the contract consistent. Update the base
method’s type hints from the incorrect lowercase itk.image to itk.Image so the
override matches cleanly and mypy does not report an incompatible override; use
the postprocess_after_labelmap symbol in SegmentAnatomyBase to locate the
declaration.
src/physiomotion4d/cli/convert_image_to_vtk.py (1)

112-131: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add range validation for the new decimation fractions.

Both --surface-target-reduction and --mesh-target-reduction are documented as fractions in [0, 1), but type=float accepts any value (negative, ≥1, NaN). Invalid values will flow unchecked into decimate_pro/netgen deep in run_workflow, producing a confusing failure instead of a clear CLI error.

🛡️ Proposed fix to validate range at parse time
+def _fraction_below_one(value: str) -> float:
+    fval = float(value)
+    if not (0.0 <= fval < 1.0):
+        raise argparse.ArgumentTypeError(f"must be in [0, 1), got {fval}")
+    return fval
+
     parser.add_argument(
         "--surface-target-reduction",
-        type=float,
+        type=_fraction_below_one,
         default=0.0,
         help=(
             "Fraction in [0, 1) of surface triangles to remove via "
             "decimate_pro (default: 0.0, no decimation)."
         ),
     )
     parser.add_argument(
         "--mesh-target-reduction",
-        type=float,
+        type=_fraction_below_one,
         default=0.0,
         help=(
             "Fraction in [0, 1) of triangles to remove from the surface "
             "(via decimate_pro) before it is meshed into a tetrahedral "
             "volume mesh by netgen; a coarser input surface yields a "
             "coarser volume mesh (default: 0.0, no decimation)."
         ),
     )
🤖 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 `@src/physiomotion4d/cli/convert_image_to_vtk.py` around lines 112 - 131, Add
parse-time range validation for the new CLI options so invalid decimation
fractions are rejected early. Update the argument handling in
convert_image_to_vtk.py for `--surface-target-reduction` and
`--mesh-target-reduction` to enforce values in `[0, 1)` instead of relying on
plain `type=float`. Prefer a small reusable validator/helper used by
`parser.add_argument` so `run_workflow` never receives negative, >=1, or NaN
values, and the CLI reports a clear error.
src/physiomotion4d/workflow_convert_image_to_vtk.py (1)

245-250: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider guarding GenerateVolumeMesh() so one bad group doesn't abort the whole workflow.

GenerateVolumeMesh() is called per anatomy group inside run_workflow's loop after an expensive segmentation step. netgen can raise (or, per your own docstring, misbehave) on surfaces that aren't clean/watertight; an uncaught exception here loses every already-computed group. Since the function already degrades gracefully to None when no 3D elements are produced, treating a meshing failure the same way would be more resilient.

♻️ Suggested resilience handling
-        ngmesh.GenerateVolumeMesh()
-
-        elements = ngmesh.Elements3D()
+        try:
+            ngmesh.GenerateVolumeMesh()
+        except Exception as exc:  # netgen may fail on non-watertight input
+            self.log_warning("netgen volume meshing failed for this surface: %s", exc)
+            return None
+
+        elements = ngmesh.Elements3D()
🤖 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 `@src/physiomotion4d/workflow_convert_image_to_vtk.py` around lines 245 - 250,
Guard the meshing step in `run_workflow`/`workflow_convert_image_to_vtk.py`
around `ngmesh.GenerateVolumeMesh()` so a failure for one anatomy group does not
abort the entire loop. Catch the exception from `GenerateVolumeMesh()` in the
same place where `Elements3D()` is checked, log a warning for that group, and
return `None` just like the existing “no volume mesh” path. This keeps the
per-group workflow resilient while preserving already-computed results.
🤖 Prompt for all review comments with 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.

Nitpick comments:
In `@src/physiomotion4d/cli/convert_image_to_vtk.py`:
- Around line 112-131: Add parse-time range validation for the new CLI options
so invalid decimation fractions are rejected early. Update the argument handling
in convert_image_to_vtk.py for `--surface-target-reduction` and
`--mesh-target-reduction` to enforce values in `[0, 1)` instead of relying on
plain `type=float`. Prefer a small reusable validator/helper used by
`parser.add_argument` so `run_workflow` never receives negative, >=1, or NaN
values, and the CLI reports a clear error.

In `@src/physiomotion4d/segment_chest_total_segmentator_with_contrast.py`:
- Around line 55-80: The override signature for postprocess_after_labelmap is
now using itk.Image, but the base hook
SegmentAnatomyBase.postprocess_after_labelmap still needs the same annotation to
keep the contract consistent. Update the base method’s type hints from the
incorrect lowercase itk.image to itk.Image so the override matches cleanly and
mypy does not report an incompatible override; use the
postprocess_after_labelmap symbol in SegmentAnatomyBase to locate the
declaration.

In `@src/physiomotion4d/workflow_convert_image_to_vtk.py`:
- Around line 245-250: Guard the meshing step in
`run_workflow`/`workflow_convert_image_to_vtk.py` around
`ngmesh.GenerateVolumeMesh()` so a failure for one anatomy group does not abort
the entire loop. Catch the exception from `GenerateVolumeMesh()` in the same
place where `Elements3D()` is checked, log a warning for that group, and return
`None` just like the existing “no volume mesh” path. This keeps the per-group
workflow resilient while preserving already-computed results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5fb6bc23-4c71-4cc3-a15f-7e67f6568a8a

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf7933 and 5d6090d.

📒 Files selected for processing (7)
  • docs/api/workflows.rst
  • docs/troubleshooting.rst
  • pyproject.toml
  • src/physiomotion4d/cli/convert_image_to_vtk.py
  • src/physiomotion4d/segment_chest_total_segmentator_with_contrast.py
  • src/physiomotion4d/workflow_convert_image_to_vtk.py
  • tutorials/tutorial_02_ct_to_vtk.py

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 19.78022% with 73 lines in your changes missing coverage. Please review.
✅ Project coverage is 36.01%. Comparing base (2bf7933) to head (d46e6c2).

Files with missing lines Patch % Lines
src/physiomotion4d/contour_tools.py 16.41% 56 Missing ⚠️
...rc/physiomotion4d/workflow_convert_image_to_vtk.py 21.42% 11 Missing ⚠️
src/physiomotion4d/cli/convert_image_to_vtk.py 25.00% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #78      +/-   ##
==========================================
- Coverage   36.06%   36.01%   -0.05%     
==========================================
  Files          58       58              
  Lines        7271     7292      +21     
==========================================
+ Hits         2622     2626       +4     
- Misses       4649     4666      +17     
Flag Coverage Δ
integration-tests 35.90% <19.78%> (?)
unittests 35.99% <19.78%> (-0.07%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR enhances the image-to-VTK pipeline by switching volumetric meshing from voxel/hexahedral thresholding to netgen-generated tetrahedral volume meshes and exposing surface/mesh decimation controls through the workflow API, CLI, and tutorial/docs.

Changes:

  • Replace VTU generation with netgen-based tetrahedral volume meshing derived from extracted surfaces.
  • Add surface_target_reduction and mesh_target_reduction parameters to WorkflowConvertImageToVTK.run_workflow() and surface them in the CLI and tutorial.
  • Update documentation and dependencies to include netgen support.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tutorials/tutorial_02_ct_to_vtk.py Updates tutorial wording and demonstrates new decimation parameters when running the workflow.
src/physiomotion4d/workflow_convert_image_to_vtk.py Implements netgen tetrahedral meshing, adds reduction parameters, and updates workflow docstrings.
src/physiomotion4d/segment_chest_total_segmentator_with_contrast.py Tightens type hints/return annotations for contrast segmenter methods.
src/physiomotion4d/cli/convert_image_to_vtk.py Updates CLI text to “tetrahedral volume mesh” and adds flags for surface/mesh reduction.
pyproject.toml Adds netgen-mesher dependency and configures mypy to ignore netgen modules.
docs/troubleshooting.rst Adjusts example imports in troubleshooting docs.
docs/api/workflows.rst Updates USD workflow example argument name (usd_project_name).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 227 to 231
meshing_surface = surface.triangulate().clean()
if mesh_target_reduction > 0.0:
meshing_surface = meshing_surface.decimate_pro(
mesh_target_reduction, preserve_topology=True
)
Comment on lines +357 to +361
export_surface = base_surface
if surface_target_reduction > 0.0:
export_surface = export_surface.decimate_pro(
surface_target_reduction, preserve_topology=True
)
Comment on lines +233 to +235
import netgen.meshing as ngm # noqa: PLC0415

triangles = meshing_surface.faces.reshape(-1, 4)[:, 1:4]
Comment on lines 187 to 192
result = workflow.run_workflow(
input_image=input_image,
anatomy_groups=args.anatomy_groups,
surface_target_reduction=args.surface_target_reduction,
mesh_target_reduction=args.mesh_target_reduction,
)
Copilot AI review requested due to automatic review settings July 6, 2026 23:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/physiomotion4d/cli/convert_image_to_vtk.py (1)

112-131: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate the reduction flags in the CLI parser. Both --surface-target-reduction and --mesh-target-reduction are documented as fractions in [0, 1), but the CLI accepts any float and passes it through unchanged. Add parse-time range checks so 1.0 or negative values fail with a clear argument error instead of surfacing later from decimate_pro.

🤖 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 `@src/physiomotion4d/cli/convert_image_to_vtk.py` around lines 112 - 131,
Validate the CLI reduction flags in the parser so `--surface-target-reduction`
and `--mesh-target-reduction` reject values outside [0, 1) at parse time instead
of passing them through to `decimate_pro`. Update the `argparse` setup in
`convert_image_to_vtk.py` to use a range-checking type or validation helper for
both arguments, and make sure invalid values like negatives or 1.0 raise a clear
`ArgumentTypeError`/parser error.
🤖 Prompt for all review comments with 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.

Outside diff comments:
In `@src/physiomotion4d/cli/convert_image_to_vtk.py`:
- Around line 112-131: Validate the CLI reduction flags in the parser so
`--surface-target-reduction` and `--mesh-target-reduction` reject values outside
[0, 1) at parse time instead of passing them through to `decimate_pro`. Update
the `argparse` setup in `convert_image_to_vtk.py` to use a range-checking type
or validation helper for both arguments, and make sure invalid values like
negatives or 1.0 raise a clear `ArgumentTypeError`/parser error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7636b002-cf1a-4471-9e34-3006093bbafa

📥 Commits

Reviewing files that changed from the base of the PR and between 5d6090d and d46e6c2.

📒 Files selected for processing (7)
  • docs/api/workflows.rst
  • src/physiomotion4d/cli/convert_image_to_vtk.py
  • src/physiomotion4d/contour_tools.py
  • src/physiomotion4d/workflow_convert_image_to_vtk.py
  • src/physiomotion4d/workflow_fit_statistical_model_to_patient.py
  • tests/test_workflow_fit_statistical_model_to_patient.py
  • tutorials/tutorial_02_ct_to_vtk.py
✅ Files skipped from review due to trivial changes (1)
  • docs/api/workflows.rst
🚧 Files skipped from review as they are similar to previous changes (1)
  • tutorials/tutorial_02_ct_to_vtk.py

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 15 comments.

Comment on lines 25 to +27
# Combined single-file output (default)
WorkflowConvertImageToVTK.save_combined_surface(result['surfaces'], './out', prefix='patient')
WorkflowConvertImageToVTK.save_combined_mesh(result['meshes'], './out', prefix='patient')
ContourTools.save_combined_surface(result['surfaces'], './out', prefix='patient')
ContourTools.save_combined_mesh(result['meshes'], './out', prefix='patient')
Comment on lines 69 to 73
Each :class:`pyvista.PolyData` surface and :class:`pyvista.UnstructuredGrid` mesh
returned by :meth:`run_workflow` carries:
returned by :meth:`process` carries:

- ``field_data['AnatomyGroup']`` — anatomy group name, e.g. ``'heart'``.
- ``field_data['SegmentationLabelNames']`` — individual structure names within the
Comment on lines +85 to +86
:meth:`ContourTools.save_combined_mesh` — or the CLI
``physiomotion4d-convert-image-to-vtk`` — to write results to disk.
Comment on lines +230 to +234
mesh_target_reduction: Fraction in ``[0, 1)`` of the surface's
triangles to remove (via the same ``decimate_pro`` call)
before it is handed to netgen for tetrahedral meshing. A
coarser input surface yields a coarser volume mesh — netgen
itself has no post-hoc decimation. ``0.0`` (default) skips
Comment on lines +300 to +304
export_surface = base_surface
if surface_target_reduction > 0.0:
export_surface = export_surface.decimate_pro(
surface_target_reduction, preserve_topology=True
)
Comment on lines +583 to +584
meshes: Mapping of name → mesh (e.g. the ``'meshes'`` value from
:meth:`WorkflowConvertImageToVTK.process`).
``{prefix}_{name}.vtu`` (or ``{name}.vtu`` when *prefix* is empty).

Returns:
Mapping of name → absolute path of the saved file.
in the merged file.

Args:
surfaces: Mapping of name → surface.
merged file.

Args:
meshes: Mapping of name → volume mesh.
Comment on lines 38 to 42
Output files — combined mode (default)
---------------------------------------
{prefix}_surfaces.vtp all surfaces merged into one file
{prefix}_meshes.vtu all voxel meshes merged into one file
{prefix}_meshes.vtu all tetrahedral volume meshes merged into one file

@aylward aylward merged commit 775e4ba into Project-MONAI:main Jul 7, 2026
10 of 12 checks passed
@aylward aylward deleted the meshing branch July 7, 2026 01:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants