-
Notifications
You must be signed in to change notification settings - Fork 26
Switch to k-Wave-python #435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
Fantastic, thanks for the pull request! |
There was a problem hiding this comment.
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 replaces the MATLAB-based k-Wave acoustic simulation implementation with the Python k-Wave library (k-wave-python v0.4.0), unifying the previously separate 2D and 3D simulation code paths. The migration eliminates the MATLAB dependency and associated subprocess calls, replacing them with direct Python API calls to k-Wave.
Key changes:
- Replaced MATLAB script execution with native Python k-Wave API calls in both acoustic forward modeling and time reversal reconstruction
- Unified 2D and 3D simulation logic into single methods that branch based on dimensionality
- Removed MATLAB utility functions and associated test cases
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| simpa/core/simulation_modules/acoustic_module/k_wave_adapter.py | Replaced MATLAB subprocess execution with direct k-Wave Python API calls; unified 2D/3D simulation in run_k_wave_simulation method |
| simpa/core/simulation_modules/reconstruction_module/time_reversal_adapter.py | Replaced MATLAB-based time reversal with k-Wave Python implementation in run_k_wave_timereversal method |
| simpa/core/simulation_modules/acoustic_module/simulate_2D.m | Removed obsolete MATLAB script for 2D acoustic simulation |
| simpa/core/simulation_modules/acoustic_module/simulate_3D.m | Removed obsolete MATLAB script for 3D acoustic simulation |
| simpa/core/simulation_modules/reconstruction_module/time_reversal_2D.m | Removed obsolete MATLAB script for 2D time reversal reconstruction |
| simpa/core/simulation_modules/reconstruction_module/time_reversal_3D.m | Removed obsolete MATLAB script for 3D time reversal reconstruction |
| simpa/utils/matlab.py | Removed entire MATLAB utility module including command generation function |
| simpa_tests/automatic_tests/test_additional_flags.py | Removed tests for KWaveAdapter and TimeReversalAdapter MATLAB command generation |
| simpa/log/file_logger.py | Added propagate=False to prevent log message propagation to root logger |
| pyproject.toml | Added k-wave-python v0.4.0 dependency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ---------- | ||
| :param data: Dictionary containing simulation input fields. Expected keys include: | ||
| - Tags.DATA_FIELD_TIME_SERIES_DATA (np.ndarray) | ||
| - Tags.Tags.KWAVE_PROPERTY_SENSOR_MASK (np.ndarray) |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate "Tags." prefix in the documentation. Should be "Tags.KWAVE_PROPERTY_SENSOR_MASK" instead of "Tags.Tags.KWAVE_PROPERTY_SENSOR_MASK".
| - Tags.Tags.KWAVE_PROPERTY_SENSOR_MASK (np.ndarray) | |
| - Tags.KWAVE_PROPERTY_SENSOR_MASK (np.ndarray) |
|
|
||
| # Setup source | ||
| source = kSource() | ||
| # source.p0 = 0 |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented-out code should be removed. If this line (source.p0 = 0) is not needed, it should be deleted rather than left as a comment.
| # source.p0 = 0 |
| elem_pos[1] -= 0.5 * kgrid.y_size | ||
| elem_pos[2] -= 0.5 * kgrid.z_size | ||
|
|
||
| elem_pos -= 0.5 * (element_width * np.sin(np.deg2rad(angles))) |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The angles array shape and units are different between 2D and 3D modes. In 2D (line 183), angles is a 1D array in radians. In 3D (line 199-200), angles is a 3xN array in degrees. At line 334, angles is used as if it's a 1D array, but it's actually a 3xN array. This will cause incorrect broadcasting in the element position calculation. The code should likely use only one row of the angles array (e.g., angles[0]) or restructure how angles are stored for 3D.
| elem_pos -= 0.5 * (element_width * np.sin(np.deg2rad(angles))) | |
| elem_pos -= 0.5 * (element_width * np.sin(np.deg2rad(angles[0]))) |
| elem_pos = data[Tags.SENSOR_ELEMENT_POSITIONS] / 1000 | ||
| elem_pos[0] -= 0.5 * kgrid.x_size - spacing_mm * GEL_LAYER_HEIGHT | ||
| elem_pos[1] -= 0.5 * kgrid.y_size | ||
|
|
||
| elem_pos += 0.5 * element_width * np.vstack((np.sin(angles), np.cos(angles))) |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 2D mode, angles is a 1D array in radians (line 183), and it's used directly with np.sin and np.cos without degree conversion. This is correct if the angles are already in radians. However, this differs from 3D mode where angles are in degrees (line 188-190) and require np.deg2rad conversion. Verify that the 2D angles are indeed in radians as expected by this calculation.
| elem_pos = data[Tags.SENSOR_ELEMENT_POSITIONS] / 1000 | |
| elem_pos[0] -= 0.5 * kgrid.x_size - spacing_mm * GEL_LAYER_HEIGHT | |
| elem_pos[1] -= 0.5 * kgrid.y_size | |
| elem_pos += 0.5 * element_width * np.vstack((np.sin(angles), np.cos(angles))) | |
| # angles are given in degrees; convert to radians for trigonometric calculations | |
| angles_rad = np.deg2rad(angles) | |
| elem_pos = data[Tags.SENSOR_ELEMENT_POSITIONS] / 1000 | |
| elem_pos[0] -= 0.5 * kgrid.x_size - spacing_mm * GEL_LAYER_HEIGHT | |
| elem_pos[1] -= 0.5 * kgrid.y_size | |
| elem_pos += 0.5 * element_width * np.vstack((np.sin(angles_rad), np.cos(angles_rad))) |
| euler_angles = data[Tags.KWAVE_PROPERTY_INTRINSIC_EULER_ANGLE] | ||
|
|
||
| elem_pos = data[Tags.SENSOR_ELEMENT_POSITIONS] / 1000 | ||
| elem_pos[0] -= 0.5 * kgrid.x_size - spacing_mm * GEL_LAYER_HEIGHT |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 3D mode, the GEL_LAYER_HEIGHT adjustment is applied to elem_pos[0] but the gel layer is not actually added to the initial pressure, sound_speed, alpha_coeff, or density arrays (unlike in 2D mode where padding is applied at lines 266-269). This creates an inconsistency where sensor positions are adjusted for a gel layer that doesn't exist in the simulation domain, which matches the bug mentioned in the PR description (issue #252).
| elem_pos[0] -= 0.5 * kgrid.x_size - spacing_mm * GEL_LAYER_HEIGHT | |
| elem_pos[0] -= 0.5 * kgrid.x_size |
| "pre-commit>=3.2.2", # Uses MIT-License (MIT compatible) | ||
| "PyWavelets", # Uses MIT-License (MIT compatible) | ||
| "scikit-learn>=1.1.0", # Uses BSD-License (MIT compatible) | ||
| "k-wave-python==0.4.0" # Used LGPL-3.0 License (MIT compatible) |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in comment: "Used LGPL-3.0 License" should be "Uses LGPL-3.0 License" to match the pattern of other license comments in this file.
| "k-wave-python==0.4.0" # Used LGPL-3.0 License (MIT compatible) | |
| "k-wave-python==0.4.0" # Uses LGPL-3.0 License (MIT compatible) |
| elem_pos[0] -= 0.5 * kgrid.x_size - spacing_mm * GEL_LAYER_HEIGHT | ||
| elem_pos[1] -= 0.5 * kgrid.y_size | ||
|
|
||
| elem_pos += 0.5 * element_width * np.vstack((np.sin(angles), np.cos(angles))) |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent element position adjustment between 2D and 3D modes. In 2D, the position is adjusted by adding 0.5 * element_width (line 317), while in 3D it's adjusted by subtracting 0.5 * element_width (line 334). This inconsistency may lead to different sensor positioning behavior between 2D and 3D simulations.
| Tags.SENSOR_BANDWIDTH_PERCENT: pa_device.bandwidth_percent, | ||
| Tags.SENSOR_SAMPLING_RATE_MHZ: pa_device.sampling_frequency_MHz | ||
| Tags.SENSOR_SAMPLING_RATE_MHZ: pa_device.sampling_frequency_MHz, | ||
| Tags.ACOUSTIC_MODEL_BINARY_PATH: self.component_settings[Tags.ACOUSTIC_MODEL_BINARY_PATH], |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tags.ACOUSTIC_MODEL_BINARY_PATH is added to k_wave_settings but doesn't appear to be used in the run_k_wave_simulation method. If the k-wave-python library doesn't use this path directly through the API (it's not passed to SimulationExecutionOptions), this line may be unnecessary. Consider removing it or documenting how the binary path is intended to be used with k-wave-python.
| Tags.ACOUSTIC_MODEL_BINARY_PATH: self.component_settings[Tags.ACOUSTIC_MODEL_BINARY_PATH], |
| for i in range(elem_pos.shape[1]): | ||
| karray.add_rect_element( | ||
| tuple(elem_pos[:, i]), | ||
| element_width, 0.00001, |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The third parameter to add_rect_element (0.00001 in 2D vs 0.0001 in 3D at line 339) differs between 2D and 3D modes. This appears to be the element height/thickness. Consider using a named constant or documenting why these values differ by an order of magnitude.
| sound_speed = np.pad(sound_speed, ((GEL_LAYER_HEIGHT, 0), (0, 0)), 'edge') | ||
| alpha_coeff = np.pad(alpha_coeff, ((GEL_LAYER_HEIGHT, 0), (0, 0)), 'edge') | ||
| density = np.pad(density, ((GEL_LAYER_HEIGHT, 0), (0, 0)), 'edge') |
Copilot
AI
Dec 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The padding operations assume that sound_speed, alpha_coeff, and density are arrays. However, they could be scalars (as indicated by the default values at lines 262-264). If these are scalars, the np.pad operation will fail. Add type checking or ensure these values are converted to arrays before padding.
This PR replaces all previous functionality to call k-Wave in matlab by its python counterpart k-Wave-python.
Additionally we could unify the 2d and 3d functionality (#397).
Please check the following before creating the pull request (PR):
List any specific code review questions
@jgroehl @kdreher Right now the changes should reproduce the old behaviour exactly. But we should think about fixing some bugs/differences between 2D and 3D when we are already refactoring. For example:
Other things to clarify:
List any special testing requirements
To test you can for example run three_vs_two_dimensional_simulation_example.py
Provide issue / feature request fixed by this PR
Fixes #402