Skip to content

Add transducer array hardware integration - #506

Merged
ebrahimebrahim merged 6 commits into
mainfrom
transducer-manager-library-components
Sep 11, 2026
Merged

ebrahimebrahim merged 6 commits into
mainfrom
transducer-manager-library-components

Conversation

@ebrahimebrahim

@ebrahimebrahim ebrahimebrahim commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

This adds the support needed for SlicerOpenLIFU’s transducer manager (OpenwaterHealth/SlicerOpenLIFU#662) to construct arrays from module configurations, read connected hardware, compare arrays with database definitions, and prepare device metadata for writeback.

Included aspects

The following functionality has been adapted from Peter’s #478:

  • Module construction: Transducer.from_module_user_config() builds elements and calibration from the module configuration and preserves its hardware ID.
  • Array construction: TransducerArray.from_module_user_configs() combines those modules with template placement, meshes, standoff, polarity, and metadata. Device metadata overrides template values, and explicit arguments override both. Module geometry and calibration come from the configurations.
  • Connected-device loading: get_connected() reads each module, checks frequencies and stored device identity, and selects a recorded or inferred template. It supports database templates and embedded meshless defaults for one or two modules at 155 or 400 kHz.
  • Device metadata serialization: to_device_config() produces a JSON-compatible device block containing array identity, ordered hardware IDs and transforms, and array attributes. The caller adds any template reference and performs hardware writeback.
  • Database comparison: arrays_structurally_equal() compares definitions while tolerating small numerical noise, list/NumPy differences, and different directories for the same mesh filename. It excludes impulse-response attributes. get_connected() warns when a same-ID database definition differs.
  • Standoff normalization: list-valued standoff transforms become numeric matrices during transducer construction and merging, supporting flattening and serialization.

This PR ports those items and then also adds the following fixes and refinements:

  • Validation also applies to the hardware-independent constructor. It checks device module counts and hardware IDs, so callers using captured configurations cannot bypass validation. Nonempty device blocks without module entries are rejected.
  • Module association preserves distinct placements. Unique hardware-ID matches are reserved first; remaining entries are assigned once each, preferring position. Duplicate IDs remain permitted, but impossible associations are rejected.
  • Geometry retains its physical scale. Inherited template placement and standoff translations are converted between units. Device-stored array standoff is converted when reordering changes the first module’s units. Ambiguous stored translation units are rejected.
  • Mesh inheritance has unit checks. References requiring mesh-coordinate rescaling are rejected, including mixed-unit reordering. Actual mesh rescaling is not implemented.
  • Construction, comparison, and array serialization preserve their inputs. Mutable configuration, template, and serialized data are copied to prevent changes from propagating between objects.
  • Interface ownership is respected. get_connected() closes interfaces it creates, including on failure, and leaves caller-owned interfaces open. SDK imports happen only when creating an interface.
  • An explicit None array-standoff override means identity, and the resulting array can be flattened.

The dependency change raises the optional SDK minimum to 2.0.14 while retaining main’s other constraints and Python 3.10 support.

Tests

This an inventory of all the tests that are added in this PR, with their origin/provenance (Peter = carried over from Peter's branch, Adapted = rewritten/combined/expanded from tests in Peter's branch, New = constructed for this branch to test new fixes or previously untested behavior).

In test_transducer.py, 27 tests were added:

  • test_transducer_from_module_user_config (Peter): A module configuration produces the expected elements, identity, frequency, sensitivity, and hardware ID.
  • test_transducer_from_module_user_config_missing_module (Peter): A configuration without module data is rejected.
  • test_transducer_array_from_module_user_configs_bare (Peter): Two configurations form an array with their hardware IDs and identity placements when no placement source is supplied.
  • test_transducer_array_from_module_user_configs_with_device_field (Peter): Device metadata supplies array identity and mesh metadata, and transforms follow hardware IDs despite reordered entries.
  • test_transducer_array_from_module_user_configs_with_template (Peter): Template identity, mesh filenames, and module placements are inherited.
  • test_transducer_array_from_module_user_configs_module_transforms_override (Peter): Explicit module transforms override transforms stored in device metadata.
  • test_transducer_array_from_module_user_configs_empty_raises (Peter): Constructing an array from no configurations is rejected.
  • test_transducer_array_from_module_user_configs_length_mismatch_raises (Peter): The explicit transform list must contain one transform per configuration.
  • test_transducer_array_from_module_user_configs_explicit_arr_id_name_override (Peter): Explicit array ID and name override device metadata.
  • test_transducer_array_from_module_user_configs_arr_id_falls_through (Peter): Template ID and name are used when no higher-priority identity is supplied.
  • test_transducer_from_module_user_config_requires_nonempty_dict (New): Module data must be a nonempty dictionary; null, empty, and other types are rejected.
  • test_transducer_from_module_user_config_geometry_and_independence (New): Element positions, sizes, numbering, units, and calibration are correct, and editing the result or input does not alter the other.
  • test_transducer_from_module_user_config_without_hwid (New): A configuration without a hardware ID can still produce a module.
  • test_transducer_array_from_module_user_configs_precedence_and_independence (New): Template, device, and explicit values have the intended precedence; hardware calibration is retained; mutable inputs remain independent.
  • test_transducer_array_device_transforms_use_position_without_hwids (New): Stored transforms use positional matching when their entries omit hardware IDs.
  • test_transducer_array_pure_constructor_validates_device_identity (New): The pure constructor enforces count and hardware-ID rules, including reordered, missing, partial, and duplicate IDs.
  • test_transducer_array_pure_constructor_accepts_no_device_metadata (New): Null or empty device metadata permits construction.
  • test_transducer_array_pure_constructor_rejects_device_without_modules (New): Nonempty device metadata with no recorded modules fails count validation.
  • test_standoff_construction_merge_and_roundtrips (Adapted): List and NumPy standoffs remain correct through construction, merging, dict/JSON round-trips, and flattening. Replaces Peter’s HTML-representation regression with these checks.
  • test_transducer_rejects_invalid_standoff_shape (New): Direct transducer construction and merging reject standoffs that are not 4×4 matrices, including None.
  • test_transducer_array_to_device_config_shape_and_independence (Adapted): Extends Peter’s device serialization test with exact output fields, JSON compatibility, reconstruction, and independence from later edits.
  • test_transducer_array_dict_serialization_does_not_alias_inputs (New): Array dict serialization and reconstruction leave inputs unchanged and do not share mutable data.
  • test_template_geometry_preserves_physical_units (New): Physically equivalent millimeter, centimeter, and mixed-unit configurations preserve element placement and standoff through construction, flattening, and device round-trips.
  • test_template_unit_conversion_preserves_device_and_explicit_overrides (New): Template conversion respects overrides; a null array-standoff override becomes identity and survives serialization and flattening.
  • test_translated_array_standoff_requires_template_units (New): A translated template standoff requires a module to establish its units; identity standoff does not.
  • test_embedded_template_translations_are_in_millimeters (New): Embedded two-module templates declare millimeter translations and convert correctly for centimeter configurations.
  • test_template_geometry_rejects_incompatible_units (New): Incompatible unit types, such as seconds versus centimeters, are rejected.

In test_transducer_array_device_config.py, 29 tests were added:

  • test_get_connected_validates_device_identity_before_database_lookup (Adapted): Combines Peter’s count/HWID validation scenarios, adds partial and duplicate IDs, and verifies rejection happens before database lookup.
  • test_get_connected_accepts_absent_or_empty_device (New): Missing or empty device metadata allows normal template inference.
  • test_get_connected_rejects_metadata_only_device (New): Device metadata containing identity but no module entries is rejected.
  • test_get_connected_infers_embedded_template (Adapted): Expands Peter’s fallback tests to all four count/frequency combinations and verifies placements, meshlessness, and module reads.
  • test_get_connected_prefers_recorded_database_template (Adapted): Combines recorded-template and database-mesh scenarios; verifies template use with fallback enabled or disabled while retaining hardware calibration.
  • test_get_connected_database_template_unavailable (New): Missing, failed, or wrong-type database template loads use the embedded fallback only when enabled.
  • test_get_connected_unknown_recorded_template_does_not_infer_replacement (New): An unavailable custom template is not silently replaced with a different inferred template.
  • test_get_connected_unknown_frequency_constructs_without_template (Adapted): Preserves Peter’s unknown-frequency scenario and adds missing frequency; construction proceeds with identity placement.
  • test_get_connected_forwards_explicit_overrides (Adapted): Connected loading honors explicit array identity and module transforms.
  • test_get_connected_checks_frequencies_before_template_lookup (Adapted): Extends Peter’s frequency-mismatch test to configurations with a recorded template and verifies no database lookup occurs.
  • test_get_connected_closes_only_owned_interface (New): Library-created interfaces close exactly once on success and many failure paths; supplied interfaces remain open. Includes Peter’s no-modules failure scenario.
  • test_optional_sdk_import_in_fresh_process (New): Pure construction and injected interfaces work without importing the SDK; implicit creation reports missing SDK and preserves unrelated import errors.
  • test_get_connected_database_comparison_warning (Adapted): Combines Peter’s matching/differing database tests and additionally verifies the stored array remains unchanged.
  • test_arrays_structurally_equal_normalizes_without_mutating_inputs (Adapted): Expands Peter’s mesh-basename test to numerical noise, list/NumPy forms, ignored impulse fields, and input preservation.
  • test_arrays_structurally_equal_detects_meaningful_differences (New): Changes to names, hardware IDs, geometry, placements, mesh names, and attributes are detected.
  • test_to_device_config_has_independent_json_compatible_data (New): A connected array produces the expected device block, reconstructs equivalently, and remains unchanged when serialized data is edited.
  • test_device_config_round_trip_preserves_placements_with_duplicate_hwids (New): Modules sharing the simulator’s "ABCDEFGH" ID retain distinct placements through both reconstruction APIs.
  • test_device_transforms_match_only_unambiguous_hwids (New): Reordered unique IDs and combinations of duplicate or missing IDs receive the expected distinct stored transforms.
  • test_device_transform_matching_reserves_entries_before_duplicate_fallback (New): A unique-ID match reserves its entry before duplicate-ID modules receive the remaining placements.
  • test_device_transform_matching_rejects_incompatible_id_multiplicities (New): Matching ID sets and total counts are insufficient when repeated-ID counts prevent a valid one-to-one assignment.
  • test_translated_device_placements_reject_ambiguous_mixed_units (New): Stored placement translations are rejected when duplicate IDs leave their millimeter/centimeter interpretation ambiguous.
  • test_explicit_placements_override_ambiguous_mixed_unit_device_placements (New): Explicit placements resolve that ambiguity and produce correct flattened geometry without changing input configurations.
  • test_device_transform_matching_rejects_non_dictionary_entry (New): Malformed recorded module entries produce a clear association error.
  • test_device_standoff_preserves_units_when_modules_are_reordered (New): An 8 mm standoff remains physically 8 mm with unchanged or reversed mixed-unit module order, through both APIs.
  • test_translated_device_standoff_rejects_ambiguous_mixed_units (New): Translated standoff is rejected when missing or duplicate IDs leave its original unit basis ambiguous.
  • test_device_standoff_allows_ambiguous_ids_when_unit_conversion_is_unnecessary (New): Duplicate IDs remain acceptable when modules share units or the standoff has no translation.
  • test_device_standoff_infers_missing_recorded_id_units_from_remaining_module (New): Matching known IDs can identify the remaining unnamed module and recover the correct standoff units after reordering.
  • test_device_standoff_accepts_matching_origin_units_in_otherwise_mixed_array (New): Ambiguous identity is acceptable when every possible source of the standoff uses the same units, even if other modules differ.
  • test_connected_array_saves_loads_and_flattens_with_temporary_mesh_files (New): A connected array and real temporary mesh files survive database persistence and flatten with the expected geometry and standoff.

In test_transducer_array_mesh_units.py, five tests were added:

  • test_inherited_mesh_matches_elements_after_database_roundtrip (New): Loaded mesh vertices align with element geometry for equivalent unit names; centimeter inheritance is rejected. Covers both mesh types and module/array references.
  • test_array_mesh_override_preserves_unit_guard (New): Keeping or renaming a mesh cannot bypass unit checks; explicitly clearing the reference permits construction.
  • test_array_mesh_requires_template_module_units (New): An array mesh cannot be inherited from a template with no module establishing its units.
  • test_array_mesh_uses_first_module_unit_basis (New): Array mesh units follow the first module; changing another module’s units alone does not invalidate the mesh.
  • test_device_mesh_rejects_reordered_module_units (New): Device mesh references survive unchanged order but reject reordering that changes their unit interpretation; cleared references remain acceptable.

Omitted features

The following features from #478 were not ported over in this PR, as this PR is focused on the transducer manager:

  • Rich model representations: text/HTML representations and summaries for elements, transducers, and arrays, including their representation-specific tests.
  • Display metadata infrastructure: the OpenLIFUFieldData redesign, field_display.py, and display annotations/summaries across beamforming, segmentation, simulation, and virtual-fit models.
  • Session restructuring: Plan, PlanningSession, and SonicationSession, their database persistence, and split-session filename conventions.
  • Photoscan/session changes: subject-scoped photoscans, separate photoscan registrations, and cleanup of stale photoscan references.
  • Solution persistence and provenance: stored analyses, session solution references, SolutionInfo approval/timestamps/transforms, subject-scoped solution storage, and solution deletion/orphan cleanup.
  • Solution-analysis changes: beam-geometry-based analysis regions and related metrics.
  • Thermal simulation: heat-source computation, pulse-event generation, thermal simulation, and related reporting changes.
  • Rastering and treatment calculation changes: Wheel/focal-pattern updates, multiple-focus ISPTA changes, and associated protocol/solution behavior.
  • Cloud changes: environment-dependent endpoints and removal of obsolete Systems synchronization.
  • Unrelated tooling and dependencies: the connected-module version-inspection script, console self-test changes, broad dependency minimums/removals, NumPy/ONNX constraint changes, and incidental helper/style changes supporting those workstreams.

Their associated tests were also omitted. The retained library work supplies the APIs for the separate Slicer manager implementation; its UI, overwrite/deletion policies, mesh copying, and hardware writeback remain application responsibilities.

For review

Please review OpenwaterHealth/SlicerOpenLIFU#662, which uses this branch, and that should be a sufficient review of the functionality here.

@ebrahimebrahim ebrahimebrahim changed the title Transducer manager library components Add transducer array hardware integration Sep 8, 2026
ebrahimebrahim and others added 6 commits September 9, 2026 15:08
Signed-off-by: Ebrahim Ebrahim <ebrahim.ebrahim@kitware.com>
Combine module geometry and calibration with template placement and mesh
metadata. Check recorded module counts and hardware IDs, and preserve inputs
during construction and serialization. Normalize standoff transforms for
flattening and JSON round-trips.

Adapted from #478

Co-authored-by: Peter Hollender <peterhollender@gmail.com>
Signed-off-by: Ebrahim Ebrahim <ebrahim.ebrahim@kitware.com>
Read module configurations, select a recorded or inferred template, and warn
when the array differs from its database definition. Import the SDK on demand
and close interfaces created by the library. Require SDK 2.0.14 or later for
hardware access.

Adapted from #478

Co-authored-by: Peter Hollender <peterhollender@gmail.com>
Signed-off-by: Ebrahim Ebrahim <ebrahim.ebrahim@kitware.com>
Match device transforms by hardware ID only when the ID is unique in both
recorded entries and module configurations. Otherwise use positional matching
so repeated IDs retain distinct placements during device-config round-trips.

Signed-off-by: Ebrahim Ebrahim <ebrahim.ebrahim@kitware.com>
Scale template placement and module standoff translations into each module's
units, and array standoff into the first module's units. Preserve device and
explicit overrides. Mark embedded fallback transforms as millimeters.

Signed-off-by: Ebrahim Ebrahim <ebrahim.ebrahim@kitware.com>
Match device modules one-to-one and infer stored transform units from their
possible module identities. Reject ambiguous mixed-unit associations and
cross-unit mesh inheritance. Normalize null array standoff overrides to
identity so reconstructed arrays can be flattened.

Signed-off-by: Ebrahim Ebrahim <ebrahim.ebrahim@kitware.com>
@ebrahimebrahim
ebrahimebrahim force-pushed the transducer-manager-library-components branch from 1ca1c07 to 7759655 Compare September 9, 2026 19:08
@ebrahimebrahim
ebrahimebrahim marked this pull request as ready for review September 9, 2026 20:02

@sadhana-r sadhana-r left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@ebrahimebrahim
ebrahimebrahim merged commit b678033 into main Sep 11, 2026
11 checks passed
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