k-Wave-python 0.6.0__py3-none-any.whl → 0.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {k_wave_python-0.6.0.dist-info → k_wave_python-0.6.2.dist-info}/METADATA +34 -42
- {k_wave_python-0.6.0.dist-info → k_wave_python-0.6.2.dist-info}/RECORD +24 -24
- kwave/__init__.py +60 -7
- kwave/compat.py +4 -0
- kwave/enums.py +12 -0
- kwave/executor.py +8 -1
- kwave/kWaveSimulation_helper/create_absorption_variables.py +4 -4
- kwave/kgrid.py +1 -1
- kwave/kmedium.py +57 -44
- kwave/kspaceFirstOrder.py +112 -8
- kwave/kspaceFirstOrder2D.py +5 -0
- kwave/kspaceFirstOrder3D.py +5 -0
- kwave/solvers/cpp_simulation.py +97 -14
- kwave/solvers/kspace_solver.py +294 -114
- kwave/utils/checks.py +62 -0
- kwave/utils/conversion.py +22 -10
- kwave/utils/filters.py +22 -8
- kwave/utils/kwave_array.py +49 -26
- kwave/utils/mapgen.py +38 -35
- kwave/utils/math.py +2 -4
- kwave/utils/plot.py +2 -2
- kwave/utils/typing.py +16 -2
- {k_wave_python-0.6.0.dist-info → k_wave_python-0.6.2.dist-info}/WHEEL +0 -0
- {k_wave_python-0.6.0.dist-info → k_wave_python-0.6.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: k-Wave-python
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Acoustics toolbox for time domain acoustic and ultrasound simulations in complex and tissue-realistic media.
|
|
5
5
|
Project-URL: Homepage, http://www.k-wave.org/
|
|
6
6
|
Project-URL: Documentation, https://waltersimson.com/k-wave-python/
|
|
7
7
|
Project-URL: Repository, https://github.com/waltsims/k-wave-python
|
|
8
8
|
Project-URL: Bug-tracker, https://github.com/waltsims/k-wave-python/issues
|
|
9
|
-
Author-email: Farid Yagubbayli <farid.yagubbayli@tum.de>, Walter Simson <walter.simson@
|
|
10
|
-
Maintainer-email: Walter Simson <walter.simson@
|
|
9
|
+
Author-email: Farid Yagubbayli <farid.yagubbayli@tum.de>, Walter Simson <walter.a.simson@gmail.com>
|
|
10
|
+
Maintainer-email: Walter Simson <walter.a.simson@gmail.com>, Farid Yagubbayli <farid.yagubbayli@tum.de>, David Sinden <david.sinden@mevis.fraunhofer.de>
|
|
11
11
|
License: GNU LESSER GENERAL PUBLIC LICENSE
|
|
12
12
|
Version 3, 29 June 2007
|
|
13
13
|
|
|
@@ -179,30 +179,32 @@ Classifier: Operating System :: OS Independent
|
|
|
179
179
|
Classifier: Programming Language :: Python :: 3
|
|
180
180
|
Requires-Python: >=3.10
|
|
181
181
|
Requires-Dist: beartype==0.22.9
|
|
182
|
-
Requires-Dist: deepdiff==
|
|
182
|
+
Requires-Dist: deepdiff==9.0.0
|
|
183
183
|
Requires-Dist: deprecated>=1.2.14
|
|
184
|
-
Requires-Dist: h5py==3.
|
|
185
|
-
Requires-Dist: jaxtyping==0.3.
|
|
186
|
-
Requires-Dist: matplotlib==3.10.
|
|
184
|
+
Requires-Dist: h5py==3.16.0
|
|
185
|
+
Requires-Dist: jaxtyping==0.3.7
|
|
186
|
+
Requires-Dist: matplotlib==3.10.9
|
|
187
187
|
Requires-Dist: numpy<2.3.0,>=1.22.2
|
|
188
188
|
Requires-Dist: opencv-python==4.13.0.92
|
|
189
189
|
Requires-Dist: scipy==1.15.3
|
|
190
|
+
Requires-Dist: tqdm>=4.60
|
|
190
191
|
Provides-Extra: dev
|
|
191
|
-
Requires-Dist: pre-commit==4.
|
|
192
|
+
Requires-Dist: pre-commit==4.6.0; extra == 'dev'
|
|
192
193
|
Provides-Extra: docs
|
|
193
|
-
Requires-Dist: furo==
|
|
194
|
+
Requires-Dist: furo==2025.12.19; extra == 'docs'
|
|
194
195
|
Requires-Dist: sphinx-copybutton==0.5.2; extra == 'docs'
|
|
195
196
|
Requires-Dist: sphinx-mdinclude==0.6.2; extra == 'docs'
|
|
196
|
-
Requires-Dist: sphinx-tabs==3.4.
|
|
197
|
-
Requires-Dist: sphinx-toolbox==
|
|
197
|
+
Requires-Dist: sphinx-tabs==3.4.5; extra == 'docs'
|
|
198
|
+
Requires-Dist: sphinx-toolbox==4.1.2; extra == 'docs'
|
|
199
|
+
Requires-Dist: sphinx<9; extra == 'docs'
|
|
198
200
|
Provides-Extra: example
|
|
199
|
-
Requires-Dist: gdown==
|
|
201
|
+
Requires-Dist: gdown==6.0.0; extra == 'example'
|
|
200
202
|
Provides-Extra: test
|
|
201
|
-
Requires-Dist: coverage==7.
|
|
203
|
+
Requires-Dist: coverage==7.14.0; extra == 'test'
|
|
202
204
|
Requires-Dist: phantominator; extra == 'test'
|
|
203
205
|
Requires-Dist: pytest; extra == 'test'
|
|
204
206
|
Requires-Dist: pytest-xdist; extra == 'test'
|
|
205
|
-
Requires-Dist: requests==2.
|
|
207
|
+
Requires-Dist: requests==2.34.0; extra == 'test'
|
|
206
208
|
Requires-Dist: testfixtures==8.3.0; extra == 'test'
|
|
207
209
|
Description-Content-Type: text/markdown
|
|
208
210
|
|
|
@@ -211,52 +213,42 @@ Description-Content-Type: text/markdown
|
|
|
211
213
|
[](https://discord.gg/your-invite-code)
|
|
212
214
|
[](https://k-wave-python.readthedocs.io/en/latest/?badge=latest)
|
|
213
215
|
[](https://codecov.io/gh/waltsims/k-wave-python)
|
|
214
|
-
[](https://mybinder.org/v2/gh/waltsims/k-wave-python/master)
|
|
215
216
|
|
|
216
|
-
|
|
217
|
-
interface to the pre-compiled v1.3 of k-Wave simulation binaries, which support NVIDIA sm 5.0 (Maxwell) to sm 9.0a (Hopper) GPUs.
|
|
218
|
-
|
|
219
|
-
**New in v0.6.0:** Unified `kspaceFirstOrder()` API with a pure NumPy/CuPy solver. See the [API guide](https://k-wave-python.readthedocs.io/en/latest/get_started/new_api.html).
|
|
217
|
+
A Python implementation of [k-Wave](http://www.k-wave.org/) — an acoustics toolbox for time-domain simulation of acoustic wave fields. Includes a pure NumPy/CuPy solver (`backend="python"`) and an interface to the pre-compiled k-Wave C++ binaries (`backend="cpp"`) with NVIDIA GPU support (sm 5.0–9.0a).
|
|
220
218
|
|
|
221
219
|
## Mission
|
|
222
220
|
|
|
223
|
-
|
|
224
|
-
for medical imaging, algorithmic prototyping, and testing. Many tools and methods of [k-Wave](http://www.k-wave.org/) can
|
|
225
|
-
be found here, but this project has and will continue to diverge from the original [k-Wave](http://www.k-wave.org/) APIs
|
|
226
|
-
to leverage pythonic practices.
|
|
221
|
+
Increase the accessibility and reproducibility of [k-Wave](http://www.k-wave.org/) simulations for medical imaging, algorithmic prototyping, and testing.
|
|
227
222
|
|
|
228
223
|
## Getting started
|
|
229
224
|
|
|
230
225
|

|
|
231
226
|
|
|
232
|
-
A
|
|
227
|
+
A [collection of examples](../examples/) covers common simulation scenarios. Run any example locally:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
uv run examples/ivp_homogeneous_medium.py
|
|
231
|
+
```
|
|
233
232
|
|
|
234
|
-
|
|
235
|
-
1. Generating a simulation medium
|
|
236
|
-
2. Configuring a transducer
|
|
237
|
-
3. Running the simulation
|
|
238
|
-
4. Reconstructing the simulation
|
|
233
|
+
No GPU required — all examples run on CPU with NumPy.
|
|
239
234
|
|
|
240
235
|
## Installation
|
|
241
236
|
|
|
242
|
-
|
|
237
|
+
Using [uv](https://docs.astral.sh/uv/) (recommended):
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
uv add k-wave-python
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Or with pip:
|
|
243
244
|
|
|
244
245
|
```bash
|
|
245
246
|
pip install k-wave-python
|
|
246
247
|
```
|
|
247
|
-
> **Note for MacOS:**
|
|
248
|
-
>
|
|
249
|
-
> k-wave-python offers initial support for MacOS, but you will need to install the following dependencies using the [brew package manager](https://docs.brew.sh/Installation) along with the Python package:
|
|
250
|
-
> ```bash
|
|
251
|
-
> brew install fftw hdf5 zlib libomp
|
|
252
|
-
> ```
|
|
253
|
-
After installing the Python package, the required binaries will be downloaded and installed the first time you run a
|
|
254
|
-
simulation.
|
|
255
248
|
|
|
256
249
|
## Development
|
|
257
250
|
|
|
258
|
-
|
|
259
|
-
found [here](https://k-wave-python.readthedocs.io/en/latest/development/development_environment.html).
|
|
251
|
+
Development instructions can be found [here](https://k-wave-python.readthedocs.io/en/latest/development/development_environment.html).
|
|
260
252
|
|
|
261
253
|
## Related Projects
|
|
262
254
|
|
|
@@ -273,7 +265,7 @@ The documentation for k-wave-python can be found [here](https://k-wave-python.re
|
|
|
273
265
|
## Citation
|
|
274
266
|
```bibtex
|
|
275
267
|
@software{k-Wave-Python,
|
|
276
|
-
author = {
|
|
268
|
+
author = {Yagubbayli, Farid and Sinden, David and Simson, Walter},
|
|
277
269
|
license = {GPL-3.0},
|
|
278
270
|
title = {{k-Wave-Python}},
|
|
279
271
|
url = {https://github.com/waltsims/k-wave-python}
|
|
@@ -281,4 +273,4 @@ url = {https://github.com/waltsims/k-wave-python}
|
|
|
281
273
|
```
|
|
282
274
|
## Contact
|
|
283
275
|
|
|
284
|
-
e-mail [
|
|
276
|
+
e-mail [walter.a.simson@gmail.com](mailto:walter.a.simson@gmail.com).
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
kwave/__init__.py,sha256=
|
|
2
|
-
kwave/compat.py,sha256=
|
|
1
|
+
kwave/__init__.py,sha256=zoHNedvJIeMioBhOiLFVCGvfhFYX3L2YYHCL3jjllxk,9674
|
|
2
|
+
kwave/compat.py,sha256=2qr4XhKTNbB-RZjg6pYpUnnkObUyR78maMNsJKuzL3A,3158
|
|
3
3
|
kwave/data.py,sha256=WA0bMA7ScwW3HMyt15GkQOfWctcguFN8NHwALiXwO2I,2623
|
|
4
|
-
kwave/enums.py,sha256=
|
|
5
|
-
kwave/executor.py,sha256=
|
|
4
|
+
kwave/enums.py,sha256=lF8RV6gn5IAFuT38CMUc5dakHSwAOaqHtmuvyYkXdk4,738
|
|
5
|
+
kwave/executor.py,sha256=LqCJ8speh6R26p0ezaWEktwWEhA9Q75sJydDwRT-oqU,6367
|
|
6
6
|
kwave/kWaveSimulation.py,sha256=0M61nrIpf_k1BLq9-3bPkj0xN4jQ3MPPjX1_-LSsnfc,68030
|
|
7
|
-
kwave/kgrid.py,sha256=
|
|
8
|
-
kwave/kmedium.py,sha256=
|
|
7
|
+
kwave/kgrid.py,sha256=RPCfqpwHcV7jZ2qgrG_nOMu42jnNERIWM9axq5FH8qY,23226
|
|
8
|
+
kwave/kmedium.py,sha256=ZLnUGbkn23EJotJh2oggam4Q4K-fFMzL2yHpM6JbTeA,9953
|
|
9
9
|
kwave/ksensor.py,sha256=_7KAQK8uCJmFwqxhg-Lio8ojGrU-9on634JnZ11wXmY,4269
|
|
10
10
|
kwave/ksource.py,sha256=twIQ774Zo7mP8yYqhSVl2PW6Y-cp3TSvYtAt6QN2_iw,19596
|
|
11
|
-
kwave/kspaceFirstOrder.py,sha256=
|
|
12
|
-
kwave/kspaceFirstOrder2D.py,sha256=
|
|
13
|
-
kwave/kspaceFirstOrder3D.py,sha256=
|
|
11
|
+
kwave/kspaceFirstOrder.py,sha256=aFu-JKeuTuVAYvz3deLzo63-XF6gJrytmR7dXN3om5U,15754
|
|
12
|
+
kwave/kspaceFirstOrder2D.py,sha256=w401i3bq_K8oRXopGWIF5CKISyqWRar6B4m9BiMeFH4,16741
|
|
13
|
+
kwave/kspaceFirstOrder3D.py,sha256=D-nWqLTX_c9ky7OqACkOKJrYfKJ09jFundSMDITUKyk,17653
|
|
14
14
|
kwave/kspaceFirstOrderAS.py,sha256=NR-RaUD96cfoy3rw3eELkDZfeF4s3vakcM5TP4pIroc,17515
|
|
15
15
|
kwave/kspaceLineRecon.py,sha256=hSw7ZTxDRPzsS_gk4zeBwk1wefIybEsRbuK0JDY0JxA,5727
|
|
16
16
|
kwave/kspacePlaneRecon.py,sha256=-0anuLC5IUCFx3drvEKdpzUMFb0lCFBrPeni2QyS8r0,5879
|
|
17
17
|
kwave/ktransducer.py,sha256=ypcNWmrQxGa0MXjOuyktvZgzs-7wFobS1fGobNi2GfU,31413
|
|
18
18
|
kwave/recorder.py,sha256=SaDaL1dsnYpsDZ7wfqCWrX0xz8_mtavtpvL8WpbKLAE,5307
|
|
19
19
|
kwave/kWaveSimulation_helper/__init__.py,sha256=pvq2XTaC-yTWSl96AA2h3Iam-wbs5EAaYGDd4ckyTqs,619
|
|
20
|
-
kwave/kWaveSimulation_helper/create_absorption_variables.py,sha256=
|
|
20
|
+
kwave/kWaveSimulation_helper/create_absorption_variables.py,sha256=W_jsWquHRCLM5TVF95LG3WkWxwrc2228ki7jF5s92f8,4915
|
|
21
21
|
kwave/kWaveSimulation_helper/display_simulation_params.py,sha256=ROqXjd4MDlJRZZ_ZrNZ3zL7_Ido_gA9dowTwMs1X_lw,3310
|
|
22
22
|
kwave/kWaveSimulation_helper/expand_grid_matrices.py,sha256=qTkKQDQhifbE59iejxKlWx6BvZad83SuU7w3X0HA_Rs,11237
|
|
23
23
|
kwave/kWaveSimulation_helper/retract_transducer_grid_size.py,sha256=jLLAtsRMDi8lVF6SOfkNj7MxBtfhXQZ1AeObSnIkN84,945
|
|
@@ -32,34 +32,34 @@ kwave/reconstruction/beamform.py,sha256=JjdYqV-ge2HfQpnR6QwoyQkF3WdbuowTXblchOuj
|
|
|
32
32
|
kwave/reconstruction/time_reversal.py,sha256=77O9ZS-ZpRCV4Z0ZIm41qzwGGsbnsP4x688qRhEpIz0,6258
|
|
33
33
|
kwave/reconstruction/tools.py,sha256=-F9k7Sbc2qvun1cygc4sHVZdFKP4zaD2p_B4aN_efWQ,2397
|
|
34
34
|
kwave/solvers/__init__.py,sha256=-cfkyK8i0lhgyJx5p-JyK9_5XLVdIdBnjsdQ_Y0AHGw,176
|
|
35
|
-
kwave/solvers/cpp_simulation.py,sha256=
|
|
36
|
-
kwave/solvers/kspace_solver.py,sha256=
|
|
35
|
+
kwave/solvers/cpp_simulation.py,sha256=XAZhFz71BBPRmmAbdOJzQ4QsbwUidkAtdW3ee6TfSeM,16768
|
|
36
|
+
kwave/solvers/kspace_solver.py,sha256=6AjIpkpw08197QNjnE3Io_481nc9GwHLQGZYbjSajkU,41488
|
|
37
37
|
kwave/solvers/native.py,sha256=lQoCOPss1kedTpT6BIN5A22XtKdVlSNRuowB_61QqaA,1152
|
|
38
38
|
kwave/solvers/validation.py,sha256=7Zn_EqmDqceD4rTGccHwsjFJIImPXfcDqTwj-nPa7vI,5062
|
|
39
39
|
kwave/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
40
|
kwave/utils/angular_spectrum.py,sha256=ciEYcM_Vv-CdKdCzwjMxL5NVpCvmjEzYh7LYoelw0p4,15220
|
|
41
41
|
kwave/utils/angular_spectrum_cw.py,sha256=XgENMdku_5gEY2SLXOqyLs6NarkGIPZZW6GbvjJ0mjQ,11319
|
|
42
42
|
kwave/utils/atten_comp.py,sha256=GGH0UBJQu_QG8K_NpyR2Ouwb0CORWZ3LqRTOF7DWrLA,11446
|
|
43
|
-
kwave/utils/checks.py,sha256=
|
|
43
|
+
kwave/utils/checks.py,sha256=zKFnM4XJU2McJgMIIK5GvfXxDIPzryrkx86AlVb-2BY,12788
|
|
44
44
|
kwave/utils/colormap.py,sha256=avgY4Gj2ozswvy_cW3w6rCGbpMiz2vkSgaCINSvz0zE,2440
|
|
45
|
-
kwave/utils/conversion.py,sha256=
|
|
45
|
+
kwave/utils/conversion.py,sha256=E3qQbEdac0j1VSUGqPiRcxwds6NNcBmSZ3DHv9b8ZcU,17683
|
|
46
46
|
kwave/utils/data.py,sha256=rMZCDT1USQxtiPssvABw4fTxArxgypwh7uLWSrqHIOs,5758
|
|
47
47
|
kwave/utils/dotdictionary.py,sha256=d46CVT_xP_a0C0foNyzVfhkdZccYcIBJCsFdBspML2Y,1613
|
|
48
|
-
kwave/utils/filters.py,sha256=
|
|
48
|
+
kwave/utils/filters.py,sha256=eXcvOQyY2Rgn4Azszg0SRuk9pENyB6EshGs7JuIsYDA,22836
|
|
49
49
|
kwave/utils/interp.py,sha256=3n4TtNiL2YVuDk0Mixe_CPpQIsA3tQ2cjPuJOcfh5F8,15326
|
|
50
50
|
kwave/utils/io.py,sha256=FAPU-2wgxkkFNEDS5ONC2s_iD6w2lEOf3LwTGlMa8-w,16631
|
|
51
|
-
kwave/utils/kwave_array.py,sha256=
|
|
52
|
-
kwave/utils/mapgen.py,sha256=
|
|
53
|
-
kwave/utils/math.py,sha256=
|
|
51
|
+
kwave/utils/kwave_array.py,sha256=uxQGWvsESuhI5Yl62qus4hbEGtcLE0Xp6v5ZjZnnLj0,39505
|
|
52
|
+
kwave/utils/mapgen.py,sha256=Gzwzr_oT1RPegFLtlrPct3kX1xfxd8ffpebJBmjK3jE,115847
|
|
53
|
+
kwave/utils/math.py,sha256=zzbOEbl-M1RQxVY7xwJYfDn3VPgDda7L6RaeUZ6evwk,13810
|
|
54
54
|
kwave/utils/matlab.py,sha256=FUN8aVQ1eewC4tgMjcbynxj-kp1jyWxXpXEiNS-iXTM,5655
|
|
55
55
|
kwave/utils/matrix.py,sha256=RFQYJ7As7sLRj6iW1hoci0xxDvBoKibtYQ5J4nrcSkQ,14120
|
|
56
|
-
kwave/utils/plot.py,sha256=
|
|
56
|
+
kwave/utils/plot.py,sha256=xDPa41z7JJKMN9jDpLJqiYEsHRcp7MmcLk3v3CqRP5I,1496
|
|
57
57
|
kwave/utils/pml.py,sha256=ijlwgxwXbBgl1V7x5B-r9stqIXOj-DEnJBsO5y-Wl5M,6282
|
|
58
58
|
kwave/utils/sharpness_filters.py,sha256=mG0oiOVuKJB6Dn_d-44Vrf-48JmYxr6XN4uBmIC9Ji8,3799
|
|
59
59
|
kwave/utils/signals.py,sha256=R4FNEc6RWKzkJoiPaALdiEcxZqwkAJx7Dd1UDv_HgBI,28767
|
|
60
60
|
kwave/utils/tictoc.py,sha256=lHAsEspcfw2AzYBgiOQgeUOEjuKMJHf8D7wcbl4wev0,1426
|
|
61
|
-
kwave/utils/typing.py,sha256=
|
|
62
|
-
k_wave_python-0.6.
|
|
63
|
-
k_wave_python-0.6.
|
|
64
|
-
k_wave_python-0.6.
|
|
65
|
-
k_wave_python-0.6.
|
|
61
|
+
kwave/utils/typing.py,sha256=s2_yfomloEkTxQy6cvvSuSI1o7e5DvdUa3nGpnyZQUs,1562
|
|
62
|
+
k_wave_python-0.6.2.dist-info/METADATA,sha256=6WZri4_-b-BBgnWsal5A2FF2oMSxdcYoTQLdH1dQFEI,13435
|
|
63
|
+
k_wave_python-0.6.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
64
|
+
k_wave_python-0.6.2.dist-info/licenses/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
|
|
65
|
+
k_wave_python-0.6.2.dist-info/RECORD,,
|
kwave/__init__.py
CHANGED
|
@@ -3,23 +3,45 @@ import json
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import platform
|
|
6
|
+
import stat
|
|
7
|
+
import warnings
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import List
|
|
8
10
|
from urllib.request import urlretrieve
|
|
9
11
|
|
|
10
12
|
# Test installation with:
|
|
11
13
|
# python3 -m pip install -i https://test.pypi.org/simple/ --extra-index-url=https://pypi.org/simple/ k-Wave-python==0.3.0
|
|
12
|
-
__version__ = "0.6.
|
|
14
|
+
__version__ = "0.6.2"
|
|
13
15
|
|
|
14
16
|
# Constants and Configurations
|
|
15
17
|
URL_BASE = "https://github.com/waltsims/"
|
|
16
|
-
BINARY_VERSION = "v1.
|
|
17
|
-
|
|
18
|
+
BINARY_VERSION = "v1.4.1"
|
|
19
|
+
# Pin both Windows binaries to v1.3.0. v1.4.x windows builds switched compiler /
|
|
20
|
+
# OpenMP / FFT / CUDA runtime stacks and neither v1.4.x release ships its runtime
|
|
21
|
+
# DLLs (cufft, cudart, vcomp, vcruntime140_1, fftw3f, etc.). v1.3.0 binaries are
|
|
22
|
+
# self-contained with their Intel-era DLL bundle (listed in WINDOWS_DLLS below,
|
|
23
|
+
# downloaded with the OMP request and used by both .exe files since they share
|
|
24
|
+
# kwave/bin/windows/). OMP DLL bundling is fixed in kspacefirstorder-unified#14
|
|
25
|
+
# (awaiting validation); CUDA DLL bundling is tracked in kspacefirstorder-unified#17.
|
|
26
|
+
WINDOWS_OMP_VERSION = "v1.3.0"
|
|
27
|
+
WINDOWS_CUDA_VERSION = "v1.3.0"
|
|
18
28
|
PLATFORM = platform.system().lower()
|
|
19
29
|
|
|
20
30
|
if PLATFORM not in ["linux", "windows", "darwin"]:
|
|
21
31
|
raise NotImplementedError(f"k-wave-python is currently unsupported on this operating system: {PLATFORM}.")
|
|
22
32
|
|
|
33
|
+
# darwin C++ binary is arm64-only; universal2 coverage tracked for v0.6.5
|
|
34
|
+
DARWIN_BINARY_ARCH = "arm64"
|
|
35
|
+
_darwin_unsupported = PLATFORM == "darwin" and platform.machine() != DARWIN_BINARY_ARCH
|
|
36
|
+
if _darwin_unsupported:
|
|
37
|
+
warnings.warn(
|
|
38
|
+
f"k-wave-python's macOS C++ binary is {DARWIN_BINARY_ARCH}-only. "
|
|
39
|
+
f"Detected {platform.machine()} — the C++ backend (backend='cpp') will not run on this machine. "
|
|
40
|
+
"Use backend='python' instead. Universal2 (Intel + Apple Silicon) coverage is tracked for v0.6.5.",
|
|
41
|
+
RuntimeWarning,
|
|
42
|
+
stacklevel=2,
|
|
43
|
+
)
|
|
44
|
+
|
|
23
45
|
# TODO: install directly in to /bin/ directory system directory is no longer needed
|
|
24
46
|
# TODO: deprecate in 0.5.0
|
|
25
47
|
BINARY_PATH = Path(__file__).parent / "bin" / PLATFORM
|
|
@@ -44,21 +66,24 @@ ARCHITECTURES = ["omp", "cuda"]
|
|
|
44
66
|
|
|
45
67
|
|
|
46
68
|
def get_windows_release_urls(architecture: str) -> list:
|
|
69
|
+
version = WINDOWS_OMP_VERSION if architecture == "omp" else WINDOWS_CUDA_VERSION
|
|
47
70
|
specific_filenames = [EXECUTABLE_PREFIX + architecture + ".exe"]
|
|
48
71
|
if architecture == "omp":
|
|
49
72
|
specific_filenames += WINDOWS_DLLS
|
|
50
|
-
|
|
51
|
-
return
|
|
73
|
+
base = f"{URL_BASE}kspaceFirstOrder-{architecture.upper()}-{PLATFORM.lower()}/releases/download/{version}/"
|
|
74
|
+
return [base + filename for filename in specific_filenames]
|
|
52
75
|
|
|
53
76
|
|
|
54
77
|
URL_DICT = {
|
|
55
78
|
"linux": {
|
|
56
|
-
"cuda": [URL_BASE + f"kspaceFirstOrder-CUDA-{PLATFORM}/releases/download/
|
|
79
|
+
"cuda": [URL_BASE + f"kspaceFirstOrder-CUDA-{PLATFORM}/releases/download/{BINARY_VERSION}/{EXECUTABLE_PREFIX}CUDA"],
|
|
57
80
|
"omp": [URL_BASE + f"kspaceFirstOrder-OMP-{PLATFORM}/releases/download/{BINARY_VERSION}/{EXECUTABLE_PREFIX}OMP"],
|
|
58
81
|
},
|
|
59
82
|
"darwin": {
|
|
60
83
|
"cuda": [],
|
|
61
|
-
"omp":
|
|
84
|
+
"omp": (
|
|
85
|
+
[] if _darwin_unsupported else [URL_BASE + f"k-wave-omp-{PLATFORM}/releases/download/{BINARY_VERSION}/{EXECUTABLE_PREFIX}OMP"]
|
|
86
|
+
),
|
|
62
87
|
},
|
|
63
88
|
"windows": {architecture: get_windows_release_urls(architecture) for architecture in ARCHITECTURES},
|
|
64
89
|
}
|
|
@@ -77,6 +102,31 @@ def _hash_file(filepath: str) -> str:
|
|
|
77
102
|
return md5.hexdigest()
|
|
78
103
|
|
|
79
104
|
|
|
105
|
+
def _ensure_executable(binary_filepath) -> None:
|
|
106
|
+
# Self-heal the executable bit on Linux/macOS. urlretrieve creates files
|
|
107
|
+
# at 0644, and prior versions of this package didn't fix that up, so users
|
|
108
|
+
# upgrading with a cached non-executable binary on disk would otherwise
|
|
109
|
+
# stay stuck (the cache check below returns True and skips re-download).
|
|
110
|
+
# Any OS-level failure here (broken symlink, read-only FS, wrong ownership,
|
|
111
|
+
# TOCTOU race) is degraded to a warning so it never aborts `import kwave`.
|
|
112
|
+
if PLATFORM == "windows":
|
|
113
|
+
return
|
|
114
|
+
try:
|
|
115
|
+
current_mode = os.stat(binary_filepath).st_mode
|
|
116
|
+
desired_mode = current_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
|
|
117
|
+
if current_mode == desired_mode:
|
|
118
|
+
return
|
|
119
|
+
os.chmod(binary_filepath, desired_mode)
|
|
120
|
+
except OSError: # pragma: no cover - defensive; degrades to warning, never fatal
|
|
121
|
+
# Don't abort import. The user can chmod +x manually or reinstall
|
|
122
|
+
# into a writable location.
|
|
123
|
+
logging.warning(
|
|
124
|
+
"kwave: cannot set executable bit on %s — backend='cpp' may fail with "
|
|
125
|
+
"Permission denied. Run `chmod +x` manually or reinstall.",
|
|
126
|
+
binary_filepath,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
80
130
|
def _is_binary_present(binary_name: str, binary_type: str) -> bool:
|
|
81
131
|
binary_filepath = BINARY_PATH / binary_name
|
|
82
132
|
binary_file_exists = os.path.exists(binary_filepath)
|
|
@@ -106,6 +156,8 @@ def _is_binary_present(binary_name: str, binary_type: str) -> bool:
|
|
|
106
156
|
if existing_metadata["url"] not in latest_urls:
|
|
107
157
|
return False
|
|
108
158
|
|
|
159
|
+
_ensure_executable(binary_filepath)
|
|
160
|
+
|
|
109
161
|
# No need to check `version` field for now
|
|
110
162
|
# because we version is already present in the URL
|
|
111
163
|
return True
|
|
@@ -176,6 +228,7 @@ def download_binaries(system_os: str, bin_type: str):
|
|
|
176
228
|
try:
|
|
177
229
|
binary_filepath = os.path.join(BINARY_PATH, filename)
|
|
178
230
|
urlretrieve(url, binary_filepath)
|
|
231
|
+
_ensure_executable(binary_filepath)
|
|
179
232
|
_record_binary_metadata(binary_version=binary_version, binary_filepath=binary_filepath, binary_url=url, filename=filename)
|
|
180
233
|
|
|
181
234
|
except TimeoutError:
|
kwave/compat.py
CHANGED
|
@@ -70,5 +70,9 @@ def options_to_kwargs(simulation_options=None, execution_options=None):
|
|
|
70
70
|
kwargs["num_threads"] = opts.num_threads
|
|
71
71
|
if opts.device_num is not None:
|
|
72
72
|
kwargs["device_num"] = opts.device_num
|
|
73
|
+
# Read _binary_path directly: the property auto-resolves to a default,
|
|
74
|
+
# so it can't distinguish a user-set path from one.
|
|
75
|
+
if opts._binary_path is not None:
|
|
76
|
+
kwargs["binary_path"] = opts._binary_path
|
|
73
77
|
|
|
74
78
|
return kwargs
|
kwave/enums.py
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
class AlphaMode(str, Enum):
|
|
5
|
+
"""Controls which absorption/dispersion terms are included in the equation of state."""
|
|
6
|
+
|
|
7
|
+
NO_ABSORPTION = "no_absorption"
|
|
8
|
+
NO_DISPERSION = "no_dispersion"
|
|
9
|
+
STOKES = "stokes"
|
|
10
|
+
|
|
11
|
+
def __str__(self):
|
|
12
|
+
return self.value
|
|
13
|
+
|
|
14
|
+
|
|
3
15
|
################################################################
|
|
4
16
|
# literals that link the discrete cosine and sine transform types with
|
|
5
17
|
# their type definitions in the functions dtt1D, dtt2D, and dtt3D
|
kwave/executor.py
CHANGED
|
@@ -52,9 +52,16 @@ class Executor:
|
|
|
52
52
|
raise subprocess.CalledProcessError(proc.returncode, command, stdout, stderr)
|
|
53
53
|
|
|
54
54
|
except subprocess.CalledProcessError as e:
|
|
55
|
-
# This ensures stdout is printed regardless of show_sim_logs value if an error occurs
|
|
56
55
|
print(e.stdout)
|
|
57
56
|
print(e.stderr, file=sys.stderr)
|
|
57
|
+
if sys.platform == "darwin" and e.stderr and any(s in e.stderr for s in ("Library not loaded", "image not found", "dyld")):
|
|
58
|
+
print(
|
|
59
|
+
"\nMissing macOS libraries for the C++ backend.\n"
|
|
60
|
+
"Install them with:\n\n"
|
|
61
|
+
" brew install fftw hdf5 zlib libomp\n\n"
|
|
62
|
+
"Alternatively, use backend='python' which requires no extra dependencies.",
|
|
63
|
+
file=sys.stderr,
|
|
64
|
+
)
|
|
58
65
|
raise
|
|
59
66
|
|
|
60
67
|
sensor_data = self.parse_executable_output(output_filename)
|
|
@@ -12,19 +12,19 @@ def create_absorption_variables(kgrid: kWaveGrid, medium: kWaveMedium, equation_
|
|
|
12
12
|
# define the lossy derivative operators and proportionality coefficients
|
|
13
13
|
"""
|
|
14
14
|
Selects and returns absorption and dispersion operators and coefficients for the given medium based on the equation of state.
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
Parameters:
|
|
17
17
|
kgrid (kWaveGrid): Grid object providing wavenumber array via `kgrid.k`.
|
|
18
18
|
medium (kWaveMedium): Medium properties used to compute absorption/dispersion coefficients.
|
|
19
19
|
equation_of_state (str): One of `"absorbing"`, `"stokes"`, or `"lossless"` determining which variables to produce.
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
Returns:
|
|
22
22
|
tuple: (nabla1, nabla2, tau, eta)
|
|
23
23
|
- nabla1: First-order absorption operator or `None` when not applicable.
|
|
24
24
|
- nabla2: Dispersion operator or `None` when not applicable.
|
|
25
25
|
- tau: Absorbing coefficient or `None` when not applicable.
|
|
26
26
|
- eta: Dispersive coefficient or `None` when not applicable.
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
Behavior:
|
|
29
29
|
- "absorbing": returns (nabla1, nabla2, tau, eta) computed for an absorbing medium.
|
|
30
30
|
- "stokes": returns (None, None, tau, None) where `tau` is the Stokes absorbing coefficient.
|
|
@@ -127,4 +127,4 @@ def apply_alpha_filter(medium, nabla1, nabla2):
|
|
|
127
127
|
# shift the parameters back
|
|
128
128
|
nabla1 = np.fft.ifftshift(nabla1)
|
|
129
129
|
nabla2 = np.fft.ifftshift(nabla2)
|
|
130
|
-
return nabla1, nabla2
|
|
130
|
+
return nabla1, nabla2
|
kwave/kgrid.py
CHANGED
|
@@ -108,7 +108,7 @@ class kWaveGrid(object):
|
|
|
108
108
|
@t_array.setter
|
|
109
109
|
def t_array(self, t_array):
|
|
110
110
|
# check for 'auto' input
|
|
111
|
-
if t_array == "auto":
|
|
111
|
+
if isinstance(t_array, str) and t_array == "auto":
|
|
112
112
|
# set values to auto
|
|
113
113
|
self.Nt = "auto"
|
|
114
114
|
self.dt = "auto"
|
kwave/kmedium.py
CHANGED
|
@@ -1,50 +1,65 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import List
|
|
3
|
+
from typing import List, Optional, Sequence, Union
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from kwave.enums import AlphaMode
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _to_alpha_mode(value):
|
|
11
|
+
"""Normalize a value to AlphaMode. Accepts None, AlphaMode, or a valid string."""
|
|
12
|
+
if value is None or isinstance(value, AlphaMode):
|
|
13
|
+
return value
|
|
14
|
+
try:
|
|
15
|
+
return AlphaMode(value)
|
|
16
|
+
except (ValueError, TypeError):
|
|
17
|
+
raise ValueError(
|
|
18
|
+
f"medium.alpha_mode must be an AlphaMode enum value or one of 'no_absorption', 'no_dispersion', 'stokes', got {value!r}"
|
|
19
|
+
) from None
|
|
8
20
|
|
|
9
21
|
|
|
10
22
|
@dataclass
|
|
11
23
|
class kWaveMedium(object):
|
|
24
|
+
"""
|
|
25
|
+
Medium properties for k-Wave simulations.
|
|
26
|
+
|
|
27
|
+
Note: For heterogeneous medium parameters, medium.sound_speed and medium.density
|
|
28
|
+
must be given in matrix form with the same dimensions as kgrid. For homogeneous
|
|
29
|
+
medium parameters, these can be given as single numeric values. If the medium is
|
|
30
|
+
homogeneous and velocity inputs or outputs are not required, it is not necessary
|
|
31
|
+
to specify medium.density.
|
|
32
|
+
"""
|
|
33
|
+
|
|
12
34
|
# sound speed distribution within the acoustic medium [m/s] | required to be defined
|
|
13
|
-
sound_speed: np.
|
|
35
|
+
sound_speed: Union[float, int, np.ndarray]
|
|
14
36
|
# reference sound speed used within the k-space operator (phase correction term) [m/s]
|
|
15
|
-
sound_speed_ref: np.
|
|
37
|
+
sound_speed_ref: Optional[Union[float, int, np.ndarray]] = None
|
|
16
38
|
# density distribution within the acoustic medium [kg/m^3]
|
|
17
|
-
density: np.
|
|
39
|
+
density: Optional[Union[float, int, np.ndarray]] = None
|
|
18
40
|
# power law absorption coefficient [dB/(MHz^y cm)]
|
|
19
|
-
alpha_coeff: np.
|
|
41
|
+
alpha_coeff: Optional[Union[float, int, np.ndarray]] = None
|
|
20
42
|
# power law absorption exponent
|
|
21
|
-
alpha_power: np.
|
|
43
|
+
alpha_power: Optional[Union[float, int, np.ndarray]] = None
|
|
22
44
|
# optional input to force either the absorption or dispersion terms in the equation of state to be excluded;
|
|
23
|
-
# valid inputs are
|
|
24
|
-
alpha_mode:
|
|
45
|
+
# valid inputs are AlphaMode.NO_ABSORPTION, AlphaMode.NO_DISPERSION, or the equivalent strings
|
|
46
|
+
alpha_mode: Optional[Union[AlphaMode, str]] = None
|
|
25
47
|
# frequency domain filter applied to the absorption and dispersion terms in the equation of state
|
|
26
|
-
alpha_filter: np.
|
|
48
|
+
alpha_filter: Optional[np.ndarray] = None
|
|
27
49
|
# two element array used to control the sign of absorption and dispersion terms in the equation of state
|
|
28
|
-
alpha_sign: np.
|
|
50
|
+
alpha_sign: Optional[np.ndarray] = None
|
|
29
51
|
# parameter of nonlinearity
|
|
30
|
-
BonA: np.
|
|
52
|
+
BonA: Optional[Union[float, int, np.ndarray]] = None
|
|
31
53
|
# is the medium absorbing?
|
|
32
54
|
absorbing: bool = False
|
|
33
55
|
# is the medium absorbing stokes?
|
|
34
56
|
stokes: bool = False
|
|
35
57
|
|
|
36
|
-
# """
|
|
37
|
-
# Note: For heterogeneous medium parameters, medium.sound_speed and
|
|
38
|
-
# medium.density must be given in matrix form with the same dimensions as
|
|
39
|
-
# kgrid. For homogeneous medium parameters, these can be given as single
|
|
40
|
-
# numeric values. If the medium is homogeneous and velocity inputs or
|
|
41
|
-
# outputs are not required, it is not necessary to specify medium.density.
|
|
42
|
-
# """
|
|
43
|
-
|
|
44
58
|
def __post_init__(self):
|
|
45
59
|
self.sound_speed = np.atleast_1d(self.sound_speed)
|
|
60
|
+
self.alpha_mode = _to_alpha_mode(self.alpha_mode)
|
|
46
61
|
|
|
47
|
-
def check_fields(self, kgrid_shape:
|
|
62
|
+
def check_fields(self, kgrid_shape: Sequence[int]) -> None:
|
|
48
63
|
"""
|
|
49
64
|
Check whether the given properties are valid
|
|
50
65
|
|
|
@@ -54,26 +69,21 @@ class kWaveMedium(object):
|
|
|
54
69
|
Returns:
|
|
55
70
|
None
|
|
56
71
|
"""
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
assert self.alpha_mode in [
|
|
60
|
-
"no_absorption",
|
|
61
|
-
"no_dispersion",
|
|
62
|
-
"stokes",
|
|
63
|
-
], "medium.alpha_mode must be set to 'no_absorption', 'no_dispersion', or 'stokes'."
|
|
72
|
+
# re-normalize alpha_mode in case it was reassigned as a plain string post-construction
|
|
73
|
+
self.alpha_mode = _to_alpha_mode(self.alpha_mode)
|
|
64
74
|
|
|
65
75
|
# check the absorption filter input is valid
|
|
66
|
-
if self.alpha_filter is not None and
|
|
76
|
+
if self.alpha_filter is not None and self.alpha_filter.shape != tuple(kgrid_shape):
|
|
67
77
|
raise ValueError("medium.alpha_filter must be the same size as the computational grid.")
|
|
68
78
|
|
|
69
79
|
# check the absorption sign input is valid
|
|
70
|
-
if self.alpha_sign is not None
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
if self.alpha_sign is not None:
|
|
81
|
+
alpha_sign_arr = np.atleast_1d(self.alpha_sign)
|
|
82
|
+
if alpha_sign_arr.size != 2 or not np.issubdtype(alpha_sign_arr.dtype, np.number):
|
|
83
|
+
raise ValueError("medium.alpha_sign must be a 2 element numeric array controlling absorption and dispersion, respectively.")
|
|
74
84
|
|
|
75
85
|
# check alpha_coeff is non-negative and real
|
|
76
|
-
if not np.all(np.isreal(self.alpha_coeff)) or np.any(self.alpha_coeff < 0):
|
|
86
|
+
if self.alpha_coeff is not None and (not np.all(np.isreal(self.alpha_coeff)) or np.any(self.alpha_coeff < 0)):
|
|
77
87
|
raise ValueError("medium.alpha_coeff must be non-negative and real.")
|
|
78
88
|
|
|
79
89
|
def is_defined(self, *fields) -> List[bool]:
|
|
@@ -102,7 +112,7 @@ class kWaveMedium(object):
|
|
|
102
112
|
None
|
|
103
113
|
"""
|
|
104
114
|
for f in fields:
|
|
105
|
-
assert getattr(self, f) is not None, f"The field {f} must
|
|
115
|
+
assert getattr(self, f) is not None, f"The field {f} must not be None"
|
|
106
116
|
|
|
107
117
|
def is_nonlinear(self) -> bool:
|
|
108
118
|
"""
|
|
@@ -141,13 +151,14 @@ class kWaveMedium(object):
|
|
|
141
151
|
# enforce both absorption parameters
|
|
142
152
|
self.ensure_defined("alpha_coeff", "alpha_power")
|
|
143
153
|
|
|
144
|
-
# check y is a scalar
|
|
145
|
-
|
|
154
|
+
# check y is a scalar (np.isscalar rejects 0-d ndarrays — accept np.array(1.5) too)
|
|
155
|
+
is_scalar = np.isscalar(self.alpha_power) or (isinstance(self.alpha_power, np.ndarray) and self.alpha_power.size == 1)
|
|
156
|
+
assert is_scalar, "medium.alpha_power must be scalar."
|
|
146
157
|
|
|
147
158
|
# check y is real and within 0 to 3
|
|
148
|
-
assert (
|
|
149
|
-
|
|
150
|
-
)
|
|
159
|
+
assert np.all(np.isreal(self.alpha_coeff)) and 0 <= self.alpha_power < 3, (
|
|
160
|
+
"medium.alpha_power must be a real number between 0 and 3."
|
|
161
|
+
)
|
|
151
162
|
|
|
152
163
|
# display warning if y is close to 1 and the dispersion term has not been set to zero
|
|
153
164
|
if self.alpha_mode != "no_dispersion":
|
|
@@ -167,8 +178,10 @@ class kWaveMedium(object):
|
|
|
167
178
|
self.ensure_defined("alpha_coeff")
|
|
168
179
|
|
|
169
180
|
# give warning if y is specified
|
|
170
|
-
if self.alpha_power is not None
|
|
171
|
-
|
|
181
|
+
if self.alpha_power is not None:
|
|
182
|
+
ap = np.asarray(self.alpha_power)
|
|
183
|
+
if ap.size != 1 or not np.isclose(ap.item(), 2.0):
|
|
184
|
+
logging.warning("the axisymmetric code and stokes absorption assume alpha_power = 2, user value ignored.")
|
|
172
185
|
|
|
173
186
|
# overwrite y value
|
|
174
187
|
self.alpha_power = 2
|
|
@@ -176,12 +189,12 @@ class kWaveMedium(object):
|
|
|
176
189
|
# don't allow medium.alpha_mode with the axisymmetric code
|
|
177
190
|
if self.alpha_mode is not None and (self.alpha_mode in ["no_absorption", "no_dispersion"]):
|
|
178
191
|
raise NotImplementedError(
|
|
179
|
-
"Input option medium.alpha_mode is not supported with the axisymmetric code
|
|
192
|
+
"Input option medium.alpha_mode is not supported with the axisymmetric code or medium.alpha_mode = stokes."
|
|
180
193
|
)
|
|
181
194
|
|
|
182
195
|
# don't allow alpha_filter with stokes absorption (no variables are applied in k-space)
|
|
183
196
|
assert self.alpha_filter is None, (
|
|
184
|
-
"Input option medium.alpha_filter is not supported with the axisymmetric code
|
|
197
|
+
"Input option medium.alpha_filter is not supported with the axisymmetric code or medium.alpha_mode = 'stokes'. "
|
|
185
198
|
)
|
|
186
199
|
|
|
187
200
|
##########################################
|