Skip to content

Commit b1af1c1

Browse files
authored
ENH: Add low-level VTK-to-USD facade and clarify API boundary (#46)
* ENH: Add low-level VTK-to-USD facade and clarify API boundary Add vtk_to_usd.convert_vtk_file() as the stable advanced file-conversion facade, while keeping ConvertVTKToUSD as the in-repo API for experiments, workflows, and tests. Refactor VTK-to-USD tests to validate behavior through ConvertVTKToUSD, replace experiment diagnostics with ConvertVTKToUSD.inspect_file(), and update docs/API map to remove stale converter APIs. * ENH: Clarify VTK-to-USD API boundary and add file facade Add vtk_to_usd.convert_vtk_file() as the stable advanced low-level single-file conversion facade while keeping ConvertVTKToUSD as the in-repo API for experiments, workflows, and tests. Add ConvertVTKToUSD.inspect_file() for public diagnostics, update experiments to use it, refactor tests to exercise conversion through ConvertVTKToUSD, and refresh docs/API map to remove stale converter APIs. Handle empty mesh inspection without raising. * DOCS: consolidate VTK-to-USD API documentation Remove duplicate ConvertVTKToUSD API pages, clean stale navigation links, clarify VTK-to-USD scaling and primvar documentation, and remove an unused Valve4D test helper.
1 parent 54d1d5e commit b1af1c1

23 files changed

Lines changed: 638 additions & 1518 deletions

README.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ print(f"PhysioMotion4D version: {physiomotion4d.__version__}")
127127
- `ContourTools`: Mesh extraction and contour manipulation
128128
- **USD Conversion**: VTK to USD conversion for Omniverse visualization
129129
- `ConvertVTKToUSD`: High-level converter for PyVista/VTK objects with colormap support
130-
- `vtk_to_usd` module: File-based conversion library
131-
- `VTKToUSDConverter`: Core converter with time-series support
130+
- `vtk_to_usd` module: Advanced low-level file conversion library
131+
- `convert_vtk_file()`: Single-file VTK/VTP/VTU to USD facade
132132
- `read_vtk_file()`: Read VTK/VTP/VTU files into MeshData
133133
- `ConversionSettings`: Configurable conversion parameters
134134
- `MaterialData`: USD material definitions
@@ -307,7 +307,7 @@ forward_transform = results["forward_transform"] # Moving to fixed
307307

308308
### VTK to USD Conversion
309309

310-
PhysioMotion4D provides two APIs for converting VTK data to USD for NVIDIA Omniverse visualization:
310+
PhysioMotion4D provides two APIs for converting VTK data to USD for NVIDIA Omniverse visualization. Repository workflows, experiments, and CLIs use `ConvertVTKToUSD`; `vtk_to_usd` is a public advanced layer for users who need low-level file conversion primitives.
311311

312312
#### Option 1: High-Level ConvertVTKToUSD (for PyVista/VTK objects)
313313

@@ -336,11 +336,10 @@ converter.set_colormap(
336336
stage = converter.convert('cardiac_motion.usd')
337337
```
338338

339-
#### Option 2: File-Based vtk_to_usd Library
339+
#### Option 2: Advanced File-Based vtk_to_usd Facade
340340

341341
```python
342342
from physiomotion4d.vtk_to_usd import (
343-
VTKToUSDConverter,
344343
ConversionSettings,
345344
MaterialData,
346345
convert_vtk_file,
@@ -349,11 +348,11 @@ from physiomotion4d.vtk_to_usd import (
349348
# Simple single-file conversion
350349
stage = convert_vtk_file('mesh.vtp', 'output.usd')
351350

352-
# Advanced: Custom settings and materials
351+
# Advanced: custom settings and material
353352
settings = ConversionSettings(
354353
triangulate_meshes=True,
355354
compute_normals=True,
356-
meters_per_unit=0.001, # mm to meters
355+
meters_per_unit=1.0, # USD stage units after built-in mm-to-m scaling
357356
times_per_second=60.0,
358357
)
359358

@@ -363,20 +362,19 @@ material = MaterialData(
363362
roughness=0.4,
364363
)
365364

366-
converter = VTKToUSDConverter(settings)
367-
stage = converter.convert_file('heart.vtp', 'heart.usd', material=material)
368-
369-
# Time-series conversion
370-
files = ['frame_000.vtp', 'frame_001.vtp', 'frame_002.vtp']
371-
time_codes = [0.0, 0.1, 0.2]
372-
stage = converter.convert_sequence(files, 'animated.usd', time_codes=time_codes)
365+
stage = convert_vtk_file(
366+
'heart.vtp',
367+
'heart.usd',
368+
data_basename='Heart',
369+
settings=settings,
370+
material=material,
371+
)
373372
```
374373

375374
Features:
376375
- Automatic coordinate system conversion (RAS to Y-up)
377376
- Material system with UsdPreviewSurface
378377
- Preserves all VTK data arrays as USD primvars
379-
- Time-series animation support
380378
- Supports VTP, VTK, and VTU file formats
381379

382380
### Logging and Debug Control

docs/API_MAP.md

Lines changed: 47 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
1515

1616
## experiments/Convert_VTK_To_USD/convert_vtk_to_usd_using_class.py
1717

18-
- `def create_deformed_pv_mesh(base, time_step, num_steps=10)` (line 237): Return a sinusoidally scaled copy of base with a synthetic pressure field.
19-
- `def verify_usd_file(usd_path)` (line 315): Verify USD file integrity.
18+
- `def create_deformed_pv_mesh(base, time_step, num_steps=10)` (line 236): Return a sinusoidally scaled copy of base with a synthetic pressure field.
19+
- `def verify_usd_file(usd_path)` (line 314): Verify USD file integrity.
2020

2121
## experiments/DisplacementField_To_USD/displacement_field_to_usd.py
2222

@@ -115,13 +115,14 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
115115

116116
## src/physiomotion4d/convert_vtk_to_usd.py
117117

118-
- **class ConvertVTKToUSD** (line 38): Advanced VTK to USD converter with colormap and anatomical labeling support.
119-
- `def __init__(self, data_basename, input_polydata, mask_ids=None, compute_normals=False, convert_to_surface=True, times_per_second=24.0, separate_by='none', solid_color=(0.8, 0.8, 0.8), log_level=logging.INFO)` (line 68): Initialize converter.
120-
- `def from_files(cls, data_basename, vtk_files, *, extract_surface=True, separate_by='none', times_per_second=24.0, solid_color=(0.8, 0.8, 0.8), time_codes=None, static_merge=False, mask_ids=None, log_level=logging.INFO)` (line 139): Create a converter by loading VTK files from disk.
121-
- `def supports_mesh_type(self, mesh)` (line 239): Check if mesh type is supported for conversion.
122-
- `def list_available_arrays(self)` (line 267): List all point data arrays available across all time steps.
123-
- `def set_colormap(self, color_by_array=None, colormap='plasma', intensity_range=None)` (line 313): Configure colormap for visualization.
124-
- `def convert(self, output_usd_file, convert_to_surface=None, compute_normals=None)` (line 347): Convert VTK meshes to USD.
118+
- **class ConvertVTKToUSD** (line 41): Advanced VTK to USD converter with colormap and anatomical labeling support.
119+
- `def __init__(self, data_basename, input_polydata, mask_ids=None, compute_normals=False, convert_to_surface=True, times_per_second=24.0, separate_by='none', solid_color=(0.8, 0.8, 0.8), log_level=logging.INFO)` (line 71): Initialize converter.
120+
- `def from_files(cls, data_basename, vtk_files, *, extract_surface=True, separate_by='none', times_per_second=24.0, solid_color=(0.8, 0.8, 0.8), time_codes=None, static_merge=False, mask_ids=None, log_level=logging.INFO)` (line 142): Create a converter by loading VTK files from disk.
121+
- `def supports_mesh_type(self, mesh)` (line 240): Check if mesh type is supported for conversion.
122+
- `def inspect_file(cls, vtk_file, *, extract_surface=True)` (line 269): Summarize a VTK file using the same low-level reader as conversion.
123+
- `def list_available_arrays(self)` (line 341): List all point data arrays available across all time steps.
124+
- `def set_colormap(self, color_by_array=None, colormap='plasma', intensity_range=None)` (line 387): Configure colormap for visualization.
125+
- `def convert(self, output_usd_file, convert_to_surface=None, compute_normals=None)` (line 421): Convert VTK meshes to USD.
125126

126127
## src/physiomotion4d/image_tools.py
127128

@@ -343,6 +344,10 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
343344
- `def list_mesh_paths_under(self, stage_or_path, parent_path='/World/Meshes')` (line 1103): List paths of all mesh prims under a parent path.
344345
- `def repair_mesh_primvar_element_sizes(self, stage_or_path, mesh_path, *, time_code=None, save=True)` (line 1130): Repair missing/incorrect primvar elementSize metadata for a mesh.
345346

347+
## src/physiomotion4d/vtk_to_usd/converter.py
348+
349+
- `def convert_vtk_file(vtk_file, output_usd_file, *, data_basename=None, mesh_name='Mesh', extract_surface=True, settings=None, material=None)` (line 14): Convert one VTK file to one USD stage.
350+
346351
## src/physiomotion4d/vtk_to_usd/data_structures.py
347352

348353
- **class DataType** (line 13): Data type enumeration for generic arrays.
@@ -390,14 +395,14 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
390395
## src/physiomotion4d/vtk_to_usd/vtk_reader.py
391396

392397
- **class VTKReader** (line 20): Base class for VTK file readers.
393-
- **class PolyDataReader** (line 222): Reader for VTK PolyData files (.vtp).
394-
- `def read(filename)` (line 226): Read a VTP file and return MeshData.
395-
- **class LegacyVTKReader** (line 282): Reader for legacy VTK files (.vtk).
396-
- `def read(filename, extract_surface=True)` (line 294): Read a legacy VTK file and return MeshData.
397-
- **class UnstructuredGridReader** (line 455): Reader for VTK UnstructuredGrid files (.vtu).
398-
- `def read(filename, extract_surface=True)` (line 459): Read a VTU file and return MeshData.
399-
- `def read_vtk_file(filename, extract_surface=True)` (line 568): Auto-detect VTK file format and read appropriately.
400-
- `def validate_time_series_topology(mesh_data_sequence, filenames=None)` (line 596): Validate topology consistency across a time series of meshes.
398+
- **class PolyDataReader** (line 234): Reader for VTK PolyData files (.vtp).
399+
- `def read(filename)` (line 238): Read a VTP file and return MeshData.
400+
- **class LegacyVTKReader** (line 294): Reader for legacy VTK files (.vtk).
401+
- `def read(filename, extract_surface=True)` (line 306): Read a legacy VTK file and return MeshData.
402+
- **class UnstructuredGridReader** (line 467): Reader for VTK UnstructuredGrid files (.vtu).
403+
- `def read(filename, extract_surface=True)` (line 471): Read a VTU file and return MeshData.
404+
- `def read_vtk_file(filename, extract_surface=True)` (line 580): Auto-detect VTK file format and read appropriately.
405+
- `def validate_time_series_topology(mesh_data_sequence, filenames=None)` (line 608): Validate topology consistency across a time series of meshes.
401406

402407
## src/physiomotion4d/workflow_convert_ct_to_vtk.py
403408

@@ -707,44 +712,31 @@ _Re-run `py utils/generate_api_map.py` whenever public APIs change._
707712

708713
## tests/test_vtk_to_usd_library.py
709714

710-
- `def get_data_dir()` (line 34): Get the data directory path.
711-
- `def check_kcl_heart_data()` (line 41): Check if KCL Heart Model data is available.
712-
- `def check_valve4d_data()` (line 48): Check if CHOP Valve4D data is available.
713-
- `def get_or_create_average_surface(test_directories)` (line 55): Get or create average_surface.vtp from average_mesh.vtk.
714-
- `def kcl_average_surface(test_directories)` (line 101): Fixture providing the KCL average heart surface.
715-
- **class TestGenericArray** (line 117): Test GenericArray data structure validation and reshaping.
716-
- `def test_scalar_1d_array(self)` (line 120): Test that 1D scalar arrays (num_components=1) are kept as-is.
717-
- `def test_flat_multicomponent_array_reshape(self)` (line 133): Test that flat 1D arrays with num_components>1 are reshaped to 2D.
718-
- `def test_2d_array_valid(self)` (line 149): Test that 2D arrays with correct shape are accepted.
719-
- `def test_flat_array_not_divisible_raises_error(self)` (line 162): Test that flat arrays with length not divisible by num_components raise error.
720-
- `def test_2d_array_wrong_shape_raises_error(self)` (line 173): Test that 2D arrays with wrong shape raise error.
721-
- `def test_3d_array_raises_error(self)` (line 184): Test that 3D arrays are rejected.
722-
- `def test_flat_array_large_components(self)` (line 195): Test reshaping with large num_components (e.g., 9 for 3x3 tensors).
723-
- **class TestFromFilesValidation** (line 210): Synthetic tests for ConvertVTKToUSD.from_files() — no real data required.
724-
- `def test_time_codes_length_mismatch_raises(self, tmp_path)` (line 222): from_files() must reject time_codes whose length != len(vtk_files).
725-
- `def test_time_codes_non_monotone_raises(self, tmp_path)` (line 232): from_files() must reject time_codes that decrease between frames.
726-
- `def test_time_codes_equal_consecutive_is_valid(self, tmp_path)` (line 242): Equal consecutive time codes are non-decreasing and must not raise.
727-
- `def test_from_files_populates_cached_mesh_data(self, tmp_path)` (line 256): from_files() with >1 frame must populate _cached_mesh_data.
728-
- `def test_from_files_cache_reused_in_convert(self, tmp_path)` (line 269): _convert_unified() must not call _vtk_to_mesh_data() when cache is populated.
729-
- `def test_from_files_single_file_no_cache(self, tmp_path)` (line 287): A single-file converter must not populate _cached_mesh_data.
730-
- `def test_from_files_static_merge_no_cache(self, tmp_path)` (line 295): static_merge=True must not populate _cached_mesh_data.
731-
- **class TestVTKReader** (line 307): Test VTK file reading capabilities.
732-
- `def test_read_vtp_file(self, kcl_average_surface)` (line 310): Test reading VTP (PolyData) files.
733-
- `def test_read_legacy_vtk_file(self)` (line 331): Test reading legacy VTK files.
734-
- `def test_generic_arrays_preserved(self, kcl_average_surface)` (line 358): Test that generic data arrays are preserved during reading.
735-
- **class TestVTKToUSDConversion** (line 382): Test VTK to USD conversion capabilities.
736-
- `def test_single_file_conversion(self, test_directories, kcl_average_surface)` (line 385): Test converting a single VTK file to USD.
737-
- `def test_conversion_with_material(self, test_directories, kcl_average_surface)` (line 417): Test conversion with a custom solid color material.
738-
- `def test_conversion_settings(self, test_directories, kcl_average_surface)` (line 455): Test that ConvertVTKToUSD applies correct default stage metadata.
739-
- `def test_primvar_preservation(self, test_directories, kcl_average_surface)` (line 478): Test that VTK data arrays are preserved as USD primvars.
740-
- **class TestTimeSeriesConversion** (line 514): Test time-series conversion capabilities.
741-
- `def test_time_series_conversion(self, test_directories, kcl_average_surface)` (line 517): Test converting multiple VTK files as a time series.
742-
- **class TestIntegration** (line 557): Integration tests combining multiple features.
743-
- `def test_end_to_end_conversion(self, test_directories, kcl_average_surface)` (line 560): Test complete conversion workflow with all features.
744-
- **class TestUnitScaling** (line 603): Verify that VTK mm coordinates are converted to USD meter coordinates.
745-
- `def test_mm_to_m_point_scaling(self, tmp_path)` (line 606): Points written to USD must be 0.001× their original mm values.
746-
- `def test_normals_remain_unit_length(self, tmp_path)` (line 638): Normal vectors must not be scaled — they should remain unit length.
747-
- `def test_stage_meters_per_unit(self, tmp_path)` (line 664): Stage metersPerUnit metadata must be 1.0 (coordinates stored in meters).
715+
- `def get_data_dir()` (line 24): Get the data directory path.
716+
- `def check_kcl_heart_data()` (line 31): Check if KCL Heart Model data is available.
717+
- `def get_or_create_average_surface(test_directories)` (line 38): Get or create average_surface.vtp from average_mesh.vtk.
718+
- `def kcl_average_surface(test_directories)` (line 65): Fixture providing the KCL average heart surface.
719+
- **class TestFromFilesValidation** (line 78): Synthetic tests for ConvertVTKToUSD.from_files().
720+
- `def test_time_codes_length_mismatch_raises(self, tmp_path)` (line 81): from_files() must reject time_codes whose length != len(vtk_files).
721+
- `def test_time_codes_non_monotone_raises(self, tmp_path)` (line 91): from_files() must reject time_codes that decrease between frames.
722+
- `def test_time_codes_equal_consecutive_is_valid(self, tmp_path)` (line 101): Equal consecutive time codes are non-decreasing and must not raise.
723+
- `def test_from_files_single_file_writes_static_mesh(self, tmp_path)` (line 113): A single-file converter writes a static mesh with no time range.
724+
- `def test_from_files_static_merge_writes_separate_meshes(self, tmp_path)` (line 125): static_merge=True treats files as static objects, not time samples.
725+
- **class TestSyntheticConversion** (line 144): Synthetic ConvertVTKToUSD tests that do not require downloaded data.
726+
- `def test_inspect_file_reports_public_summary(self, tmp_path)` (line 147): inspect_file() reports geometry, bounds, arrays, and cell types.
727+
- `def test_inspect_file_reports_empty_mesh(self, tmp_path)` (line 167): inspect_file() reports empty meshes without raising.
728+
- `def test_file_primvar_preservation(self, tmp_path)` (line 183): Point arrays in a VTP file are preserved as USD primvars.
729+
- `def test_time_series_conversion(self, tmp_path)` (line 202): Multiple VTP files write point time samples and stage time metadata.
730+
- **class TestVTKToUSDConversion** (line 226): Test ConvertVTKToUSD on optional real VTK data.
731+
- `def test_single_file_conversion(self, test_directories, kcl_average_surface)` (line 229): Test converting a single VTK file to USD.
732+
- `def test_conversion_with_material(self, test_directories, kcl_average_surface)` (line 248): Test conversion with a custom solid color material.
733+
- `def test_conversion_settings(self, test_directories, kcl_average_surface)` (line 276): Test that ConvertVTKToUSD applies correct default stage metadata.
734+
- **class TestIntegration** (line 293): Integration tests combining multiple features.
735+
- `def test_end_to_end_conversion(self, test_directories, kcl_average_surface)` (line 296): Test complete conversion workflow with all features.
736+
- **class TestUnitScaling** (line 319): Verify that VTK mm coordinates are converted to USD meter coordinates.
737+
- `def test_mm_to_m_point_scaling(self, tmp_path)` (line 322): Points written to USD must be 0.001x their original mm values.
738+
- `def test_normals_remain_unit_length(self, tmp_path)` (line 342): Normal vectors must not be scaled.
739+
- `def test_stage_meters_per_unit(self, tmp_path)` (line 362): Stage metersPerUnit metadata must be 1.0.
748740

749741
## utils/claude_github_reviews.py
750742

docs/api/index.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ This section provides detailed documentation for all PhysioMotion4D classes, fun
4949
usd/tools
5050
usd/anatomy_tools
5151
usd/vtk_conversion
52-
usd/polymesh
53-
usd/tetmesh
5452

5553
.. toctree::
5654
:maxdepth: 2

docs/api/usd/index.rst

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@ USD generation tools for creating animated 3D models from medical images:
1313

1414
* **USD Tools**: Core USD file operations
1515
* **Anatomy Tools**: Anatomical structure handling
16-
* **VTK Conversion**: Convert VTK meshes to USD
17-
* **PolyMesh**: Surface mesh representation
18-
* **TetMesh**: Tetrahedral mesh representation
16+
* **VTK Conversion**: Convert VTK meshes to USD with :class:`ConvertVTKToUSD`
1917

2018
Quick Links
2119
===========
@@ -24,8 +22,6 @@ Quick Links
2422
* :doc:`tools` - Core USD utilities
2523
* :doc:`anatomy_tools` - Anatomical structure tools
2624
* :doc:`vtk_conversion` - VTK to USD conversion
27-
* :doc:`polymesh` - Surface mesh USD
28-
* :doc:`tetmesh` - Tetrahedral mesh USD
2925

3026
Module Documentation
3127
====================
@@ -36,8 +32,6 @@ Module Documentation
3632
tools
3733
anatomy_tools
3834
vtk_conversion
39-
polymesh
40-
tetmesh
4135

4236
Quick Start
4337
===========
@@ -49,16 +43,12 @@ Convert VTK to USD
4943
5044
from physiomotion4d import ConvertVTKToUSD
5145
52-
converter = ConvertVTKToUSD(
53-
output_file="animated_heart.usd",
54-
colormap="rainbow",
55-
verbose=True
56-
)
57-
58-
converter.convert(
46+
converter = ConvertVTKToUSD.from_files(
47+
data_basename="Heart",
5948
vtk_files=["heart_phase_00.vtk", "heart_phase_01.vtk"],
60-
time_points=[0.0, 0.1, 0.2]
49+
time_codes=[0.0, 1.0],
6150
)
51+
stage = converter.convert("animated_heart.usd")
6252
6353
Create Anatomical Scene
6454
-----------------------

docs/api/usd/polymesh.rst

Lines changed: 0 additions & 20 deletions
This file was deleted.

docs/api/usd/tetmesh.rst

Lines changed: 0 additions & 20 deletions
This file was deleted.

docs/api/usd/vtk_conversion.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ Class Reference
1717

1818
.. rubric:: Navigation
1919

20-
:doc:`anatomy_tools` | :doc:`index` | :doc:`polymesh`
20+
:doc:`anatomy_tools` | :doc:`index`

docs/api/utilities/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,4 @@ See Also
4444

4545
.. rubric:: Navigation
4646

47-
:doc:`../usd/tetmesh` | :doc:`../index` | :doc:`image_tools`
47+
:doc:`../usd/vtk_conversion` | :doc:`../index` | :doc:`image_tools`

docs/developer/architecture.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ Most PhysioMotion4D classes inherit from :class:`PhysioMotion4DBase`:
103103
│ │ └── RegisterImagesICON
104104
│ └── (Model registration classes)
105105
└── Conversion Classes
106-
└── ConvertVTKToUSDBase
107-
├── ConvertVTKToUSDPolyMesh
108-
└── ConvertVTKToUSDTetMesh
106+
└── ConvertVTKToUSD
109107
110108
The base class provides:
111109

0 commit comments

Comments
 (0)