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.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: k-Wave-python
3
- Version: 0.6.0
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@tum.de>
10
- Maintainer-email: Walter Simson <walter.simson@tum.de>, Farid Yagubbayli <farid.yagubbayli@tum.de>, David Sinden <david.sinden@mevis.fraunhofer.de>
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==8.6.1
182
+ Requires-Dist: deepdiff==9.0.0
183
183
  Requires-Dist: deprecated>=1.2.14
184
- Requires-Dist: h5py==3.15.1
185
- Requires-Dist: jaxtyping==0.3.2
186
- Requires-Dist: matplotlib==3.10.7
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.5.1; extra == 'dev'
192
+ Requires-Dist: pre-commit==4.6.0; extra == 'dev'
192
193
  Provides-Extra: docs
193
- Requires-Dist: furo==2024.8.6; extra == 'docs'
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.7; extra == 'docs'
197
- Requires-Dist: sphinx-toolbox==3.8.0; extra == 'docs'
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==5.2.0; extra == 'example'
201
+ Requires-Dist: gdown==6.0.0; extra == 'example'
200
202
  Provides-Extra: test
201
- Requires-Dist: coverage==7.10.6; extra == 'test'
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.32.5; extra == 'test'
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
  [![Support](https://img.shields.io/discord/1234942672418897960?style=flat&logo=discord)](https://discord.gg/your-invite-code)
212
214
  [![Documentation Status](https://readthedocs.org/projects/k-wave-python/badge/?version=latest)](https://k-wave-python.readthedocs.io/en/latest/?badge=latest)
213
215
  [![codecov](https://codecov.io/gh/waltsims/k-wave-python/graph/badge.svg?token=6ofwtPiDNG)](https://codecov.io/gh/waltsims/k-wave-python)
214
- [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/waltsims/k-wave-python/master)
215
216
 
216
- This project is a Python implementation of v1.4.0 of the [MATLAB toolbox k-Wave](http://www.k-wave.org/) as well as an
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
- With this project, we hope to increase the accessibility and reproducibility of [k-Wave](http://www.k-wave.org/) simulations
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
  ![](_static/example_bmode.png)
231
226
 
232
- A large [collection of examples](../examples/) exists to get started with k-wave-python. All examples can be run in Google Colab notebooks with a few clicks. One can begin with e.g. the [B-mode reconstruction example notebook](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb).
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
- This example file steps through the process of:
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
- To install the most recent build of k-Wave-python from PyPI, run:
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
- If you're enjoying k-Wave-python and want to contribute, development instructions can be
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 = {Yagubbbayli, Farid and Sinden, David and Simson, Walter},
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 [wsimson@stanford.edu](mailto:wsimson@stanford.edu).
276
+ e-mail [walter.a.simson@gmail.com](mailto:walter.a.simson@gmail.com).
@@ -1,23 +1,23 @@
1
- kwave/__init__.py,sha256=KllpkYD76o1gZ8jqC9qKTKPBoOLPfB0FG_Xl89MiDtQ,7008
2
- kwave/compat.py,sha256=KscnU7yPF4jLfEj9PIeQdIiIn8eQGDgtsTsL6sE9l9U,2923
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=tptEv0ZDcXEd2k5S-3cM21YudmagMQm2oYx52MwZDPk,470
5
- kwave/executor.py,sha256=pf1NhJmoCLsS-zVQXBua1C0qamyBB6V6sjRXY6I30Sg,5973
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=WYICT1g6KES5Gr8SMZG47Wc-mKn-uzg-DsmEECae61I,23197
8
- kwave/kmedium.py,sha256=CS3deWUuyVksC3D5L_Z6UlS7_-0VJX5oDsTCVKUUc4E,9046
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=FtbdIIwb0miJCKH3kRq2cUhECYZ1lCHsFd22kMf9BFQ,10733
12
- kwave/kspaceFirstOrder2D.py,sha256=nA-dN3zEsZy4QazxsMBNa5DcBeFjudrh5lIeBAukMSU,16551
13
- kwave/kspaceFirstOrder3D.py,sha256=tWB4KvDRflYZ8rqVpwwtdiCfq7AJ87PGJ-n1qv_QkH8,17463
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=7Du_Q3Jzr_RiSjhEHF-afdQL_Q5Dds1Xj0Sc-Fmivqg,4926
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=2MCENhNjPHN6xWLbhil4b7_GIVbqjWxleiF6xi7xSzE,12974
36
- kwave/solvers/kspace_solver.py,sha256=XMrd9iCtl8-YEjjFZEE0Fbtmx5BlV3NgM5Uke6LLcVc,34027
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=VUdPHGKUP9D81-NZk8XgJFGotlG8gOcw9XMryQ5BGJc,9979
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=4O8WOViuHePuwfPBmIxOi-J6yVZKZxg1LiJHJJFxlqc,17320
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=hPldsyMVd_Mr1TjZXK-eF_klwJ2MBWDmUUZ6eDYIL9Q,22253
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=44CFUwSFCWbHBiAr-V4xp3d5CbNCE-WPRLvLb9Vkn7A,38427
52
- kwave/utils/mapgen.py,sha256=kHghj_mxsGFMW1cPBw4cgmDbw-IcgLko0Fpnkdwry9Y,116073
53
- kwave/utils/math.py,sha256=fKS1g7XvOs6qQ6R5Ykss1hu9vant1VbemuBcNuiQBT0,13843
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=MU6Pl4GYIZttePNfky1bEqdFyDb1W1_omlAtWfbd3b8,1496
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=uu_dCUIlHiyov4avdOZ2BfloOuFiXzQwzQ51tixcIZc,1028
62
- k_wave_python-0.6.0.dist-info/METADATA,sha256=nJwrMbi6qsWD76SOBMG3VbmgmQRaqMA4r8QXQjEAGJM,14584
63
- k_wave_python-0.6.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
64
- k_wave_python-0.6.0.dist-info/licenses/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
65
- k_wave_python-0.6.0.dist-info/RECORD,,
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.0"
14
+ __version__ = "0.6.2"
13
15
 
14
16
  # Constants and Configurations
15
17
  URL_BASE = "https://github.com/waltsims/"
16
- BINARY_VERSION = "v1.3.0"
17
- PREFIX = f"{URL_BASE}kspaceFirstOrder-{{}}-{{}}/releases/download/{BINARY_VERSION}/"
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
- release_urls = [PREFIX.format(architecture.upper(), PLATFORM.lower()) + filename for filename in specific_filenames]
51
- return release_urls
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/v1.3.1/{EXECUTABLE_PREFIX}CUDA"],
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": [URL_BASE + f"k-wave-omp-{PLATFORM}/releases/download/v0.3.0rc3/{EXECUTABLE_PREFIX}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
- import kwave.utils.checks
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.array
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.array = None
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.array = None
39
+ density: Optional[Union[float, int, np.ndarray]] = None
18
40
  # power law absorption coefficient [dB/(MHz^y cm)]
19
- alpha_coeff: np.array = None
41
+ alpha_coeff: Optional[Union[float, int, np.ndarray]] = None
20
42
  # power law absorption exponent
21
- alpha_power: np.array = None
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 'no_absorption' or 'no_dispersion'
24
- alpha_mode: np.array = None
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.array = None
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.array = None
50
+ alpha_sign: Optional[np.ndarray] = None
29
51
  # parameter of nonlinearity
30
- BonA: np.array = None
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: np.ndarray) -> None:
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
- # check the absorption mode input is valid
58
- if self.alpha_mode is not None:
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 not (self.alpha_filter.shape == kgrid_shape).all():
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 and (not kwave.utils.checkutils.is_number(self.alpha_sign) or (self.alpha_sign.size != 2)):
71
- raise ValueError(
72
- "medium.alpha_sign must be given as a " "2 element numerical array controlling absorption and dispersion, respectively."
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 be not be None"
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
- assert np.isscalar(self.alpha_power), "medium.alpha_power must be scalar."
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
- np.all(np.isreal(self.alpha_coeff)) and 0 <= self.alpha_power < 3
150
- ), "medium.alpha_power must be a real number between 0 and 3."
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 and (self.alpha_power.size != 1 or self.alpha_power != 2):
171
- logging.log(logging.WARN, "the axisymmetric code and stokes absorption assume alpha_power = 2, user value ignored.")
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 " "or medium.alpha_mode = " "stokes" "."
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 " "or medium.alpha_mode = 'stokes'. "
197
+ "Input option medium.alpha_filter is not supported with the axisymmetric code or medium.alpha_mode = 'stokes'. "
185
198
  )
186
199
 
187
200
  ##########################################