Add transducer array hardware integration - #506
Merged
Merged
Conversation
1 task
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
force-pushed
the
transducer-manager-library-components
branch
from
September 9, 2026 19:08
1ca1c07 to
7759655
Compare
ebrahimebrahim
marked this pull request as ready for review
September 9, 2026 20:02
sadhana-r
approved these changes
Sep 11, 2026
sadhana-r
left a comment
Contributor
There was a problem hiding this comment.
Tested via OpenwaterHealth/SlicerOpenLIFU#662
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
Transducer.from_module_user_config()builds elements and calibration from the module configuration and preserves its hardware ID.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.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.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.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.This PR ports those items and then also adds the following fixes and refinements:
get_connected()closes interfaces it creates, including on failure, and leaves caller-owned interfaces open. SDK imports happen only when creating an interface.Nonearray-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, includingNone.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:
OpenLIFUFieldDataredesign,field_display.py, and display annotations/summaries across beamforming, segmentation, simulation, and virtual-fit models.Plan,PlanningSession, andSonicationSession, their database persistence, and split-session filename conventions.SolutionInfoapproval/timestamps/transforms, subject-scoped solution storage, and solution deletion/orphan cleanup.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.