diff --git a/README.md b/docs/README.md similarity index 72% rename from README.md rename to docs/README.md index fe266905..f61ccc05 100644 --- a/README.md +++ b/docs/README.md @@ -6,9 +6,6 @@ This project is a Python implementation of most of the [MATLAB toolbox k-Wave](h With this project, we hope to increase accessibility and reproducablitiy of [k-Wave](http://www.k-wave.org/) simulations for medical imaging, algorithmic prototyping and testing. Many tools and methods of [k-Wave](http://www.k-wave.org/) can be found here, but this project has and will continue to diverge from the original [k-Wave](http://www.k-wave.org/) APIs in order to leverage pythonic practices. -## Documentation - -The documentation for k-wave-python can be found [here](http://waltersimson.com/k-wave-python/) ## Installation To install the most recent build of k-Wave-python from PyPI, run: @@ -20,7 +17,7 @@ Currently, we are looking for beta testers on Windows. ## Getting started -![](docs/images/example_bmode.png) +![](_static/example_bmode.png) After installation, run the B-mode reconstruction example in the `examples` directory of the repository: @@ -42,18 +39,25 @@ This example expects an NVIDIA GPU by default to simulate with k-Wave. To test the reconstruction on a machine with a GPU, set `RUN_SIMULATION` [on line 14 of `bmode_reconstruction_example.py`](https://github.com/waltsims/k-wave-python/blob/master/examples/bmode_reconstruction_example.py#L18) -to `True` and the exmaple will run without the pre-computed data. +to `True` and the example will run without the pre-computed data. ## Development -If you're enjoying k-Wave-python and want to contribute, development instructions can be found [here](https://waltersimson.com/k-wave-python/development/development_environment.html). +If you're enjoying k-Wave-python and want to contribute, development instructions can be +found [here](https://waltersimson.com/k-wave-python/development/development_environment.html). ## Related Projects -1. [`k-Wave`](https://github.com/ucl-bug/k-wave): A MATLAB toolbox for the time-domain simulation of acoustic wave fields. -2. [`j-wave`](https://github.com/ucl-bug/jwave): Differentiable acoustic simulations in JAX. -3. [`ADSeismic.jl`](https://github.com/kailaix/ADSeismic.jl): a finite difference acoustic simulator with support for AD and JIT compilation in Julia. -4. [`stride`](https://github.com/trustimaging/stride): a general optimisation framework for medical ultrasound tomography. +1. [k-Wave](https://github.com/ucl-bug/k-wave): A MATLAB toolbox for the time-domain simulation of acoustic wave fields. +2. [j-wave](https://github.com/ucl-bug/jwave): Differentiable acoustic simulations in JAX. +3. [ADSeismic.jl](https://github.com/kailaix/ADSeismic.jl): a finite difference acoustic simulator with support for AD + and JIT compilation in Julia. +4. [stride](https://github.com/trustimaging/stride): a general optimisation framework for medical ultrasound tomography. + +## Documentation + +The documentation for k-wave-python can be found [here](http://waltersimson.com/k-wave-python/) ## Contact + e-mail [walter.simson@tum.de](mailto:walter.simson@tum.de). diff --git a/docs/images/example_bmode.png b/docs/_static/example_bmode.png similarity index 100% rename from docs/images/example_bmode.png rename to docs/_static/example_bmode.png diff --git a/docs/array.rst b/docs/array.rst deleted file mode 100644 index f4e8366c..00000000 --- a/docs/array.rst +++ /dev/null @@ -1,5 +0,0 @@ -Array -===== -.. automodule:: kwave.data - :members: - :undoc-members: diff --git a/docs/conf.py b/docs/conf.py index 59961467..a7bd798f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,69 +1,57 @@ # Configuration file for the Sphinx documentation builder. # -# This file only contains a selection of the most common options. For a full -# list see the documentation: +# For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - -import os -import sys - -sys.path.insert(0, os.path.abspath('..')) - # -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = 'k-Wave-python' -copyright = '2021, Walter Simson, Farid Yagubbayli' +project = 'k-wave-python' +copyright = '2022, Walter Simson, Farid Yagubbayli' author = 'Walter Simson, Farid Yagubbayli' +version = '0.1.0' # -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.githubpages', 'sphinx_toolbox.code', + 'sphinx_copybutton', 'sphinx.ext.coverage', 'sphinx.ext.napoleon', - 'sphinx.ext.viewcode' + 'sphinx.ext.viewcode', + "m2r2" ] -# Add any paths that contain templates here, relative to this directory. +source_suffix = [".rst", ".md"] templates_path = ['_templates'] +exclude_patterns = ['README.md', '_build', 'Thumbs.db', '.DS_Store'] -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +language = 'en' # -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +html_theme = 'furo' +html_theme_options = { + "source_repository": "https://github.com/waltsims/k-wave-python", + "source_branch": "master", + "source_directory": "docs/", +} +html_static_path = ['_static'] -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'sphinx_rtd_theme' +# -- Options for todo extension ---------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/todo.html#configuration -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [''] +todo_include_todos = True -autodoc_default_options = { - 'member-order': 'groupwise', - 'undoc-members': True -} +# -- Options for autodoc ---------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration -html_context = { - "display_github": True, # Integrate GitHub - "github_user": "waltsims", # Username - "github_repo": "k-wave-python", # Repo name - "github_version": "master", # Version - "conf_py_path": "/docs/", # Path in the checkout to the docs root -} +# Automatically extract typehints when specified and place them in +# descriptions of the relevant function/method. +autodoc_typehints = "description" + +# Don't show class signature with the class' name. +autodoc_class_signature = "separated" diff --git a/docs/examples/bmode.rst b/docs/examples/bmode.rst deleted file mode 100644 index fc17692d..00000000 --- a/docs/examples/bmode.rst +++ /dev/null @@ -1,18 +0,0 @@ -B-mode Reconstruction -====================== - -After installation, run the B-mode reconstruction example in the `examples` directory of the repository: - -.. code-block:: bash - - pip install -r example_requirements.txt - python3 examples/bmode_reconstruction_example.py - - -This example file steps through the process of: - 1. Generating a simulation medium - 2. Configuring a transducer - 3. Running the simulation - 4. Reconstructing the simulation - -.. image:: ../images/example_bmode.png diff --git a/docs/get_started/installation.rst b/docs/get_started/installation.rst deleted file mode 100644 index 017d2635..00000000 --- a/docs/get_started/installation.rst +++ /dev/null @@ -1,10 +0,0 @@ -Installation -============ - -to install run: - -.. code-block:: bash - - git clone https://github.com/waltsims/k-wave-python - cd k-wave-python - python setup.py install diff --git a/docs/index.rst b/docs/index.rst index b5a1decf..41463b48 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,50 +3,47 @@ Welcome to k-Wave-python's documentation! k-Wave is an open source acoustics toolbox for MATLAB and C++ developed by Bradley Treeby and Ben Cox (University College London) and Jiri Jaros (Brno University of Technology). The software is designed for time domain acoustic and ultrasound simulations in complex and tissue-realistic media. The simulation functions are based on the k-space pseudospectral method and are both fast and easy to use. +.. mdinclude:: README.md + :start-line: 5 + :end-line: -7 .. toctree:: :maxdepth: 2 :caption: Getting Started + :hidden: - get_started/installation get_started/contrib get_started/license -.. toctree:: - :maxdepth: 2 - :caption: Examples - - examples/bmode - - -.. toctree:: - :maxdepth: 3 - :caption: Base Modules - - kgrid - kmedium - ksensor - ksource - kspace_fo - ksimulation - -.. toctree:: - :maxdepth: 2 - :caption: Helper Modules - - simopt - array - recorder - .. toctree:: :maxdepth: 2 :caption: Development + :hidden: development/development_environment -Indices and tables -================== +.. toctree:: + :caption: kwave + :titlesonly: + :maxdepth: 4 + :hidden: + + kwave.data + kwave.enums + kwave.executor + kwave.kWaveSimulation + kwave.kgrid + kwave.kmedium + kwave.ksensor + kwave.ksource + kwave.kspaceFirstOrder + kwave.kspaceFirstOrder2D + kwave.kspaceFirstOrder3D + kwave.kspaceFirstOrderAS + kwave.ktransducer + kwave.options + kwave.recorder + kwave.utils + kwave.kWaveSimulation_helper + kwave.reconstruction -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/kgrid.rst b/docs/kgrid.rst deleted file mode 100644 index acfd5467..00000000 --- a/docs/kgrid.rst +++ /dev/null @@ -1,6 +0,0 @@ -kWaveGrid -========= -.. automodule:: kwave.kgrid - :members: - - diff --git a/docs/kmedium.rst b/docs/kmedium.rst deleted file mode 100644 index 1bcd16a8..00000000 --- a/docs/kmedium.rst +++ /dev/null @@ -1,4 +0,0 @@ -kMedium -======= -.. automodule:: kwave.kmedium - :members: diff --git a/docs/ksensor.rst b/docs/ksensor.rst deleted file mode 100644 index de3051f2..00000000 --- a/docs/ksensor.rst +++ /dev/null @@ -1,11 +0,0 @@ -kSensor -======= -.. automodule:: kwave.ksensor - -.. currentmodule:: kwave.ksensor - -.. autoclass:: kSensor - :members: - -.. autoclass:: kSensorDirectivity - :members: diff --git a/docs/ksimulation.rst b/docs/ksimulation.rst deleted file mode 100644 index 598ffc88..00000000 --- a/docs/ksimulation.rst +++ /dev/null @@ -1,5 +0,0 @@ -kWaveSimulation -=============== -.. automodule:: kwave.kWaveSimulation - :members: - :undoc-members: diff --git a/docs/ksource.rst b/docs/ksource.rst deleted file mode 100644 index b4bd2b13..00000000 --- a/docs/ksource.rst +++ /dev/null @@ -1,10 +0,0 @@ -kSource -======= - -.. automodule:: kwave.ksource - -.. currentmodule:: kwave.ksource - -.. autoclass:: kSource - :members: - :undoc-members: diff --git a/docs/kspace_fo.rst b/docs/kspace_fo.rst deleted file mode 100644 index 99dea521..00000000 --- a/docs/kspace_fo.rst +++ /dev/null @@ -1,28 +0,0 @@ -kSpace First Order -================== - -Decorator -******************* -.. automodule:: kwave.kspaceFirstOrder - :members: - - -kSpaceFirstOrder 2D -******************* - -.. automodule:: kwave.kspaceFirstOrder2D - :members: - - -kSpaceFirstOrder 3D -******************* - -.. automodule:: kwave.kspaceFirstOrder3D - :members: - - -kSpaceFirstOrder Axisymmetric -***************************** - -.. automodule:: kwave.kspaceFirstOrderAS - :members: diff --git a/docs/kwave.data.rst b/docs/kwave.data.rst new file mode 100644 index 00000000..5d708a27 --- /dev/null +++ b/docs/kwave.data.rst @@ -0,0 +1,7 @@ +kwave.data module +================= + +.. automodule:: kwave.data + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.enums.rst b/docs/kwave.enums.rst new file mode 100644 index 00000000..f65358f5 --- /dev/null +++ b/docs/kwave.enums.rst @@ -0,0 +1,7 @@ +kwave.enums module +================== + +.. automodule:: kwave.enums + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.executor.rst b/docs/kwave.executor.rst new file mode 100644 index 00000000..cb4b9e28 --- /dev/null +++ b/docs/kwave.executor.rst @@ -0,0 +1,7 @@ +kwave.executor module +===================== + +.. automodule:: kwave.executor + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation.rst b/docs/kwave.kWaveSimulation.rst new file mode 100644 index 00000000..87d6c18a --- /dev/null +++ b/docs/kwave.kWaveSimulation.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation module +============================ + +.. automodule:: kwave.kWaveSimulation + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.create_absorption_variables.rst b/docs/kwave.kWaveSimulation_helper.create_absorption_variables.rst new file mode 100644 index 00000000..6e7f8361 --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.create_absorption_variables.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.create\_absorption\_variables module +================================================================== + +.. automodule:: kwave.kWaveSimulation_helper.create_absorption_variables + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.create_storage_variables.rst b/docs/kwave.kWaveSimulation_helper.create_storage_variables.rst new file mode 100644 index 00000000..8458553b --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.create_storage_variables.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.create\_storage\_variables module +=============================================================== + +.. automodule:: kwave.kWaveSimulation_helper.create_storage_variables + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.data_cast.rst b/docs/kwave.kWaveSimulation_helper.data_cast.rst new file mode 100644 index 00000000..c754cd37 --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.data_cast.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.data\_cast module +=============================================== + +.. automodule:: kwave.kWaveSimulation_helper.data_cast + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.display_simulation_params.rst b/docs/kwave.kWaveSimulation_helper.display_simulation_params.rst new file mode 100644 index 00000000..6f3fde3c --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.display_simulation_params.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.display\_simulation\_params module +================================================================ + +.. automodule:: kwave.kWaveSimulation_helper.display_simulation_params + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.expand_grid_matrices.rst b/docs/kwave.kWaveSimulation_helper.expand_grid_matrices.rst new file mode 100644 index 00000000..2d9761c9 --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.expand_grid_matrices.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.expand\_grid\_matrices module +=========================================================== + +.. automodule:: kwave.kWaveSimulation_helper.expand_grid_matrices + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.retract_transducer_grid_size.rst b/docs/kwave.kWaveSimulation_helper.retract_transducer_grid_size.rst new file mode 100644 index 00000000..d586bacc --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.retract_transducer_grid_size.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.retract\_transducer\_grid\_size module +==================================================================== + +.. automodule:: kwave.kWaveSimulation_helper.retract_transducer_grid_size + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.rst b/docs/kwave.kWaveSimulation_helper.rst new file mode 100644 index 00000000..0894e801 --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.rst @@ -0,0 +1,26 @@ +kwave.kWaveSimulation\_helper package +===================================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + kwave.kWaveSimulation_helper.create_absorption_variables + kwave.kWaveSimulation_helper.create_storage_variables + kwave.kWaveSimulation_helper.data_cast + kwave.kWaveSimulation_helper.display_simulation_params + kwave.kWaveSimulation_helper.expand_grid_matrices + kwave.kWaveSimulation_helper.retract_transducer_grid_size + kwave.kWaveSimulation_helper.save_to_disk_func + kwave.kWaveSimulation_helper.scale_source_terms_func + kwave.kWaveSimulation_helper.set_sound_speed_ref + +Module contents +--------------- + +.. automodule:: kwave.kWaveSimulation_helper + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.save_to_disk_func.rst b/docs/kwave.kWaveSimulation_helper.save_to_disk_func.rst new file mode 100644 index 00000000..66224c49 --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.save_to_disk_func.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.save\_to\_disk\_func module +========================================================= + +.. automodule:: kwave.kWaveSimulation_helper.save_to_disk_func + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.scale_source_terms_func.rst b/docs/kwave.kWaveSimulation_helper.scale_source_terms_func.rst new file mode 100644 index 00000000..f1cba425 --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.scale_source_terms_func.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.scale\_source\_terms\_func module +=============================================================== + +.. automodule:: kwave.kWaveSimulation_helper.scale_source_terms_func + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kWaveSimulation_helper.set_sound_speed_ref.rst b/docs/kwave.kWaveSimulation_helper.set_sound_speed_ref.rst new file mode 100644 index 00000000..8389ab33 --- /dev/null +++ b/docs/kwave.kWaveSimulation_helper.set_sound_speed_ref.rst @@ -0,0 +1,7 @@ +kwave.kWaveSimulation\_helper.set\_sound\_speed\_ref module +=========================================================== + +.. automodule:: kwave.kWaveSimulation_helper.set_sound_speed_ref + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kgrid.rst b/docs/kwave.kgrid.rst new file mode 100644 index 00000000..e15f6ac2 --- /dev/null +++ b/docs/kwave.kgrid.rst @@ -0,0 +1,7 @@ +kwave.kgrid module +================== + +.. automodule:: kwave.kgrid + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kmedium.rst b/docs/kwave.kmedium.rst new file mode 100644 index 00000000..b1ea05c5 --- /dev/null +++ b/docs/kwave.kmedium.rst @@ -0,0 +1,7 @@ +kwave.kmedium module +==================== + +.. automodule:: kwave.kmedium + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.ksensor.rst b/docs/kwave.ksensor.rst new file mode 100644 index 00000000..9946ac14 --- /dev/null +++ b/docs/kwave.ksensor.rst @@ -0,0 +1,7 @@ +kwave.ksensor module +==================== + +.. automodule:: kwave.ksensor + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.ksource.rst b/docs/kwave.ksource.rst new file mode 100644 index 00000000..09d2109b --- /dev/null +++ b/docs/kwave.ksource.rst @@ -0,0 +1,7 @@ +kwave.ksource module +==================== + +.. automodule:: kwave.ksource + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kspaceFirstOrder.rst b/docs/kwave.kspaceFirstOrder.rst new file mode 100644 index 00000000..17a0cf8f --- /dev/null +++ b/docs/kwave.kspaceFirstOrder.rst @@ -0,0 +1,7 @@ +kwave.kspaceFirstOrder module +============================= + +.. automodule:: kwave.kspaceFirstOrder + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kspaceFirstOrder2D.rst b/docs/kwave.kspaceFirstOrder2D.rst new file mode 100644 index 00000000..b5366af9 --- /dev/null +++ b/docs/kwave.kspaceFirstOrder2D.rst @@ -0,0 +1,7 @@ +kwave.kspaceFirstOrder2D module +=============================== + +.. automodule:: kwave.kspaceFirstOrder2D + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kspaceFirstOrder3D.rst b/docs/kwave.kspaceFirstOrder3D.rst new file mode 100644 index 00000000..91f233a9 --- /dev/null +++ b/docs/kwave.kspaceFirstOrder3D.rst @@ -0,0 +1,7 @@ +kwave.kspaceFirstOrder3D module +=============================== + +.. automodule:: kwave.kspaceFirstOrder3D + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.kspaceFirstOrderAS.rst b/docs/kwave.kspaceFirstOrderAS.rst new file mode 100644 index 00000000..6ec74d7f --- /dev/null +++ b/docs/kwave.kspaceFirstOrderAS.rst @@ -0,0 +1,7 @@ +kwave.kspaceFirstOrderAS module +=============================== + +.. automodule:: kwave.kspaceFirstOrderAS + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.ktransducer.rst b/docs/kwave.ktransducer.rst new file mode 100644 index 00000000..b2925293 --- /dev/null +++ b/docs/kwave.ktransducer.rst @@ -0,0 +1,7 @@ +kwave.ktransducer module +======================== + +.. automodule:: kwave.ktransducer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.options.rst b/docs/kwave.options.rst new file mode 100644 index 00000000..890033c9 --- /dev/null +++ b/docs/kwave.options.rst @@ -0,0 +1,7 @@ +kwave.options module +==================== + +.. automodule:: kwave.options + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.reconstruction.beamform.rst b/docs/kwave.reconstruction.beamform.rst new file mode 100644 index 00000000..59f0456a --- /dev/null +++ b/docs/kwave.reconstruction.beamform.rst @@ -0,0 +1,7 @@ +kwave.reconstruction.beamform module +==================================== + +.. automodule:: kwave.reconstruction.beamform + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.reconstruction.converter.rst b/docs/kwave.reconstruction.converter.rst new file mode 100644 index 00000000..175d8f9a --- /dev/null +++ b/docs/kwave.reconstruction.converter.rst @@ -0,0 +1,7 @@ +kwave.reconstruction.converter module +===================================== + +.. automodule:: kwave.reconstruction.converter + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.reconstruction.rst b/docs/kwave.reconstruction.rst new file mode 100644 index 00000000..bc1b2189 --- /dev/null +++ b/docs/kwave.reconstruction.rst @@ -0,0 +1,21 @@ +kwave.reconstruction package +============================ + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + kwave.reconstruction.beamform + kwave.reconstruction.converter + kwave.reconstruction.shifted_transform + kwave.reconstruction.tools + +Module contents +--------------- + +.. automodule:: kwave.reconstruction + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.reconstruction.shifted_transform.rst b/docs/kwave.reconstruction.shifted_transform.rst new file mode 100644 index 00000000..4d076777 --- /dev/null +++ b/docs/kwave.reconstruction.shifted_transform.rst @@ -0,0 +1,7 @@ +kwave.reconstruction.shifted\_transform module +============================================== + +.. automodule:: kwave.reconstruction.shifted_transform + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.reconstruction.tools.rst b/docs/kwave.reconstruction.tools.rst new file mode 100644 index 00000000..6fde6220 --- /dev/null +++ b/docs/kwave.reconstruction.tools.rst @@ -0,0 +1,7 @@ +kwave.reconstruction.tools module +================================= + +.. automodule:: kwave.reconstruction.tools + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.recorder.rst b/docs/kwave.recorder.rst new file mode 100644 index 00000000..742009a4 --- /dev/null +++ b/docs/kwave.recorder.rst @@ -0,0 +1,7 @@ +kwave.recorder module +===================== + +.. automodule:: kwave.recorder + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.rst b/docs/kwave.rst new file mode 100644 index 00000000..2cca85b8 --- /dev/null +++ b/docs/kwave.rst @@ -0,0 +1,42 @@ +kwave package +============= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + kwave.kWaveSimulation_helper + kwave.reconstruction + kwave.utils + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + kwave.data + kwave.enums + kwave.executor + kwave.kWaveSimulation + kwave.kgrid + kwave.kmedium + kwave.ksensor + kwave.ksource + kwave.kspaceFirstOrder + kwave.kspaceFirstOrder2D + kwave.kspaceFirstOrder3D + kwave.kspaceFirstOrderAS + kwave.ktransducer + kwave.options + kwave.recorder + +Module contents +--------------- + +.. automodule:: kwave + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.checks.rst b/docs/kwave.utils.checks.rst new file mode 100644 index 00000000..2c75a174 --- /dev/null +++ b/docs/kwave.utils.checks.rst @@ -0,0 +1,7 @@ +kwave.utils.checks module +========================= + +.. automodule:: kwave.utils.checks + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.colormap.rst b/docs/kwave.utils.colormap.rst new file mode 100644 index 00000000..887657ed --- /dev/null +++ b/docs/kwave.utils.colormap.rst @@ -0,0 +1,7 @@ +kwave.utils.colormap module +=========================== + +.. automodule:: kwave.utils.colormap + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.conversion.rst b/docs/kwave.utils.conversion.rst new file mode 100644 index 00000000..5c656ffd --- /dev/null +++ b/docs/kwave.utils.conversion.rst @@ -0,0 +1,7 @@ +kwave.utils.conversion module +============================= + +.. automodule:: kwave.utils.conversion + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.data.rst b/docs/kwave.utils.data.rst new file mode 100644 index 00000000..5358de2d --- /dev/null +++ b/docs/kwave.utils.data.rst @@ -0,0 +1,7 @@ +kwave.utils.data module +======================= + +.. automodule:: kwave.utils.data + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.dotdictionary.rst b/docs/kwave.utils.dotdictionary.rst new file mode 100644 index 00000000..8012e66d --- /dev/null +++ b/docs/kwave.utils.dotdictionary.rst @@ -0,0 +1,7 @@ +kwave.utils.dotdictionary module +================================ + +.. automodule:: kwave.utils.dotdictionary + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.filters.rst b/docs/kwave.utils.filters.rst new file mode 100644 index 00000000..30986f43 --- /dev/null +++ b/docs/kwave.utils.filters.rst @@ -0,0 +1,7 @@ +kwave.utils.filters module +========================== + +.. automodule:: kwave.utils.filters + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.interp.rst b/docs/kwave.utils.interp.rst new file mode 100644 index 00000000..22717971 --- /dev/null +++ b/docs/kwave.utils.interp.rst @@ -0,0 +1,7 @@ +kwave.utils.interp module +========================= + +.. automodule:: kwave.utils.interp + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.io.rst b/docs/kwave.utils.io.rst new file mode 100644 index 00000000..fcbd7df6 --- /dev/null +++ b/docs/kwave.utils.io.rst @@ -0,0 +1,7 @@ +kwave.utils.io module +===================== + +.. automodule:: kwave.utils.io + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.mapgen.rst b/docs/kwave.utils.mapgen.rst new file mode 100644 index 00000000..002bb8ac --- /dev/null +++ b/docs/kwave.utils.mapgen.rst @@ -0,0 +1,7 @@ +kwave.utils.mapgen module +========================= + +.. automodule:: kwave.utils.mapgen + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.math.rst b/docs/kwave.utils.math.rst new file mode 100644 index 00000000..e86bd4a7 --- /dev/null +++ b/docs/kwave.utils.math.rst @@ -0,0 +1,7 @@ +kwave.utils.math module +======================= + +.. automodule:: kwave.utils.math + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.matrix.rst b/docs/kwave.utils.matrix.rst new file mode 100644 index 00000000..289c07f0 --- /dev/null +++ b/docs/kwave.utils.matrix.rst @@ -0,0 +1,7 @@ +kwave.utils.matrix module +========================= + +.. automodule:: kwave.utils.matrix + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.pml.rst b/docs/kwave.utils.pml.rst new file mode 100644 index 00000000..f0a27f06 --- /dev/null +++ b/docs/kwave.utils.pml.rst @@ -0,0 +1,7 @@ +kwave.utils.pml module +====================== + +.. automodule:: kwave.utils.pml + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.rst b/docs/kwave.utils.rst new file mode 100644 index 00000000..69e6257b --- /dev/null +++ b/docs/kwave.utils.rst @@ -0,0 +1,31 @@ +kwave.utils package +=================== + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + kwave.utils.checks + kwave.utils.colormap + kwave.utils.conversion + kwave.utils.data + kwave.utils.dotdictionary + kwave.utils.filters + kwave.utils.interp + kwave.utils.io + kwave.utils.mapgen + kwave.utils.math + kwave.utils.matrix + kwave.utils.pml + kwave.utils.signals + kwave.utils.tictoc + +Module contents +--------------- + +.. automodule:: kwave.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.signals.rst b/docs/kwave.utils.signals.rst new file mode 100644 index 00000000..3e89667a --- /dev/null +++ b/docs/kwave.utils.signals.rst @@ -0,0 +1,7 @@ +kwave.utils.signals module +========================== + +.. automodule:: kwave.utils.signals + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/kwave.utils.tictoc.rst b/docs/kwave.utils.tictoc.rst new file mode 100644 index 00000000..3e3fc5ed --- /dev/null +++ b/docs/kwave.utils.tictoc.rst @@ -0,0 +1,7 @@ +kwave.utils.tictoc module +========================= + +.. automodule:: kwave.utils.tictoc + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/make.bat b/docs/make.bat index 922152e9..954237b9 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -10,8 +10,6 @@ if "%SPHINXBUILD%" == "" ( set SOURCEDIR=. set BUILDDIR=_build -if "%1" == "" goto help - %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. @@ -21,10 +19,12 @@ if errorlevel 9009 ( echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ + echo.https://www.sphinx-doc.org/ exit /b 1 ) +if "%1" == "" goto help + %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end diff --git a/docs/recorder.rst b/docs/recorder.rst deleted file mode 100644 index 1d9a59ad..00000000 --- a/docs/recorder.rst +++ /dev/null @@ -1,8 +0,0 @@ -Recorder -======== -.. automodule:: kwave.recorder - -.. currentmodule:: kwave.recorder - -.. autoclass:: Recorder - :members: diff --git a/docs/simopt.rst b/docs/simopt.rst deleted file mode 100644 index 1838e340..00000000 --- a/docs/simopt.rst +++ /dev/null @@ -1,5 +0,0 @@ -Simulation Options -================== -.. automodule:: kwave.options - :members: - :undoc-members: diff --git a/examples/bmode_reconstruction_example.py b/examples/bmode_reconstruction_example.py index 6eaa80a4..006eede5 100644 --- a/examples/bmode_reconstruction_example.py +++ b/examples/bmode_reconstruction_example.py @@ -1,3 +1,4 @@ +import os from tempfile import gettempdir import scipy.io @@ -8,6 +9,8 @@ from kwave.ktransducer import * from kwave.reconstruction.beamform import beamform from kwave.reconstruction.converter import build_channel_data +from kwave.utils.dotdictionary import dotdict +from kwave.utils.signals import tone_burst if __name__ == '__main__': # pathname for the input and output files diff --git a/kwave/kWaveSimulation.py b/kwave/kWaveSimulation.py index d6f9068d..a4b0ebbf 100644 --- a/kwave/kWaveSimulation.py +++ b/kwave/kWaveSimulation.py @@ -8,8 +8,15 @@ from kwave.ktransducer import NotATransducer from kwave.options import SimulationOptions from kwave.recorder import Recorder -from kwave.utils import * -from kwave.utils import dotdict +from kwave.utils.checks import num_dim2, check_stability +from kwave.utils.colormap import get_color_map +from kwave.utils.conversion import cast_to_type +from kwave.utils.data import get_smallest_possible_type +from kwave.utils.dotdictionary import dotdict +from kwave.utils.filters import smooth +from kwave.utils.interp import cart2grid +from kwave.utils.io import get_date_string +from kwave.utils.matlab import matlab_find, matlab_mask @dataclass @@ -773,7 +780,7 @@ def check_source(self, kgrid_dim) -> None: sensor.time_reversal_boundary_data(:, new_col_pos) = order_index; # reorder p0 based on the order_index - sensor.time_reversal_boundary_data = sortrows(sensor.time_reversal_boundary_data, new_col_pos); + sensor.time_reversal_boundary_data = sort_rows(sensor.time_reversal_boundary_data, new_col_pos); # remove the reordering data sensor.time_reversal_boundary_data = sensor.time_reversal_boundary_data(:, 1:new_col_pos - 1); @@ -1090,7 +1097,8 @@ def check_input_combinations(self, opt: SimulationOptions, user_medium_density_i # check input options for data streaming ***** if opt.stream_to_disk: if not self.use_sensor or self.time_rev: - raise ValueError('The optional input ''StreamToDisk'' is currently only compatible with forward simulations using a non-zero sensor mask.'); + raise ValueError( + 'The optional input ''StreamToDisk'' is currently only compatible with forward simulations using a non-zero sensor mask.') elif self.sensor.record is not None and self.sensor.record.ismember(self.record.flags[1:]).any(): raise ValueError('The optional input ''StreamToDisk'' is currently only compatible with sensor.record = {''p''} (the default).') @@ -1255,12 +1263,12 @@ def smooth_and_enlarge(self, source, k_dim, kgrid_N, opt: SimulationOptions) -> # smooth the sound speed distribution if required if opt.smooth_c0 and num_dim2(self.medium.sound_speed) == k_dim and self.medium.sound_speed.size > 1: print(' smoothing sound speed distribution...') - ev('medium.sound_speed = smooth(medium.sound_speed);') + self.medium.sound_speed = smooth(self.medium.sound_speed) # smooth the ambient density distribution if required if opt.smooth_rho0 and num_dim2(self.medium.density) == k_dim and self.medium.density.size > 1: print('smoothing density distribution...') - ev('medium.density = smooth(medium.density);') + self.medium.density = smooth(self.medium.density) def create_sensor_variables(self) -> None: """ diff --git a/kwave/kWaveSimulation_helper/create_absorption_variables.py b/kwave/kWaveSimulation_helper/create_absorption_variables.py index ea15187a..3951a893 100644 --- a/kwave/kWaveSimulation_helper/create_absorption_variables.py +++ b/kwave/kWaveSimulation_helper/create_absorption_variables.py @@ -1,8 +1,9 @@ import math + import numpy as np from kwave import kWaveGrid, kWaveMedium -from kwave.utils import db2neper +from kwave.utils.conversion import db2neper def create_absorption_variables(kgrid: kWaveGrid, medium: kWaveMedium, equation_of_state): diff --git a/kwave/kWaveSimulation_helper/create_storage_variables.py b/kwave/kWaveSimulation_helper/create_storage_variables.py index 9575464a..95ffe9e6 100644 --- a/kwave/kWaveSimulation_helper/create_storage_variables.py +++ b/kwave/kWaveSimulation_helper/create_storage_variables.py @@ -1,9 +1,10 @@ +import numpy as np from numpy.fft import ifftshift from kwave import SimulationOptions, kWaveGrid from kwave.data import Array -from kwave.utils import dotdict -import numpy as np +from kwave.utils.dotdictionary import dotdict + # Note from Farid: This function/file is very suspicios. I'm pretty sure that the implementation is not correct. # Full test-coverage is required for bug-fixes! diff --git a/kwave/kWaveSimulation_helper/data_cast.py b/kwave/kWaveSimulation_helper/data_cast.py index 17776e54..dd4d06fd 100644 --- a/kwave/kWaveSimulation_helper/data_cast.py +++ b/kwave/kWaveSimulation_helper/data_cast.py @@ -1,6 +1,6 @@ from kwave import kWaveMedium, kWaveGrid, SimulationOptions -from kwave.utils import cast_to_type -from kwave.utils import dotdict +from kwave.utils.conversion import cast_to_type +from kwave.utils.dotdictionary import dotdict def dataCast(data_cast, medium: kWaveMedium, kgrid: kWaveGrid, opt: SimulationOptions, values: dotdict, flags: dotdict): diff --git a/kwave/kWaveSimulation_helper/display_simulation_params.py b/kwave/kWaveSimulation_helper/display_simulation_params.py index 78736cee..46457b4b 100644 --- a/kwave/kWaveSimulation_helper/display_simulation_params.py +++ b/kwave/kWaveSimulation_helper/display_simulation_params.py @@ -1,7 +1,8 @@ -from kwave import kWaveGrid, kWaveMedium -from kwave.utils import scale_SI import numpy as np +from kwave import kWaveGrid, kWaveMedium +from kwave.utils.conversion import scale_SI + def display_simulation_params(kgrid: kWaveGrid, medium: kWaveMedium, elastic_code: bool): dt = kgrid.dt diff --git a/kwave/kWaveSimulation_helper/expand_grid_matrices.py b/kwave/kWaveSimulation_helper/expand_grid_matrices.py index cf4c2516..0fe1e245 100644 --- a/kwave/kWaveSimulation_helper/expand_grid_matrices.py +++ b/kwave/kWaveSimulation_helper/expand_grid_matrices.py @@ -1,8 +1,11 @@ +import numpy as np + from kwave import kWaveGrid, kWaveMedium, SimulationOptions, NotATransducer from kwave.data import Array -from kwave.utils import matlab_find, expand_matrix, get_smallest_possible_type -from kwave.utils import dotdict -import numpy as np +from kwave.utils.data import get_smallest_possible_type +from kwave.utils.dotdictionary import dotdict +from kwave.utils.matlab import matlab_find +from kwave.utils.matrix import expand_matrix def expand_grid_matrices( diff --git a/kwave/kWaveSimulation_helper/save_to_disk_func.py b/kwave/kWaveSimulation_helper/save_to_disk_func.py index 4e63aeb2..4c6d2623 100644 --- a/kwave/kWaveSimulation_helper/save_to_disk_func.py +++ b/kwave/kWaveSimulation_helper/save_to_disk_func.py @@ -1,11 +1,14 @@ import os +import numpy as np from scipy.io import savemat from kwave import kWaveMedium, kWaveGrid, SimulationOptions -from kwave.utils import scale_time, TicToc, num_dim2, write_matrix, write_attributes -from kwave.utils import dotdict -import numpy as np +from kwave.utils.checks import num_dim2 +from kwave.utils.conversion import scale_time +from kwave.utils.dotdictionary import dotdict +from kwave.utils.io import write_attributes, write_matrix +from kwave.utils.tictoc import TicToc def save_to_disk_func( diff --git a/kwave/kWaveSimulation_helper/scale_source_terms_func.py b/kwave/kWaveSimulation_helper/scale_source_terms_func.py index 898a7520..f9178c38 100644 --- a/kwave/kWaveSimulation_helper/scale_source_terms_func.py +++ b/kwave/kWaveSimulation_helper/scale_source_terms_func.py @@ -1,11 +1,12 @@ import math -import numpy as np from warnings import warn +import numpy as np + from kwave import kWaveGrid from kwave.ksource import kSource -from kwave.utils import matlab_mask -from kwave.utils import dotdict +from kwave.utils.dotdictionary import dotdict +from kwave.utils.matlab import matlab_mask def scale_source_terms_func( diff --git a/kwave/kgrid.py b/kwave/kgrid.py index c7ab1373..3abeaf03 100644 --- a/kwave/kgrid.py +++ b/kwave/kgrid.py @@ -1,11 +1,12 @@ import math +import sys from dataclasses import dataclass import numpy as np -import sys from kwave.data import Array from kwave.enums import DiscreteCosine, DiscreteSine +from kwave.utils.math import largest_prime_factor # default CFL number CFL_DEFAULT = 0.3 @@ -422,7 +423,6 @@ def highest_prime_factors(self, axisymmetric=None) -> np.ndarray: Vector of three elements """ # import statement place here in order to avoid circular dependencies - from kwave.utils import largest_prime_factor if axisymmetric is not None: if axisymmetric == 'WSWA': prime_facs = [largest_prime_factor(self.Nx), diff --git a/kwave/kmedium.py b/kwave/kmedium.py index 10ac2bf0..a19948f1 100644 --- a/kwave/kmedium.py +++ b/kwave/kmedium.py @@ -3,8 +3,7 @@ from warnings import warn import numpy as np -import kwave.utils.misc as util -import kwave.utils.checkutils +import kwave.utils.checks @dataclass diff --git a/kwave/ksensor.py b/kwave/ksensor.py index d8bb1305..abedf642 100644 --- a/kwave/ksensor.py +++ b/kwave/ksensor.py @@ -1,7 +1,8 @@ from dataclasses import dataclass + import numpy as np -from kwave.utils import expand_matrix +from kwave.utils.matrix import expand_matrix class kSensor(object): diff --git a/kwave/ksource.py b/kwave/ksource.py index 9410917a..6ed9d121 100644 --- a/kwave/ksource.py +++ b/kwave/ksource.py @@ -4,7 +4,7 @@ import numpy as np from kwave.kgrid import kWaveGrid -from kwave.utils import num_dim2, matlab_find +from kwave.utils.checks import num_dim2 @dataclass @@ -128,11 +128,11 @@ def validate(self, kgrid: kWaveGrid) -> None: else: # check the source labels are monotonic, and start from 1 - if eng.eval('(sum(p_unique(2:end) - p_unique(1:end-1)) != (numel(p_unique) - 1)) || (~any(p_unique == 1))'): - raise ValueError('If using a labelled source.p_mask, the source labels must be monotonically increasing and start from 1.') - + if (sum(p_unique[1:] - p_unique[:-1]) != len(p_unique) - 1) or (not any(p_unique == 1)): + raise ValueError( + 'If using a labelled source.p_mask, the source labels must be monotonically increasing and start from 1.') # make sure the correct number of input signals are given - if eng.eval('size(source.p, 1) != (numel(p_unique) - 1)'): + if np.size(self.p, 1) != (np.size(p_unique) - 1): raise ValueError('The number of time series in source.p must match the number of labelled source elements in source.p_mask.') # check for time varying velocity source input and set source flag diff --git a/kwave/kspaceFirstOrder.py b/kwave/kspaceFirstOrder.py index f24ff30a..b9e9380b 100644 --- a/kwave/kspaceFirstOrder.py +++ b/kwave/kspaceFirstOrder.py @@ -1,19 +1,13 @@ +import functools import os from operator import itemgetter from tempfile import gettempdir from warnings import warn -import numpy as np -from numpy.fft import ifftshift -from scipy.io import savemat - -from kwave.kgrid import * from kwave.ktransducer import * -from kwave.utils import dotdict -import math -import functools - -from kwave.utils import is_unix +from kwave.utils.checks import is_unix +from kwave.utils.dotdictionary import dotdict +from kwave.utils.io import get_date_string def kspaceFirstOrderG(func): diff --git a/kwave/kspaceFirstOrder2D.py b/kwave/kspaceFirstOrder2D.py index de6587f6..3c6c7463 100644 --- a/kwave/kspaceFirstOrder2D.py +++ b/kwave/kspaceFirstOrder2D.py @@ -1,11 +1,14 @@ -from numpy.fft import ifftshift import tempfile +from numpy.fft import ifftshift + from kwave.executor import Executor from kwave.kWaveSimulation import kWaveSimulation from kwave.kWaveSimulation_helper import retract_transducer_grid_size, save_to_disk_func from kwave.kspaceFirstOrder import * -from kwave.utils import * +from kwave.utils.interp import interpolate2d +from kwave.utils.pml import get_pml +from kwave.utils.tictoc import TicToc @kspaceFirstOrderG diff --git a/kwave/kspaceFirstOrder3D.py b/kwave/kspaceFirstOrder3D.py index 56187e8b..b26dc5cd 100644 --- a/kwave/kspaceFirstOrder3D.py +++ b/kwave/kspaceFirstOrder3D.py @@ -4,7 +4,9 @@ from kwave.kWaveSimulation import kWaveSimulation from kwave.kWaveSimulation_helper import retract_transducer_grid_size, save_to_disk_func from kwave.kspaceFirstOrder import * -from kwave.utils import * +from kwave.utils.interp import interpolate3d +from kwave.utils.pml import get_pml +from kwave.utils.tictoc import TicToc @kspaceFirstOrderG diff --git a/kwave/kspaceFirstOrderAS.py b/kwave/kspaceFirstOrderAS.py index 8ce553dc..6bd15baf 100644 --- a/kwave/kspaceFirstOrderAS.py +++ b/kwave/kspaceFirstOrderAS.py @@ -1,11 +1,17 @@ import tempfile +from numpy.fft import ifftshift + +from kwave.enums import DiscreteCosine from kwave.executor import Executor +from kwave.kWaveSimulation import kWaveSimulation from kwave.kWaveSimulation_helper import retract_transducer_grid_size, save_to_disk_func from kwave.kspaceFirstOrder import * -from kwave.kWaveSimulation import kWaveSimulation -from kwave.utils import * -from kwave.enums import DiscreteCosine +from kwave.utils.checks import num_dim2 +from kwave.utils.interp import interpolate2d +from kwave.utils.math import sinc +from kwave.utils.pml import get_pml +from kwave.utils.tictoc import TicToc @kspaceFirstOrderC() diff --git a/kwave/ktransducer.py b/kwave/ktransducer.py index 865a0973..c0395c09 100644 --- a/kwave/ktransducer.py +++ b/kwave/ktransducer.py @@ -1,6 +1,12 @@ +import numpy as np + from kwave.kgrid import kWaveGrid from kwave.ksensor import kSensor -from kwave.utils import * +from kwave.utils.checks import is_number +from kwave.utils.data import get_smallest_possible_type +from kwave.utils.matlab import matlab_find, matlab_mask, unflatten_matlab_mask +from kwave.utils.matrix import expand_matrix +from kwave.utils.signals import get_win # force value to be a positive integer @@ -519,7 +525,7 @@ def retract_grid(self, retract_size): @property def transmit_apodization_mask(self): """ - % convert the transmit apodization into the form of a element mask, + % convert the transmit wave apodization into the form of a element mask, % where the apodization values are placed at the grid points % belonging to the active transducer elements. These values are % then extracted in the correct order within diff --git a/kwave/options.py b/kwave/options.py index 6d0ad804..8f7cfea6 100644 --- a/kwave/options.py +++ b/kwave/options.py @@ -1,7 +1,9 @@ from dataclasses import dataclass + import numpy as np -from kwave.utils import get_h5_literals -from kwave.utils import get_optimal_pml_size + +from kwave.utils.io import get_h5_literals +from kwave.utils.pml import get_optimal_pml_size @dataclass diff --git a/kwave/reconstruction/beamform.py b/kwave/reconstruction/beamform.py index d10a30e9..c99289af 100644 --- a/kwave/reconstruction/beamform.py +++ b/kwave/reconstruction/beamform.py @@ -1,11 +1,18 @@ +import warnings +from typing import Tuple, Optional + import numpy as np -from scipy.signal import hilbert -from scipy.interpolate import interp1d -from uff import UFF, ChannelData +import scipy from matplotlib import pyplot as plt -import kwave.reconstruction.tools as tools +from scipy.interpolate import interp1d +from scipy.signal import hilbert +from uff import ChannelData from uff.position import Position -from kwave.reconstruction.shifted_transform import ShiftedTransform + +from .shifted_transform import ShiftedTransform +from .tools import make_time_vector, get_t0, get_origin_array, apodize +from ..utils.conversion import scale_time, cart2pol +from ..utils.tictoc import TicToc def beamform(channel_data: ChannelData): @@ -46,7 +53,7 @@ def beamform(channel_data: ChannelData): transmit_wave = event.transmit_setup.transmit_waves[0] # make time vector - time_vector = tools.make_time_vector(num_samples=number_samples, sampling_freq=sampling_freq, + time_vector = make_time_vector(num_samples=number_samples, sampling_freq=sampling_freq, time_offset=event.receive_setup.time_offset) # todo: make indexing 0 min and not 1 min @@ -57,15 +64,15 @@ def beamform(channel_data: ChannelData): expanding_aperture = pixel_positions[:, 2] / f_number # time zero delays for spherical waves - origin = tools.get_origin_array(channel_data, transmit_wave) - t0_point = tools.get_t0(transmit_wave) + origin = get_origin_array(channel_data, transmit_wave) + t0_point = get_t0(transmit_wave) # print(origin, t0_point) transmit_distance = np.sign(pixel_positions[:, 2] - origin[2]) * \ np.sqrt(np.sum((pixel_positions - origin) ** 2, axis=1)) + \ np.abs(1.2 * t0_point[0]) - # np.sqrt(np.sum((origin - t0_point) ** 2)) + # np.sqrt(np.sum((origin - t0_point) ** 2)) probe = channel_data.probes[probe - 1] # todo: why are element positions saved as transforms and not positions? @@ -87,7 +94,7 @@ def beamform(channel_data: ChannelData): pixel_element_lateral_distance = abs(pixel_positions[:, 0] - element_location[0]) # print(pixel_element_lateral_distance) - receive_apodization = tools.apodize(pixel_element_lateral_distance, expanding_aperture, apodization_window) + receive_apodization = apodize(pixel_element_lateral_distance, expanding_aperture, apodization_window) # receive distance receive_distance = np.sqrt(np.sum((pixel_positions - np.array(element_location)) ** 2, axis=1)) @@ -117,3 +124,146 @@ def beamform(channel_data: ChannelData): plt.title(channel_data.description) plt.colorbar() plt.show() + + +def focus(kgrid, input_signal, source_mask, focus_position, sound_speed): + """ + focus Create input signal based on source mask and focus position. + focus takes a single input signal and a source mask and creates an + input signal matrix (with one input signal for each source point). + The appropriate time delays required to focus the signals at a given + position in Cartesian space are automatically added based on the user + inputs for focus_position and sound_speed. + + Args: + kgrid: k-Wave grid object returned by kWaveGrid + input_signal: single time series input + source_mask: matrix specifying the positions of the time + varying source distribution (i.e., source.p_mask + or source.u_mask) + focus_position: position of the focus in Cartesian coordinates + sound_speed: scalar sound speed + + Returns: + input_signal_mat: matrix of time series following the source points + """ + + assert kgrid.t_array != 'auto', "kgrid.t_array must be defined." + if isinstance(sound_speed, int): + sound_speed = float(sound_speed) + + assert isinstance(sound_speed, float), "sound_speed must be a scalar." + + positions = [kgrid.x.flatten(), kgrid.y.flatten(), kgrid.z.flatten()] + + # filter_positions + positions = [position for position in positions if (position != np.nan).any()] + assert len(positions) == kgrid.dim + positions = np.array(positions) + + if isinstance(focus_position, list): + focus_position = np.array(focus_position) + assert isinstance(focus_position, np.ndarray) + + dist = np.linalg.norm(positions[:, source_mask.flatten() == 1] - focus_position[:, np.newaxis]) + + # distance to delays + delay = int(np.round(dist / (kgrid.dt * sound_speed))) + max_delay = np.max(delay) + rel_delay = -(delay - max_delay) + + signal_mat = np.zeros((rel_delay.size, input_signal.size + max_delay)) + + # for src_idx, delay in enumerate(rel_delay): + # signal_mat[src_idx, delay:max_delay - delay] = input_signal + # signal_mat[rel_delay, delay:max_delay - delay] = input_signal + + warnings.warn("This method is not fully migrated, might be depricated and is untested.", PendingDeprecationWarning) + return signal_mat + + +def scan_conversion( + scan_lines: np.ndarray, + steering_angles, + image_size: Tuple[float, float], + c0, + dt, + resolution: Optional[Tuple[int, int]] +) -> np.ndarray: + if resolution is None: + resolution = (256, 256) # in pixels + + x_resolution, y_resolution = resolution + + # assign the inputs + x, y = image_size + + # start the timer + TicToc.tic() + + # update command line status + print('Computing ultrasound scan conversion...') + + # extract a_line parameters + Nt = scan_lines.shape[1] + + # calculate radius variable based on the sound speed in the medium and the + # round trip distance + r = c0 * np.arange(1, Nt + 1) * dt / 2 # [m] + + # create regular Cartesian grid to remap to + pos_vec_y_new = np.linspace(0, 1, y_resolution) * y - y / 2 + pos_vec_x_new = np.linspace(0, 1, x_resolution) * x + [pos_mat_x_new, pos_mat_y_new] = np.array(np.meshgrid(pos_vec_x_new, pos_vec_y_new, indexing='ij')) + + # convert new points to polar coordinates + [th_cart, r_cart] = cart2pol(pos_mat_x_new, pos_mat_y_new) + + # TODO: move this import statement at the top of the file + # Not possible now due to cyclic dependencies + from kwave.utils.interp import interpolate2d_with_queries + + # below part has some modifications + # we flatten the _cart matrices and build queries + # then we get values at the query locations + # and reshape the values to the desired size + # These three steps can be accomplished in one step in Matlab + # However, we don't want to add custom logic to the `interpolate2D_with_queries` method. + + # Modifications -start + queries = np.array([r_cart.flatten(), th_cart.flatten()]).T + + b_mode = interpolate2d_with_queries( + [r, 2 * np.pi * steering_angles / 360], + scan_lines.T, + queries, + method='linear', + copy_nans=False + ) + image_size_points = (len(pos_vec_x_new), len(pos_vec_y_new)) + b_mode = b_mode.reshape(image_size_points) + # Modifications -end + + b_mode[np.isnan(b_mode)] = 0 + + # update command line status + print(f' completed in {scale_time(TicToc.toc())}') + + return b_mode + + +def envelope_detection(signal): + """ + envelopeDetection applies the Hilbert transform to extract the + envelope from an input vector x. If x is a matrix, the envelope along + the last axis. + + Args: + signal: + + Returns: + signal_envelope: + + """ + + return np.abs(scipy.signal.hilbert(signal)) diff --git a/kwave/reconstruction/converter.py b/kwave/reconstruction/converter.py index 1d5ce606..023e97d9 100644 --- a/kwave/reconstruction/converter.py +++ b/kwave/reconstruction/converter.py @@ -1,9 +1,10 @@ -import scipy -import uff +import time + import numpy as np -from kwave import NotATransducer +import uff from uff.linear_array import LinearArray -import time + +from kwave import NotATransducer def build_channel_data(sensor_data: np.ndarray, diff --git a/kwave/utils/__init__.py b/kwave/utils/__init__.py index d4c40327..e69de29b 100644 --- a/kwave/utils/__init__.py +++ b/kwave/utils/__init__.py @@ -1,16 +0,0 @@ -from kwave.utils.dotdictionary import * -from kwave.utils.checkutils import * -from kwave.utils.colormap import * -from kwave.utils.conversionutils import * -from kwave.utils.datautils import * -from kwave.utils.filterutils import * -from kwave.utils.interputils import * -from kwave.utils.ioutils import * -from kwave.utils.kutils import * -from kwave.utils.maputils import * -from kwave.utils.matrixutils import * -from kwave.utils.misc import * -from kwave.utils.pmlutils import * -from kwave.utils.tictoc import * -from kwave.utils.mathutils import * -from kwave.utils.osutils import * diff --git a/kwave/utils/abcdata.py b/kwave/utils/abcdata.py deleted file mode 100644 index 1e8af149..00000000 --- a/kwave/utils/abcdata.py +++ /dev/null @@ -1,10 +0,0 @@ -from abc import ABC -from dataclasses import dataclass - - -@dataclass -class AbstractDataclass(ABC): - def __new__(cls, *args, **kwargs): - if cls == AbstractDataclass or cls.__bases__[0] == AbstractDataclass: - raise TypeError("Cannot instantiate abstract class.") - return super().__new__(cls) diff --git a/kwave/utils/checks.py b/kwave/utils/checks.py new file mode 100644 index 00000000..a4adcc0a --- /dev/null +++ b/kwave/utils/checks.py @@ -0,0 +1,330 @@ +import platform +from copy import deepcopy +from typing import List + +import numpy as np + +from .conversion import db2neper +from .math import sinc, primefactors + + +def enforce_fields(dictionary, *fields): + """ + Ensures that the given dictionary contains the specified fields. + + Args: + dictionary: A dictionary to check. + *fields: The fields that must be present in the dictionary. + + Raises: + AssertionError: If any of the specified fields are not in the dictionary. + """ + for f in fields: + assert f in dictionary.keys(), [f'The field {f} must be defined in the given dictionary'] + + +def enforce_fields_obj(obj, *fields): + """ + Enforces that certain fields are not None in the given object. + + Args: + obj: Object to check the fields of. + *fields: List of field names to check. + + Raises: + AssertionError: If any of the given fields are None in the given object. + """ + for f in fields: + assert getattr(obj, f) is not None, f'The field {f} must be not None in the given object' + + +def check_field_names(dictionary, *fields): + """ + This method checks if the keys of the given dictionary are valid fields. + + Args: + dictionary: A dictionary where the keys will be checked for validity. + *fields: A list of valid field names. + + Returns: + None + + Raises: + AssertionError: If any of the keys in the dictionary are not in the list of valid fields. + """ + for k in dictionary.keys(): + assert k in fields, f'The field {k} is not a valid field for the given dictionary' + + +def num_dim(x): + """ + Returns the number of dimensions in x, after collapsing any singleton dimensions. + + Args: + x (np.ndarray): The input array. + + Returns: + int: The number of dimensions in x. + """ + return len(x.squeeze().shape) + + +def num_dim2(x: np.ndarray): + """ + Get the number of dimensions of an array after collapsing singleton dimensions. + + Args: + x (np.ndarray): The input array. + + Returns: + int: The number of dimensions of the array after collapsing singleton dimensions. + """ + sz = np.squeeze(x).shape + + if len(sz) > 2: + return len(sz) + else: + return np.sum(np.array(sz) > 1) + + +def check_str_eq(value, target: str): + """ + This method checks whether the given value is a string and is equal to the target string. It is useful to avoid FutureWarnings when value is not a string. + + Args: + value: The value to check. + target: The target string to compare with. + + Returns: + bool: True if the value is a string and is equal to the target, False otherwise. + """ + return isinstance(value, str) and value == target + + +def check_str_in(value, target: List[str]) -> bool: + """ + Check if value is in the given list only if the value is string. + Helps to avoid FutureWarnings when value is not a string. + Added by @Farid + + Args: + value: The value to check for inclusion in `target` + target: A list of strings to check for the presence of `value` + + Returns: + True if `value` is a string and is present in `target`, otherwise False + """ + return isinstance(value, str) and value in target + + +def is_number(value): + """ + Check if the given value is a numeric type. + + Args: + value: The value to check. + + Returns: + bool: True if the value is numeric, False otherwise. + """ + if value is None: + return False + if isinstance(value, (int, float)): + return True + if isinstance(value, str): + return False + if value.dtype in [np.float32, np.float64]: + return True + return np.issubdtype(np.array(value), np.number) + + +def is_unix(): + """ + Check whether the current platform is a Unix-like system. + + Args: + None + + Returns: + bool: True if the current platform is a Unix-like system, False otherwise. + + """ + return platform.system() in ['Linux', 'Darwin'] + + +def check_stability(kgrid, medium): + """ + checkStability calculates the maximum time step for which the k-space + propagation models kspaceFirstOrder1D, kspaceFirstOrder2D and + kspaceFirstOrder3D are stable. These models are unconditionally + stable when the reference sound speed is equal to or greater than the + maximum sound speed in the medium and there is no absorption. + However, when the reference sound speed is less than the maximum + sound speed the model is only stable for sufficiently small time + steps. The criterion is more stringent (the time step is smaller) in + the absorbing case. + + The time steps given are accurate when the medium properties are + homogeneous. For a heterogeneous media they give a useful, but not + exact, estimate. + Args: + kgrid: k-Wave grid object return by kWaveGrid + medium: structure containing the medium properties + + Returns: the maximum time step for which the models are stable. + This is set to Inf when the model is unconditionally stable. + """ + # why? : this function was migrated from Matlab. + # Matlab would treat the 'medium' as a "pass by value" argument. + # In python argument is passed by reference and changes in this function will cause original data to be changed. + # Instead of making significant changes to the function, we make a deep copy of the argument + medium = deepcopy(medium) + + # define literals + FIXED_POINT_ACCURACY = 1e-12 + + # find the maximum wavenumber + kmax = kgrid.k.max() + + # calculate the reference sound speed for the fluid code, using the + # maximum by default which ensures the model is unconditionally stable + reductions = { + 'min': np.min, + 'max': np.max, + 'mean': np.mean + } + + if medium.sound_speed_ref is not None: + ss_ref = medium.sound_speed_ref + if np.isscalar(ss_ref): + c_ref = ss_ref + else: + try: + c_ref = reductions[ss_ref](medium.sound_speed) + except KeyError: + raise NotImplementedError('Unknown input for medium.sound_speed_ref.') + else: + c_ref = reductions['max'](medium.sound_speed) + + # calculate the timesteps required for stability + if medium.alpha_coeff is None or np.all(medium.alpha_coeff == 0): + + # ===================================================================== + # NON-ABSORBING CASE + # ===================================================================== + + medium.sound_speed = np.atleast_1d(medium.sound_speed) + if c_ref >= medium.sound_speed.max(): + # set the timestep to Inf when the model is unconditionally stable + dt_stability_limit = float('inf') + + else: + # set the timestep required for stability when c_ref~=max(medium.sound_speed(:)) + dt_stability_limit = 2 / (c_ref * kmax) * np.asin(c_ref / medium.sound_speed.max()) + + else: + + # ===================================================================== + # ABSORBING CASE + # ===================================================================== + + # convert the absorption coefficient to nepers.(rad/s)^-y.m^-1 + medium.alpha_coeff = db2neper(medium.alpha_coeff, medium.alpha_power) + + # calculate the absorption constant + if medium.alpha_mode == 'no_absorption': + absorb_tau = -2 * medium.alpha_coeff * medium.sound_speed ** (medium.alpha_power - 1) + else: + absorb_tau = np.array([0]) + + # calculate the dispersion constant + if medium.alpha_mode == 'no_dispersion': + absorb_eta = 2 * medium.alpha_coeff * medium.sound_speed ** medium.alpha_power * np.tan( + np.pi * medium.alpha_power / 2) + else: + absorb_eta = np.array([0]) + + # estimate the timestep required for stability in the absorbing case by + # assuming the k-space correction factor, kappa = 1 (note that + # absorb_tau and absorb_eta are negative quantities) + medium.sound_speed = np.atleast_1d(medium.sound_speed) + + temp1 = medium.sound_speed.max() * absorb_tau.min() * kmax ** (medium.alpha_power - 1) + temp2 = 1 - absorb_eta.min() * kmax ** (medium.alpha_power - 1) + dt_estimate = (temp1 + np.sqrt(temp1 ** 2 + 4 * temp2)) / (temp2 * kmax * medium.sound_speed.max()) + + # use a fixed point iteration to find the correct timestep, assuming + # now that kappa = kappa(dt), using the previous estimate as a starting + # point + + # first define the function to iterate + def kappa(dt): + return sinc(c_ref * kmax * dt / 2) + + def temp3(dt): + return medium.sound_speed.max() * absorb_tau.min() * kappa(dt) * kmax ** (medium.alpha_power - 1) + + def func_to_solve(dt): + return (temp3(dt) + np.sqrt((temp3(dt)) ** 2 + 4 * temp2)) / ( + temp2 * kmax * kappa(dt) * medium.sound_speed.max()) + + # run the fixed point iteration + dt_stability_limit = dt_estimate + dt_old = 0 + while abs(dt_stability_limit - dt_old) > FIXED_POINT_ACCURACY: + dt_old = dt_stability_limit + dt_stability_limit = func_to_solve(dt_stability_limit) + + return dt_stability_limit + + +def check_factors(min_number: int, max_number: int) -> None: + """ + Return the maximum prime factor for a range of numbers. + + checkFactors loops through the given range of numbers and finds the + numbers with the smallest maximum prime factors. This allows suitable + grid sizes to be selected to maximise the speed of the FFT (this is + fastest for FFT lengths with small prime factors). The output is + printed to the command line. + + Args: + min_number: integer specifying the lower bound of values to test + max_number: integer specifying the upper bound of values to test + + Returns: + None + """ + + # compute the factors and maximum prime factors for each number in the range + factors = {} + for n in range(min_number, max_number): + factors[n] = { + 'factors': primefactors(n), + 'max_prime_factor': max(primefactors(n)) + } + + # print the numbers that match each maximum prime factor + for factor in [2, 3, 5, 7]: + print(f'Numbers with a maximum prime factor of {factor}:') + for n in range(min_number, max_number): + if factors[n]['max_prime_factor'] == factor: + print(n) + + +def check_divisible(number: float, divider: float) -> bool: + """ + Checks whether number is divisible by divider without any remainder + Why do we need such a function? -> Because due to floating point precision we + experience rounding errors while using standard modulo operator with floating point numbers + Args: + number: Number that's supposed to be divided + divider: Divider that should devide the number + + Returns: + bool: True if number is divisible by divider, False otherwise + """ + result = number / divider + after_decimal = result % 1 + return after_decimal == 0 + diff --git a/kwave/utils/checkutils.py b/kwave/utils/checkutils.py deleted file mode 100644 index a745b807..00000000 --- a/kwave/utils/checkutils.py +++ /dev/null @@ -1,78 +0,0 @@ -from typing import List - -import numpy as np - - -def enforce_fields(dictionary, *fields): - # from kwave - for f in fields: - assert f in dictionary.keys(), [f'The field {f} must be defined in the given dictionary'] - - -def enforce_fields_obj(obj, *fields): - # from kwave - for f in fields: - assert getattr(obj, f) is not None, f'The field {f} must be not None in the given object' - - -def check_field_names(dictionary, *fields): - # from kwave - for k in dictionary.keys(): - assert k in fields, f'The field {k} is not a valid field for the given dictionary' - - -def num_dim(x): - # get the size collapsing any singleton dimensions - return len(x.squeeze().shape) - - -def num_dim2(x: np.ndarray): - # get the size collapsing any singleton dimensions - sz = np.squeeze(x).shape - - if len(sz) > 2: - return len(sz) - else: - return np.sum(np.array(sz) > 1) - - -def check_str_eq(value, target: str): - """ - String equality check only if the value is string. Helps to avoid FutureWarnings when value is not a string. - Added by @Farid - Args: - value: - target: - - Returns: - - """ - return isinstance(value, str) and value == target - - -def check_str_in(value, target: List[str]): - """ - Check if value is in the given list only if the value is string. - Helps to avoid FutureWarnings when value is not a string. - Added by @Farid - Args: - value: - target: - - Returns: - - """ - # added by Farid - return isinstance(value, str) and value in target - - -def is_number(value): - if value is None: - return False - if isinstance(value, (int, float)): - return True - if isinstance(value, str): - return False - if value.dtype in [np.float32, np.float64]: - return True - return np.issubdtype(np.array(value), np.number) \ No newline at end of file diff --git a/kwave/utils/colormap.py b/kwave/utils/colormap.py index f00b8de2..55ffb38f 100644 --- a/kwave/utils/colormap.py +++ b/kwave/utils/colormap.py @@ -1,19 +1,21 @@ +from typing import Optional + import numpy as np -def get_color_map(num_colors=None): +def get_color_map(num_colors: Optional[int] = None) -> np.ndarray: """ - DESCRIPTION: - getColorMap returns the default color map used for display and - visualisation across the k-Wave Toolbox. Zero values are displayed as - white, positive values are displayed as yellow through red to black, - and negative values are displayed as light to dark blue-greys. If no - value for num_colors is provided, cm will have 256 colors. + Returns the default color map used for display and visualisation across + the k-Wave Toolbox. Zero values are displayed as white, positive values + are displayed as yellow through red to black, and negative values are + displayed as light to dark blue-greys. If no value for `num_colors` is + provided, `cm` will have 256 colors. + Args: - num_colors: number of colors in the color map (default is 256) + num_colors: The number of colors in the color map (default is 256). Returns: - cm: three column color map matrix which can be applied using colormap + A three-column color map matrix which can be applied using colormap. """ if num_colors is None: neg_pad = 48 @@ -29,20 +31,17 @@ def get_color_map(num_colors=None): return np.vstack([neg, pos]) -def hot(m): +def hot(m: int) -> np.ndarray: """ - %HOT Red-yellow-white color map inspired by black body radiation - % HOT(M) returns an M-by-3 matrix containing a "hot" colormap. - % HOT, by itself, is the same length as the current figure's - % colormap. If no figure exists, MATLAB uses the length of the - % default colormap. - Args: - m: + Generate a hot colormap of length m. + The colormap consists of a progression from black to red, yellow, and white. + Args: + m: The length of the colormap. Returns: - + An m-by-3 array containing the hot colormap. """ - n = int(np.fix(3/8 * m)) + n = int(np.fix(3 / 8 * m)) r = np.concatenate([np.arange(1, n + 1) / n, np.ones(m-n)]) g = np.concatenate([np.zeros(n), np.arange(1, n + 1) / n, np.ones(m-2*n)]) @@ -51,11 +50,30 @@ def hot(m): return np.hstack([r[:, None], g[:, None], b[:, None]]) -def bone(m): +def bone(m: int) -> np.ndarray: + """ + Returns an m-by-3 matrix containing a "bone" colormap. + + Args: + m: The number of rows in the colormap. + + Returns: + An m-by-3 matrix containing the colormap. + """ return (7 * gray(m) + np.fliplr(hot(m))) / 8 -def gray(m): - g = np.arange(m) / max(m-1, 1) +def gray(m: int) -> np.ndarray: + """ + Returns an M-by-3 matrix containing a grayscale colormap. + + Args: + m: The length of the colormap. + + Returns: + An M-by-3 matrix containing the grayscale colormap. + """ + + g = np.arange(m) / max(m - 1, 1) g = g[:, None] - return np.hstack([g, g, g]) \ No newline at end of file + return np.hstack([g, g, g]) diff --git a/kwave/utils/conversionutils.py b/kwave/utils/conversion.py similarity index 51% rename from kwave/utils/conversionutils.py rename to kwave/utils/conversion.py index 406b2bb1..40501a1a 100644 --- a/kwave/utils/conversionutils.py +++ b/kwave/utils/conversion.py @@ -1,11 +1,11 @@ import math from math import floor -from typing import Optional, Tuple +from typing import Tuple, Union import numpy as np -from scipy.interpolate import interp1d +from numpy import ndarray -from kwave.utils.tictoc import TicToc +from kwave import kWaveGrid def scale_time(seconds): @@ -137,28 +137,16 @@ def scale_SI(x): return x_sc, scale, prefix, prefix_fullname -def db2neper(alpha, y=1): +def db2neper(alpha: float, y: int = 1) -> float: """ - DB2NEPER Convert decibels to nepers. + Convert decibels to nepers. - DESCRIPTION: - db2neper converts an attenuation coefficient in units of - dB / (MHz ^ y cm) to units of Nepers / ((rad / s) ^ y m). + Args: + alpha: Attenuation in dB / (MHz ^ y cm). + y: Power law exponent (default=1). - USAGE: - alpha = db2neper(alpha) - alpha = db2neper(alpha, y) - - INPUTS: - alpha - attenuation in dB / (MHz ^ y cm) - - OPTIONAL INPUTS: - y - power law exponent(default=1) - - OUTPUTS: - alpha - attenuation in Nepers / ((rad / s) ^ y m) - - set default y value if not given by user + Returns: + Attenuation in Nepers / ((rad / s) ^ y m). """ # calculate conversion @@ -166,27 +154,16 @@ def db2neper(alpha, y=1): return alpha -def neper2db(alpha, y=1): +def neper2db(alpha: float, y: int = 1) -> float: """ - NEPER2DB Convert nepers to decibels. - - DESCRIPTION: - neper2db converts an attenuation coefficient in units of - Nepers / ((rad / s) ^ y m) to units of dB / (MHz ^ y cm). + Converts an attenuation coefficient in units of Nepers / ((rad / s) ^ y m) to units of dB / (MHz ^ y cm). - USAGE: - alpha = neper2db(alpha) - alpha = neper2db(alpha, y) - - INPUTS: - alpha - attenuation in Nepers / ((rad / s) ^ y m) - - OPTIONAL INPUTS: - y - power law exponent(default=1) - - OUTPUTS: - alpha - attenuation in dB / (MHz ^ y cm) + Args: + alpha: Attenuation in Nepers / ((rad / s) ^ y m) + y: Power law exponent (default=1) + Returns: + Attenuation in dB / (MHz ^ y cm) """ # calculate conversion @@ -207,110 +184,74 @@ def cast_to_type(data, matlab_type: str): return data.astype(type_map[matlab_type]) -def scan_conversion( - scan_lines: np.ndarray, - steering_angles, - image_size: Tuple[float, float], - c0, - dt, - resolution: Optional[Tuple[int, int]] -) -> np.ndarray: - - if resolution is None: - resolution = (256, 256) # in pixels - - x_resolution, y_resolution = resolution - - # assign the inputs - x, y = image_size - - # start the timer - TicToc.tic() - - # update command line status - print('Computing ultrasound scan conversion...') - - # extract a_line parameters - Nt = scan_lines.shape[1] - - # calculate radius variable based on the sound speed in the medium and the - # round trip distance - r = c0 * np.arange(1, Nt + 1) * dt / 2 # [m] - - # create regular Cartesian grid to remap to - pos_vec_y_new = np.linspace(0, 1, y_resolution) * y - y / 2 - pos_vec_x_new = np.linspace(0, 1, x_resolution) * x - [pos_mat_x_new, pos_mat_y_new] = np.array(np.meshgrid(pos_vec_x_new, pos_vec_y_new, indexing='ij')) - - # convert new points to polar coordinates - [th_cart, r_cart] = cart2pol(pos_mat_x_new, pos_mat_y_new) - - # TODO: move this import statement at the top of the file - # Not possible now due to cyclic dependencies - from kwave.utils.interputils import interpolate2d_with_queries - - # below part has some modifications - # we flatten the _cart matrices and build queries - # then we get values at the query locations - # and reshape the values to the desired size - # These three steps can be accomplished in one step in Matlab - # However, we don't want to add custom logic to the `interpolate2D_with_queries` method. - - # Modifications -start - queries = np.array([r_cart.flatten(), th_cart.flatten()]).T - - b_mode = interpolate2d_with_queries( - [r, 2 * np.pi * steering_angles / 360], - scan_lines.T, - queries, - method='linear', - copy_nans=False - ) - image_size_points = (len(pos_vec_x_new), len(pos_vec_y_new)) - b_mode = b_mode.reshape(image_size_points) - # Modifications -end - - b_mode[np.isnan(b_mode)] = 0 - - # update command line status - print(f' completed in {scale_time(TicToc.toc())}') - - return b_mode +def cart2pol(x, y): + """ + Convert from cartesian to polar coordinates. + Args: + x: The x-coordinate of the point. + y: The y-coordinate of the point. -def cart2pol(x, y): - rho = np.sqrt(x**2 + y**2) + Returns: + A tuple containing the polar coordinates of the point. + """ + rho = np.sqrt(x ** 2 + y ** 2) phi = np.arctan2(y, x) return phi, rho -def revolve2D(mat2D): - # start timer - TicToc.tic() - - # update command line status - print('Revolving 2D matrix to form a 3D matrix...') - - # get size of matrix - m, n = mat2D.shape - - # create the reference axis for the 2D image - r_axis_one_sided = np.arange(0, n) - r_axis_two_sided = np.arange(-(n-1), n) +def grid2cart(input_kgrid: kWaveGrid, grid_selection: ndarray) -> Tuple[ndarray, ndarray]: + """ + Returns the Cartesian coordinates of the non-zero points of a binary grid. - # compute the distance from every pixel in the z-y cross-section of the 3D - # matrix to the rotation axis - z, y = np.meshgrid(r_axis_two_sided, r_axis_two_sided) - r = np.sqrt(y**2 + z**2) + Args: + input_kgrid: k-Wave grid object returned by kWaveGrid + grid_selection: binary grid with the same dimensions as the k-Wave grid kgrid - # create empty image matrix - mat3D = np.zeros((m, 2 * n - 1, 2 * n - 1)) + Returns: + cart_data: 1 x N, 2 x N, or 3 x N (for 1, 2, and 3 dimensions) array of Cartesian sensor points + order_index: returns a list of indices of the returned cart_data coordinates. + Raises: + ValueError: when input_kgrid.dim is not in [1, 2, 3] + """ + grid_data = np.array((grid_selection != 0), dtype=bool) + cart_data = np.zeros((input_kgrid.dim, np.sum(grid_data))) + + if input_kgrid.dim > 0: + cart_data[0, :] = input_kgrid.x[grid_data] + if input_kgrid.dim > 1: + cart_data[1, :] = input_kgrid.y[grid_data] + if input_kgrid.dim > 2: + cart_data[2, :] = input_kgrid.z[grid_data] + if 0 <= input_kgrid.dim > 3: + raise ValueError("kGrid with unsupported size passed.") + + order_index = np.argwhere(grid_data.squeeze() != 0) + return cart_data.squeeze(), order_index + + +def freq2wavenumber(N: int, k_max: float, filter_cutoff: float, c: float, k_dim: Union[int, Tuple[int]]) -> Tuple[ + int, float]: + """Convert the given frequency and maximum wavenumber to a wavenumber cutoff and filter size. + + Args: + N: The size of the grid. + k_max: The maximum wavenumber. + filter_cutoff: The frequency to convert to a wavenumber cutoff. + c: The speed of sound. + k_dim: The dimensions of the wavenumber grid. + + Returns: + A tuple containing the calculated filter size and wavenumber cutoff. + """ + k_cutoff = 2 * np.pi * filter_cutoff / c - # loop through each cross section and create 3D matrix - for x_index in range(m): - interp = interp1d(x=r_axis_one_sided, y=mat2D[x_index, :], kind='linear', bounds_error=False, fill_value=0) - mat3D[x_index, :, :] = interp(r) + # set the alpha_filter size + filter_size = round(N * k_cutoff / k_dim[-1]) - # update command line status - print(f' completed in {scale_time(TicToc.toc())}s') - return mat3D + # check the alpha_filter size + if filter_size > N: + # set the alpha_filter size to be the same as the grid size + filter_size = N + filter_cutoff = k_max * c / (2 * np.pi) + return filter_size, filter_cutoff diff --git a/kwave/utils/datautils.py b/kwave/utils/data.py similarity index 59% rename from kwave/utils/datautils.py rename to kwave/utils/data.py index 39be9d23..5a0f3adf 100644 --- a/kwave/utils/datautils.py +++ b/kwave/utils/data.py @@ -14,4 +14,12 @@ def get_smallest_possible_type(max_array_val, target_type_group, default=None): def intmax(dtype: str): - return np.iinfo(getattr(np, dtype)).max \ No newline at end of file + """ + Returns the maximum value for the given integer type. + + :param dtype: The integer type. + :type dtype: str + :return: The maximum value for the given integer type. + :rtype: int + """ + return np.iinfo(getattr(np, dtype)).max diff --git a/kwave/utils/dotdictionary.py b/kwave/utils/dotdictionary.py index 131a65a3..193effd6 100644 --- a/kwave/utils/dotdictionary.py +++ b/kwave/utils/dotdictionary.py @@ -1,20 +1,48 @@ +from typing import Any + + class dotdict(dict): """ A dictionary supporting dot notation. + + This class extends the built-in `dict` type by adding support for accessing + items using dot notation (e.g. `dotdict.a.b.c`) instead of square bracket + notation (e.g. `dotdict['a']['b']['c']`). The class also provides a + `lookup` method for looking up a value in a nested dictionary structure + using a dot-separated path (e.g. "a.b.c"). + + Examples: + >>> d = dotdict({'a': {'b': {'c': 1}}}) + >>> d.a.b.c + 1 + >>> d.lookup('a.b.c') + 1 + """ + __getattr__ = dict.get __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) for k, v in self.items(): if isinstance(v, dict): self[k] = dotdict(v) - def lookup(self, dotkey): + def lookup(self, dotkey: str) -> Any: """ - Lookup value in a nested structure with a single key, e.g. "a.b.c" + Look up a value in a nested dictionary structure using a dot-separated path. + + Args: + dotkey: A dot-separated path to the value, e.g. "a.b.c". + + Returns: + The value at the specified path. + + Raises: + KeyError: If the specified path does not exist in the dictionary. + """ path = list(reversed(dotkey.split("."))) v = self diff --git a/kwave/utils/filterutils.py b/kwave/utils/filters.py similarity index 83% rename from kwave/utils/filterutils.py rename to kwave/utils/filters.py index 12113313..bf466af3 100644 --- a/kwave/utils/filterutils.py +++ b/kwave/utils/filters.py @@ -1,18 +1,31 @@ +import math + import numpy as np -from kwave.utils.kutils import get_win -from kwave.utils.misc import find_closest, sinc import scipy +from scipy.fftpack import fft, ifft, ifftshift, fftshift from scipy.signal import lfilter -from scipy.fftpack import fft, ifft, ifftshift, fftshift, fftn, ifftn -import math -from math import pi -from kwave.utils.conversionutils import scale_SI -from kwave.utils.checkutils import num_dim, num_dim2 +from .checks import num_dim, num_dim2, is_number +from .conversion import scale_SI +from .math import find_closest, sinc +from .signals import get_win # Compute the next highest power of 2 of a 32–bit number `n` def next_pow2(n): + """ + Calculate the next power of 2 that is greater than or equal to `n`. + + This function takes a positive integer `n` and returns the smallest power of 2 that is greater + than or equal to `n`. + + Args: + n: The number to find the next power of 2 for. + + Returns: + The smallest power of 2 that is greater than or equal to `n`. + """ + # decrement `n` (to handle cases when `n` itself is a power of 2) n = n - 1 @@ -28,10 +41,18 @@ def next_pow2(n): def single_sided_correction(func_fft, fft_len, dim): - """ - correct the single - sided magnitude by multiplying the symmetric points by - 2(the DC and Nyquist components are unique and are not multiplied by 2 - and the Nyquist component only exists for even numbered FFT lengths) + """Correct the single-sided magnitude by multiplying the symmetric points by 2. + + The DC and Nyquist components are unique and are not multiplied by 2. + The Nyquist component only exists for even numbered FFT lengths. + + Args: + func_fft: The FFT of the function to be corrected. + fft_len: The length of the FFT. + dim: The number of dimensions of `func_fft`. + + Returns: + The corrected FFT of the function. """ if fft_len % 2: @@ -61,7 +82,6 @@ def single_sided_correction(func_fft, fft_len, dim): def spect(func, Fs, dim='auto', fft_len=0, power_two=False, unwrap=False, window='Rectangular'): """ - Args: func: signal to analyse Fs: sampling frequency [Hz] @@ -82,6 +102,9 @@ def spect(func, Fs, dim='auto', fft_len=0, power_two=False, unwrap=False, window func_as: single-sided amplitude spectrum func_ps: single-sided phase spectrum + Raises: + ValueError: if input signal is scalar or has more than 4 dimensions. + """ # check the size of the input @@ -161,26 +184,24 @@ def spect(func, Fs, dim='auto', fft_len=0, power_two=False, unwrap=False, window def extract_amp_phase(data, Fs, source_freq, dim='auto', fft_padding=3, window='Hanning'): - """ - extract_amp_phase extracts the amplitude and phase information at a - specified frequency from a vector or matrix of time series data. By - default the time dimension is set to the highest non-singleton - dimension. The amplitude and phase are extracted from the frequency - spectrum, which is calculated using a windowed and zero padded FFT. - The values are extracted at the frequency closest to source_freq. + """Extract the amplitude and phase information at a specified frequency from a vector or matrix of time series data. - Args: + The amplitude and phase are extracted from the frequency spectrum, which is calculated using a windowed and zero + padded FFT. The values are extracted at the frequency closest to source_freq. By default, the time dimension is set + to the highest non-singleton dimension. - data: matrix of time signals [s] - Fs: sampling frequency [Hz] - source_freq: frequency at which the amplitude and phase should be - extracted [Hz] - dim: - fft_padding: - window: + Args: + data: matrix of time signals [s] + Fs: sampling frequency [Hz] + source_freq: frequency at which the amplitude and phase should be extracted [Hz] + dim: the time dimension of the input data. If 'auto', the highest non-singleton dimension is used. + fft_padding: the amount of zero padding to apply to the FFT. + window: the windowing function to use for the FFT. Returns: - + amp: the extracted amplitude values + phase: the extracted phase values + f: the frequencies of the FFT spectrum """ # check for the dim input @@ -226,99 +247,11 @@ def extract_amp_phase(data, Fs, source_freq, dim='auto', fft_padding=3, window=' amp = func_as[:, :, :, f_index] phase = func_ps[:, :, :, f_index] else: - raise ValueError('dim must be 0, 1, 2, or 3'); + raise ValueError('dim must be 0, 1, 2, or 3') return amp.squeeze(), phase.squeeze(), f[f_index] -def create_cw_signals(t_array, freq, amp, phase, ramp_length=4): - """ - create_cw_signals generates a series of continuous wave (CW) signals - based on the 1D or 2D input matrices amp and phase, where each signal - is given by: - - amp(i, j) .* sin(2 .* pi .* freq .* t_array + phase(i, j)); - - To avoid startup transients, a cosine tapered up-ramp is applied to - the beginning of the signal. By default, the length of this ramp is - four periods of the wave. The up-ramp can be turned off by setting - the ramp_length to 0. - - Example: - - # define sampling parameters - f = 5e6 - T = 1/f - Fs = 100e6 - dt = 1/Fs - t_array = np.arange(0, 10*T, dt) - - # define amplitude and phase - amp = get_win(9, 'Gaussian') - phase = np.arange(0, 2*pi, 9).T - - # create signals and plot - cw_signal = create_cw_signals(t_array, f, amp, phase) - - Args: - t_array: - freq: - amp: - phase: - ramp_length: - - Returns: - cw_signals: - - """ - if len(phase) == 1: - phase = phase * np.ones(amp.shape) - - N1, N2 = amp.T.shape - - cw_signals = np.zeros([N1, N2, len(t_array)]) - - for idx1 in range(N1 - 1): - for idx2 in range(N2 - 1): - cw_signals[idx1, idx2, :] = amp[idx1, idx2] * np.sin(2 * pi * freq * t_array + phase[idx1, idx2]) - - if ramp_length != 0: - # get period and time-step - period = 1 / freq - dt = t_array[1] - t_array[0] - - # create ramp x-axis between 0 and pi - ramp_length_points = int(np.round(ramp_length * period / dt)) - ramp_axis = np.arange(0, pi, pi / (ramp_length_points)) - - # create ramp using a shifted cosine - ramp = (-np.cos(ramp_axis) + 1) * 0.5 - ramp = np.reshape(ramp, (1, 1, -1)) - - # apply ramp to all signals simultaneously - - cw_signals[:, :, :ramp_length_points] *= ramp - - return np.squeeze(cw_signals) - - -def envelope_detection(signal): - """ - envelopeDetection applies the Hilbert transform to extract the - envelope from an input vector x. If x is a matrix, the envelope along - the last axis. - - Args: - signal: - - Returns: - signal_envelope: - - """ - - return np.abs(scipy.signal.hilbert(signal)) - - def brenner_sharpness(im): ndim = len(np.squeeze(im).shape) @@ -462,6 +395,11 @@ def gaussian(x, magnitude=None, mean=0, variance=1): return gauss_distr # return magnitude * norm.pdf(x, loc=mean, scale=variance) + """ # Former impl. form Farid + if magnitude is None: + magnitude = np.sqrt(2 * np.pi * variance) + return magnitude * np.exp(-(x - mean) ** 2 / (2 * variance)) + """ def gaussian_filter(signal, Fs, frequency, bandwidth): @@ -715,7 +653,7 @@ def apply_filter(signal, Fs, cutoff_f, filter_type, zero_phase=False, transition return filtered_signal[np.newaxis] -def smooth(A, restore_max=False, window_type="Blackman"): +def smooth(a, restore_max=False, window_type='Blackman'): """ Smooth a matrix. @@ -725,40 +663,46 @@ def smooth(A, restore_max=False, window_type="Blackman"): Blackman window is used. Args: - A: spatial distribution to smooth + a: spatial distribution to smooth restore_max: Boolean controlling whether the maximum value is restored after smoothing(default=false). window_type: shape of the smoothing window; any valid inputs to get_win are supported(default='Blackman'). OUTPUTS: A_sm - smoothed - """ + """ + DEF_USE_ROTATION = True + + assert is_number(a) and np.all(~np.isinf(a)) + assert isinstance(restore_max, bool) + assert isinstance(window_type, str) + # get the grid size - grid_size = A.shape + grid_size = a.shape # remove singleton dimensions - if num_dim(A) is not len(grid_size): - A = A.squeeze() - grid_size = A.shape + if num_dim2(a) != len(grid_size): + grid_size = np.squeeze(grid_size) # use a symmetric filter for odd grid sizes, and a non-symmetric filter for # even grid sizes to ensure the DC component of the window has a value of # unity - window_symmetry = [bool(n % 2) for n in grid_size] + window_symmetry = (np.array(grid_size) % 2).astype(bool) # get the window, taking the absolute value to discard machine precision # negative values - win_tmp, _ = get_win(grid_size, window_type, rotation=True, symmetric=window_symmetry) - win = abs(win_tmp) + from .signals import get_win + win, _ = get_win(grid_size, type_=window_type, + rotation=DEF_USE_ROTATION, symmetric=window_symmetry) + win = np.abs(win) - # rotate window if input A is (1, N) - if win.shape[0] == 1: - win = win.transpose() + # rotate window if input mat is (1, N) + if a.shape[0] == 1: # is row? + win = win.T # apply the filter - A_sm = np.real(ifftn(fftn(A) * ifftshift(win))) + a_sm = np.real(np.fft.ifftn(np.fft.fftn(a) * np.fft.ifftshift(win))) # restore magnitude if required if restore_max: - A_sm = (np.max(abs(A)) / np.max(abs(A_sm))) * A_sm - - return A_sm + a_sm = (np.abs(a).max() / np.abs(a_sm).max()) * a_sm + return a_sm diff --git a/kwave/utils/interputils.py b/kwave/utils/interp.py similarity index 96% rename from kwave/utils/interputils.py rename to kwave/utils/interp.py index b2bf567f..4d10d695 100644 --- a/kwave/utils/interputils.py +++ b/kwave/utils/interp.py @@ -5,13 +5,13 @@ from scipy.interpolate import interpn from scipy.signal import resample -from kwave.utils.conversionutils import scale_time -from kwave.utils.tictoc import TicToc +from .conversion import scale_time, grid2cart +from .tictoc import TicToc -def sortrows(arr: np.ndarray, index: int): - assert arr.ndim == 2, "'sortrows' currently supports only 2-dimensional matrices" - return arr[arr[:, index].argsort(),] +def sort_rows(arr: np.ndarray, index: int): + assert arr.ndim == 2, "'sort_rows' currently supports only 2-dimensional matrices" + return arr[arr[:, index].argsort()] def interpolate3d(grid_points: List[np.ndarray], grid_values: np.ndarray, interp_locs: List[np.ndarray]) -> np.ndarray: @@ -224,10 +224,7 @@ def cart2grid(kgrid, cart_data, axisymmetric=False): # map values for data_index in range(data_x.size): - try: - grid_data[data_x[data_index], data_y[data_index]] = int(data_index) - except: - print("nice try") + grid_data[data_x[data_index], data_y[data_index]] = int(data_index) # extract reordering index reorder_index = grid_data.flatten(order='F')[ @@ -281,7 +278,7 @@ def cart2grid(kgrid, cart_data, axisymmetric=False): order_index = np.ones((reorder_index.size, 2), dtype=int) order_index[:, 0] = np.squeeze(reorder_index) order_index[:, 1] = np.arange(1, reorder_index.size + 1) - order_index = sortrows(order_index, 0) + order_index = sort_rows(order_index, 0) order_index = order_index[:, 1] order_index = order_index[:, None] # [N] => [N, 1] @@ -393,7 +390,6 @@ def interp_cart_data(kgrid, cart_sensor_data, cart_sensor_mask, binary_sensor_ma if kgrid.dim not in [2, 3]: raise ValueError('Data must be two- or three-dimensional.') - from kwave.utils.kutils import grid2cart cart_bsm, _ = grid2cart(kgrid, binary_sensor_mask) # nearest neighbour interpolation of the data points @@ -416,7 +412,7 @@ def interp_cart_data(kgrid, cart_sensor_data, cart_sensor_mask, binary_sensor_ma new_col_pos = -1 # reorder the data set based on distance information - cart_sensor_data_ro = sortrows(cart_sensor_data_ro, new_col_pos) + cart_sensor_data_ro = sort_rows(cart_sensor_data_ro, new_col_pos) # linearly interpolate between the two closest points perc = cart_sensor_data_ro[2, new_col_pos] / ( @@ -437,7 +433,7 @@ def interp_cart_data(kgrid, cart_sensor_data, cart_sensor_mask, binary_sensor_ma # cart_sensor_data_ro[:, new_col_pos] = dist # # # reorder the data set based on distance information - # cart_sensor_data_ro = sortrows(cart_sensor_data_ro, new_col_pos) + # cart_sensor_data_ro = sort_rows(cart_sensor_data_ro, new_col_pos) # # # linearly interpolate between the two closest points # perc = cart_sensor_data_ro[1, new_col_pos] / (cart_sensor_data_ro[0, new_col_pos] + cart_sensor_data_ro[1, new_col_pos] ) diff --git a/kwave/utils/ioutils.py b/kwave/utils/io.py similarity index 89% rename from kwave/utils/ioutils.py rename to kwave/utils/io.py index b67cba9c..23148f93 100644 --- a/kwave/utils/ioutils.py +++ b/kwave/utils/io.py @@ -3,14 +3,14 @@ import socket import warnings from datetime import datetime +from typing import Optional import cv2 import h5py import numpy as np -from .misc import get_date_string -from .conversionutils import cast_to_type -from kwave.utils import dotdict +from .conversion import cast_to_type +from .dotdictionary import dotdict def get_h5_literals(): @@ -225,8 +225,24 @@ def write_attributes_typed(filename, file_description=None): f[h5_literals.FILE_CREATION_DATE_ATT_NAME] = get_date_string() -def write_attributes(filename, file_description=None, legacy=False): +def write_attributes(filename: str, file_description: Optional[str] = None, legacy: bool = False) -> None: + """ + Write attributes to a HDF5 file. + + This function writes attributes to a HDF5 file using a deprecated legacy method if legacy is set to True, or a new + typed method if legacy is set to False. The function warns if legacy is set to True and deprecates it. If + file_description is not provided, a default file description will be used. + Args: + filename (str): The name of the HDF5 file. + file_description (Optional[str], optional): The description of the file. If not provided, a default description + will be used. + legacy (bool, optional): If set to True, the function will use the deprecated legacy method to write attributes. + If set to False, the function will use the new typed method. Defaults to False. + + Raises: + DeprecationWarning: If legacy is set to True, a DeprecationWarning will be raised. + """ if not legacy: write_attributes_typed(filename, file_description) return @@ -254,13 +270,18 @@ def write_attributes(filename, file_description=None, legacy=False): # set additional file attributes with h5py.File(filename, "a") as f: - assign_str_attr(f.attrs, h5_literals.FILE_MAJOR_VER_ATT_NAME, h5_literals.HDF_FILE_MAJOR_VERSION) - assign_str_attr(f.attrs, h5_literals.FILE_MINOR_VER_ATT_NAME, h5_literals.HDF_FILE_MINOR_VERSION) - assign_str_attr(f.attrs, h5_literals.CREATED_BY_ATT_NAME, f'k-Wave N/A') - assign_str_attr(f.attrs, h5_literals.FILE_DESCR_ATT_NAME, file_description) - assign_str_attr(f.attrs, h5_literals.FILE_TYPE_ATT_NAME, h5_literals.HDF_INPUT_FILE) - assign_str_attr(f.attrs, h5_literals.FILE_CREATION_DATE_ATT_NAME, get_date_string()) - + # create a dictionary of attributes + attributes = { + h5_literals.FILE_MAJOR_VER_ATT_NAME: h5_literals.HDF_FILE_MAJOR_VERSION, + h5_literals.FILE_MINOR_VER_ATT_NAME: h5_literals.HDF_FILE_MINOR_VERSION, + h5_literals.CREATED_BY_ATT_NAME: f'k-Wave N/A', + h5_literals.FILE_DESCR_ATT_NAME: file_description, + h5_literals.FILE_TYPE_ATT_NAME: h5_literals.HDF_INPUT_FILE, + h5_literals.FILE_CREATION_DATE_ATT_NAME: get_date_string(), + } + # loop through the attributes dictionary and assign each attribute to the file + for key, value in attributes.items(): + assign_str_attr(f.attrs, key, value) def write_flags(filename): """ @@ -509,4 +530,8 @@ def load_image(path, is_gray): # scale pixel values from 0 -> 1 img = img.max() - img img = img * (1 / img.max()) - return img \ No newline at end of file + return img + + +def get_date_string(): + return datetime.now().strftime("%d-%b-%Y-%H-%M-%S") diff --git a/kwave/utils/maputils.py b/kwave/utils/mapgen.py similarity index 92% rename from kwave/utils/maputils.py rename to kwave/utils/mapgen.py index 3edce94f..08e95d54 100644 --- a/kwave/utils/maputils.py +++ b/kwave/utils/mapgen.py @@ -1,17 +1,17 @@ import math +import warnings from math import floor -from typing import Tuple, Optional +from typing import Tuple, Optional, Union import matplotlib.pyplot as plt import numpy as np -from kwave.utils.tictoc import TicToc - -from kwave.utils.matrixutils import matlab_find, matlab_assign, max_nd -from kwave.utils.conversionutils import scale_SI +import scipy from scipy import optimize -import warnings -from kwave.utils.conversionutils import db2neper, neper2db +from .conversion import scale_SI, db2neper, neper2db +from .matlab import matlab_assign, matlab_find +from .matrix import max_nd +from .tictoc import TicToc def get_spaced_points(start, stop, n=100, spacing='linear'): @@ -538,26 +538,24 @@ def make_cart_circle(radius, num_points, center_pos=(0, 0), arc_angle=2 * np.pi, def make_disc(Nx, Ny, cx, cy, radius, plot_disc=False): """ - Create a binary map of a filled disc within a 2D grid. - - make_disc creates a binary map of a filled disc within a - two-dimensional grid (the disc position is denoted by 1's in the - matrix with 0's elsewhere). A single grid point is taken as the disc - centre thus the total diameter of the disc will always be an odd - number of grid points. As the returned disc has a constant radius, if - used within a k-Wave grid where dx ~= dy, the disc will appear oval - shaped. If part of the disc overlaps the grid edge, the rest of the - disc will wrap to the grid edge on the opposite side. + Create a binary map of a filled disc within a 2D grid. + + This function creates a binary map of a filled disc within a two-dimensional grid. The disc position is denoted by 1's + in the matrix with 0's elsewhere. A single grid point is taken as the disc centre, so the total diameter of the disc + will always be an odd number of grid points. If used within a k-Wave grid where dx != dy, the disc will appear oval + shaped. If part of the disc overlaps the grid edge, the rest of the disc will wrap to the grid edge on the opposite + side. + Args: - Nx: - Ny: - cx: - cy: - radius: - plot_disc: + Nx (int): The number of grid points along the x-axis. + Ny (int): The number of grid points along the y-axis. + cx (int): The x-coordinate of the disc centre. + cy (int): The y-coordinate of the disc centre. + radius (int): The radius of the disc. + plot_disc (bool): If set to True, the disc will be plotted using Matplotlib. Returns: - + np.ndarray: A binary map of the disc in the 2D grid. """ # define literals MAGNITUDE = 1 @@ -598,29 +596,28 @@ def make_disc(Nx, Ny, cx, cy, radius, plot_disc=False): return disc -def make_circle(Nx, Ny, cx, cy, radius, arc_angle=None, plot_circle=False): +def make_circle(Nx: int, Ny: int, cx: int, cy: int, radius: int, arc_angle: Optional[float] = None, + plot_circle: bool = False) -> np.ndarray: """ - Create a binary map of a circle within a 2D grid. + Create a binary map of a circle within a 2D grid. - make_circle creates a binary map of a circle or arc (using the - midpoint circle algorithm) within a two-dimensional grid (the circle - position is denoted by 1's in the matrix with 0's elsewhere). A - single grid point is taken as the circle centre thus the total - diameter will always be an odd number of grid points. + This function creates a binary map of a circle (or arc) using the midpoint circle algorithm within a two-dimensional grid. + The circle position is denoted by 1's in the matrix with 0's elsewhere. A single grid point is taken as the circle + centre, so the total diameter will always be an odd number of grid points. The centre of the circle and the radius + are not constrained by the grid dimensions, so it is possible to create sections of circles or a blank image if none + of the circle intersects the grid. - Note: The centre of the circle and the radius are not constrained by - the grid dimensions, so it is possible to create sections of circles, - or a blank image if none of the circle intersects the grid. Args: - Nx: - Ny: - cx: - cy: - radius: - plot_circle: + Nx (int): The number of grid points along the x-axis. + Ny (int): The number of grid points along the y-axis. + cx (int): The x-coordinate of the circle centre. + cy (int): The y-coordinate of the circle centre. + radius (int): The radius of the circle. + arc_angle (Optional[float], optional): The angle of the circular arc in degrees. If set to None, a full circle will be created. + plot_circle (bool, optional): If set to True, the circle will be plotted using Matplotlib. Returns: - + np.ndarray: A binary map of the circle in the 2D grid. """ # define literals MAGNITUDE = 1 @@ -2343,3 +2340,106 @@ def make_spherical_section(radius, height, width=None, plot_section=False, binar raise NotImplementedError return ss, dist_map + + +def focused_bowl_oneil(radius: float, diameter: float, velocity: float, frequency: float, sound_speed: float, + density: float, axial_positions: Union[np.array, float, list] = None, + lateral_positions: Union[np.array, float, list] = None) -> [float, float]: + """ + focused_bowl_oneil calculates O'Neil's solution (O'Neil, H. Theory of + focusing radiators. J. Acoust. Soc. Am., 21(5), 516-526, 1949) for + the axial and lateral pressure amplitude generated by a focused bowl + transducer when uniformly driven by a continuous wave sinusoid at a + given frequency and normal surface velocity. + + The solution is evaluated at the positions along the beam axis given + by axial_position (where 0 corresponds to the transducer surface), + and lateral positions through the geometric focus given by + lateral_position (where 0 corresponds to the beam axis). To return + only the axial or lateral pressure, set the either axial_position or + lateral_position to []. + + Note, O'Neil's formulae are derived under the assumptions of the + Rayleigh integral, which are valid when the transducer diameter is + large compared to both the transducer height and the acoustic + wavelength. + + Args: + radius: + diameter: + velocity: + frequency: + sound_speed: + density: + axial_positions: + lateral_positions: + + Example: + # define transducer parameters + radius = 140e-3 # [m] + diameter = 120e-3 # [m] + velocity = 100e-3 # [m / s] + frequency = 1e6 # [Hz] + sound_speed = 1500 # [m / s] + density = 1000 # [kg / m ^ 3] + + # define position vectors + axial_position = np.arange(0, 250e-3 + 1e-4, 1e-4) # [m] + lateral_position = np.arange(-15e-3, 15e-3 + 1e-4, 1e-4) # [m] + + # evaluate pressure + [p_axial, p_lateral] = focused_bowl_oneil(radius, diameter, + velocity, frequency, sound_speed, density, + axial_position, lateral_position) + Returns: + p_axial: pressure amplitude at the axial_position [Pa] + p_lateral: pressure amplitude at the lateral_position [Pa] + """ + float_eps = np.finfo(float).eps + + def calculate_axial_pressure() -> float: + # calculate distances + B = np.sqrt((axial_positions - h) ** 2 + (diameter / 2) ** 2) + d = B - axial_positions + E = 2 / (1 - axial_positions / radius) + + # compute pressure + P = E * np.sin(k * d / 2) + + # replace values where axial_position is equal to the radius with limit + P[np.abs(axial_positions - radius) < float_eps] = k * h + + # calculate magnitude of the on - axis pressure + axial_pressure = density * sound_speed * velocity * np.abs(P) + return axial_pressure + + def calculate_lateral_pressure() -> float: + # calculate magnitude of the lateral pressure at the geometric focus + Z = k * lateral_positions * diameter / (2 * radius) + lateral_pressure = 2. * density * sound_speed * velocity * k * h * scipy.special.jv(1, Z) / Z + + # replace origin with limit + lateral_pressure[lateral_positions == 0] = density * sound_speed * velocity * k * h + return lateral_pressure + + # wave number + k = 2 * np.pi * frequency / sound_speed + + # height of rim + h = radius - np.sqrt(radius ** 2 - (diameter / 2) ** 2) + + p_axial = None + p_lateral = None + + if lateral_positions is not None: + p_lateral = calculate_lateral_pressure() + if axial_positions is not None: + p_axial = calculate_axial_pressure() + + return p_axial, p_lateral + + return [p_axial, p_lateral] + + +def ndgrid(*args): + return np.array(np.meshgrid(*args, indexing='ij')) diff --git a/kwave/utils/math.py b/kwave/utils/math.py new file mode 100644 index 00000000..34b3063a --- /dev/null +++ b/kwave/utils/math.py @@ -0,0 +1,144 @@ +import math +from itertools import compress +from typing import Optional, Tuple, Union + +import numpy as np +from numpy.fft import ifftshift, fft, ifft + + +def largest_prime_factor(n): + i = 2 + while i * i <= n: + if n % i: + i += 1 + else: + n //= i + return n + + +def rwh_primes(n): + # https://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188 + """ Returns a list of primes < n for n > 2 """ + sieve = bytearray([True]) * (n // 2 + 1) + for i in range(1, int(n ** 0.5) // 2 + 1): + if sieve[i]: + sieve[2 * i * (i + 1)::2 * i + 1] = bytearray((n // 2 - 2 * i * (i + 1)) // (2 * i + 1) + 1) + return [2, *compress(range(3, n, 2), sieve[1:])] + + +def fourier_shift( + data: np.ndarray, + shift: float, + shift_dim: Optional[int] = None +) -> np.ndarray: + if shift_dim is None: + shift_dim = data.ndim - 1 + if (shift_dim == 1) and (data.shape[1] == 1): + # row vector + shift_dim = 0 + else: + # subtract 1 in order to keep function interface compatible with matlab + shift_dim -= 1 + + N = data.shape[shift_dim] + + if N % 2 == 0: + # grid dimension has an even number of points + k_vec = (2 * np.pi) * (np.arange(-N // 2, N // 2) / N) + else: + # grid dimension has an odd number of points + k_vec = (2 * np.pi) * (np.arange(-(N - 1) // 2, N // 2 + 1) / N) + + # force middle value to be zero in case 1/N is a recurring number and the + # series doesn't give exactly zero + k_vec[N // 2] = 0 + + # put the wavenumber vector in the correct orientation for use with bsxfun + reshape_dims_to = [1] * data.ndim + if 0 <= shift_dim <= 3: + reshape_dims_to[shift_dim] = -1 + k_vec = np.reshape(k_vec, reshape_dims_to) + else: + raise ValueError('Input dim must be 0, 1, 2 or 3.') + + # shift the input using a Fourier interpolant + part_1 = ifftshift(np.exp(1j * k_vec * shift)) + part_2 = fft(data, axis=shift_dim) + part_1_times_2 = part_1 * part_2 + result = ifft(part_1_times_2, axis=shift_dim).real + return result + + +def round_even(x): + """ + Rounds to the nearest even integer. + + Args: + x (float): inpput value + + Returns: + (int): nearest odd integer. + """ + return 2 * round(x / 2) + + +def round_odd(x): + """ + Rounds to the nearest odd integer. + + Args: + x (float): input value + + Returns: + (int): nearest odd integer. + + """ + return 2 * round((x + 1) / 2) - 1 + + +def find_closest(A: np.ndarray, a: Union[float, int]) -> Tuple[Union[float, int], Tuple[int, ...]]: + """ + Returns the value and index of the item in A that is closest to the value a. + + This function finds the value and index of the item in the input array A that is closest to the given value a. + For vectors, the value and index correspond to the closest element in A. For matrices, value and index are row + vectors corresponding to the closest element from each column. For N-D arrays, the function finds the closest + value along the first matrix dimension (singleton dimensions are removed before the search). If there is more + than one element with the closest value, the index of the first one is returned. + + Args: + A (np.ndarray): The array to search. + a (Union[float, int]): The value to find. + + Returns: + Tuple[Union[float, int], Tuple[int, ...]]: A tuple containing the closest value and its index in the input array. + """ + + assert isinstance(A, np.ndarray), "A must be an np.array" + + idx = np.unravel_index(np.argmin(abs(A - a)), A.shape) + return A[idx], idx + + +def sinc(x): + return np.sinc(x / np.pi) + + +def primefactors(n): + # even number divisible + factors = [] + while n % 2 == 0: + factors.append(2), + n = n / 2 + + # n became odd + for i in range(3, int(math.sqrt(n)) + 1, 2): + + while (n % i == 0): + factors.append(i) + n = n / i + + if n > 2: + factors.append(n) + + return factors diff --git a/kwave/utils/mathutils.py b/kwave/utils/mathutils.py deleted file mode 100644 index 564a6346..00000000 --- a/kwave/utils/mathutils.py +++ /dev/null @@ -1,86 +0,0 @@ -from typing import Optional - -import numpy as np -from itertools import compress - -from numpy.fft import ifftshift, fft, ifft - - -def largest_prime_factor(n): - i = 2 - while i * i <= n: - if n % i: - i += 1 - else: - n //= i - return n - - -def rwh_primes(n): - # https://stackoverflow.com/questions/2068372/fastest-way-to-list-all-primes-below-n-in-python/3035188#3035188 - """ Returns a list of primes < n for n > 2 """ - sieve = bytearray([True]) * (n//2+1) - for i in range(1, int(n**0.5)//2+1): - if sieve[i]: - sieve[2*i*(i+1)::2*i+1] = bytearray((n//2-2*i*(i+1))//(2*i+1)+1) - return [2, *compress(range(3, n, 2), sieve[1:])] - - -def check_divisible(number: float, divider: float) -> bool: - """ - Checks whether number is divisible by divider without any remainder - Why do we need such a function? -> Because due to floating point precision we - experience rounding errors while using standard modulo operator with floating point numbers - Args: - number: Number that's supposed to be divided - divider: Divider that should devide the number - - Returns: - - """ - result = number / divider - after_decimal = result % 1 - return after_decimal == 0 - - -def fourier_shift( - data: np.ndarray, - shift: float, - shift_dim: Optional[int] = None -) -> np.ndarray: - if shift_dim is None: - shift_dim = data.ndim - 1 - if (shift_dim == 1) and (data.shape[1] == 1): - # row vector - shift_dim = 0 - else: - # subtract 1 in order to keep function interface compatible with matlab - shift_dim -= 1 - - N = data.shape[shift_dim] - - if N % 2 == 0: - # grid dimension has an even number of points - k_vec = (2 * np.pi) * ( np.arange(-N // 2, N // 2) / N) - else: - # grid dimension has an odd number of points - k_vec = (2 * np.pi) * ( np.arange(-(N -1) // 2, N // 2 + 1) / N) - - # force middle value to be zero in case 1/N is a recurring number and the - # series doesn't give exactly zero - k_vec[N // 2] = 0 - - # put the wavenumber vector in the correct orientation for use with bsxfun - reshape_dims_to = [1] * data.ndim - if 0 <= shift_dim <= 3: - reshape_dims_to[shift_dim] = -1 - k_vec = np.reshape(k_vec, reshape_dims_to) - else: - raise ValueError('Input dim must be 0, 1, 2 or 3.') - - # shift the input using a Fourier interpolant - part_1 = ifftshift(np.exp(1j * k_vec * shift)) - part_2 = fft(data, axis=shift_dim) - part_1_times_2 = part_1 * part_2 - result = ifft(part_1_times_2, axis=shift_dim).real - return result diff --git a/kwave/utils/matlab.py b/kwave/utils/matlab.py new file mode 100644 index 00000000..13b3b3a7 --- /dev/null +++ b/kwave/utils/matlab.py @@ -0,0 +1,33 @@ +import numpy as np + + +def matlab_assign(matrix: np.ndarray, indices, values): + original_shape = matrix.shape + matrix = matrix.flatten(order='F') + matrix[indices] = values + return matrix.reshape(original_shape, order='F') + + +def matlab_find(arr, val=0, mode='neq'): + if not isinstance(arr, np.ndarray): + arr = np.array(arr) + if mode == 'neq': + arr = np.where(arr.flatten(order='F') != val)[0] + 1 # +1 due to matlab indexing + else: # 'eq' + arr = np.where(arr.flatten(order='F') == val)[0] + 1 # +1 due to matlab indexing + return np.expand_dims(arr, -1) # compatibility, n => [n, 1] + + +def matlab_mask(arr, mask, diff=None): + if diff is None: + return np.expand_dims(arr.ravel(order='F')[mask.ravel(order='F')], axis=-1) # compatibility, n => [n, 1] + else: + return np.expand_dims(arr.ravel(order='F')[mask.ravel(order='F') + diff], axis=-1) # compatibility, n => [n, 1] + + +def unflatten_matlab_mask(arr, mask, diff=None): + if diff is None: + return np.unravel_index(mask.ravel(order='F'), arr.shape, order='F') + else: + return np.unravel_index(mask.ravel(order='F') + diff, arr.shape, order='F') + \ No newline at end of file diff --git a/kwave/utils/matrixutils.py b/kwave/utils/matrix.py similarity index 72% rename from kwave/utils/matrixutils.py rename to kwave/utils/matrix.py index ac84b0bc..4fede947 100644 --- a/kwave/utils/matrixutils.py +++ b/kwave/utils/matrix.py @@ -1,13 +1,12 @@ +import warnings from typing import Tuple -from skimage.transform import resize as si_resize -from scipy.interpolate import interpn + import numpy as np -import warnings +from scipy.interpolate import interpn, interp1d +from .checks import num_dim2 +from .conversion import scale_time from .tictoc import TicToc -from .conversionutils import scale_time -from .checkutils import num_dim2, is_number -from .interputils import interpolate2d def expand_matrix(matrix, exp_coeff, edge_val=None): @@ -35,6 +34,7 @@ def expand_matrix(matrix, exp_coeff, edge_val=None): Returns: expanded matrix """ + opts = {} matrix = np.squeeze(matrix) @@ -67,35 +67,6 @@ def expand_matrix(matrix, exp_coeff, edge_val=None): return np.pad(matrix, **opts) -def matlab_find(arr, val=0, mode='neq'): - if not isinstance(arr, np.ndarray): - arr = np.array(arr) - if mode == 'neq': - arr = np.where(arr.flatten(order='F') != val)[0] + 1 # +1 due to matlab indexing - else: # 'eq' - arr = np.where(arr.flatten(order='F') == val)[0] + 1 # +1 due to matlab indexing - return np.expand_dims(arr, -1) # compatibility, n => [n, 1] - - -def matlab_mask(arr, mask, diff=None): - if diff is None: - return np.expand_dims(arr.ravel(order='F')[mask.ravel(order='F')], axis=-1) # compatibility, n => [n, 1] - else: - return np.expand_dims(arr.ravel(order='F')[mask.ravel(order='F') + diff], axis=-1) # compatibility, n => [n, 1] - - -def unflatten_matlab_mask(arr, mask, diff=None): - if diff is None: - return np.unravel_index(mask.ravel(order='F'), arr.shape, order='F') - else: - return np.unravel_index(mask.ravel(order='F') + diff, arr.shape, order='F') - -def matlab_assign(matrix: np.ndarray, indices, values): - original_shape = matrix.shape - matrix = matrix.flatten(order='F') - matrix[indices] = values - return matrix.reshape(original_shape, order='F') - def resize(mat, new_size, interp_mode='linear'): """ resize: resamples a "matrix" of spatial samples to a desired "resolution" or spatial sampling frequency via interpolation @@ -109,6 +80,7 @@ def resize(mat, new_size, interp_mode='linear'): res_mat: "resized" matrix """ + # start the timer TicToc.tic() @@ -149,50 +121,6 @@ def resize(mat, new_size, interp_mode='linear'): return mat_rs -def smooth(mat, restore_max=False, window_type='Blackman'): - """ - Smooth a matrix - Returns: - - """ - DEF_USE_ROTATION = True - - assert is_number(mat) and np.all(~np.isinf(mat)) - assert isinstance(restore_max, bool) - assert isinstance(window_type, str) - - # get the grid size - grid_size = mat.shape - - # remove singleton dimensions - if num_dim2(mat) != len(grid_size): - grid_size = np.squeeze(grid_size) - - # use a symmetric filter for odd grid sizes, and a non-symmetric filter for - # even grid sizes to ensure the DC component of the window has a value of - # unity - window_symmetry = (np.array(grid_size) % 2).astype(bool) - - # get the window, taking the absolute value to discard machine precision - # negative values - from .kutils import get_win - win, _ = get_win(grid_size, type_=window_type, - rotation=DEF_USE_ROTATION, symmetric=window_symmetry) - win = np.abs(win) - - # rotate window if input mat is (1, N) - if mat.shape[0] == 1: # is row? - win = win.T - - # apply the filter - mat_sm = np.real(np.fft.ifftn(np.fft.fftn(mat) * np.fft.ifftshift(win))) - - # restore magnitude if required - if restore_max: - mat_sm = (np.abs(mat).max() / np.abs(mat_sm).max()) * mat_sm - return mat_sm - - def gradient_fd(f, dx=None, dim=None, deriv_order=None, accuracy_order=None): """ A wrapper of the numpy gradient method for use in the k-wave library. @@ -228,6 +156,7 @@ def gradient_fd(f, dx=None, dim=None, deriv_order=None, accuracy_order=None): fx, fy, ... gradient """ + if deriv_order: warnings.warn("deriv_order is no longer a supported argument.", DeprecationWarning) if accuracy_order: @@ -251,7 +180,73 @@ def min_nd(matrix: np.ndarray) -> Tuple[float, Tuple]: def max_nd(matrix: np.ndarray) -> Tuple[float, Tuple]: + """ + Returns the maximum value in a n-dimensional array and its index. + + Args: + matrix (np.ndarray): n-dimensional array of values. + + Returns: + A tuple containing the maximum value in the array, and a tuple containing the index of the + maximum value. The index is given in the MATLAB convention, where indexing starts at 1. + + """ + # Get the maximum value and its linear index max_val, linear_index = np.max(matrix), matrix.argmax() + + # Convert the linear index to a tuple of indices in the original matrix numpy_index = np.unravel_index(linear_index, matrix.shape) + + # Convert the tuple of indices to 1-based indices (as used in Matlab) matlab_index = tuple(idx + 1 for idx in numpy_index) + + # Return the maximum value and the 1-based index return max_val, matlab_index + + +def broadcast_axis(data: np.ndarray, ndims: int, axis: int) -> np.ndarray: + """Broadcast the given axis of the data to the specified number of dimensions. + + Args: + data (np.ndarray): The data to broadcast. + ndims (int): The number of dimensions to broadcast the axis to. + axis (int): The axis to broadcast. + + Returns: + The broadcasted data. + """ + newshape = [1] * ndims + newshape[axis] = -1 + return data.reshape(*newshape) + + +def revolve2d(mat2D): + # start timer + TicToc.tic() + + # update command line status + print('Revolving 2D matrix to form a 3D matrix...') + + # get size of matrix + m, n = mat2D.shape + + # create the reference axis for the 2D image + r_axis_one_sided = np.arange(0, n) + r_axis_two_sided = np.arange(-(n - 1), n) + + # compute the distance from every pixel in the z-y cross-section of the 3D + # matrix to the rotation axis + z, y = np.meshgrid(r_axis_two_sided, r_axis_two_sided) + r = np.sqrt(y ** 2 + z ** 2) + + # create empty image matrix + mat3D = np.zeros((m, 2 * n - 1, 2 * n - 1)) + + # loop through each cross section and create 3D matrix + for x_index in range(m): + interp = interp1d(x=r_axis_one_sided, y=mat2D[x_index, :], kind='linear', bounds_error=False, fill_value=0) + mat3D[x_index, :, :] = interp(r) + + # update command line status + print(f' completed in {scale_time(TicToc.toc())}s') + return mat3D diff --git a/kwave/utils/misc.py b/kwave/utils/misc.py deleted file mode 100644 index 8cd9d671..00000000 --- a/kwave/utils/misc.py +++ /dev/null @@ -1,175 +0,0 @@ -from datetime import datetime -from typing import Union -import scipy - -import numpy as np - - -def get_date_string(): - return datetime.now().strftime("%d-%b-%Y-%H-%M-%S") - - -def gaussian(x, magnitude=None, mean=0, variance=1): - if magnitude is None: - magnitude = np.sqrt(2 * np.pi * variance) - return magnitude * np.exp(-(x - mean) ** 2 / (2 * variance)) - - -def ndgrid(*args): - return np.array(np.meshgrid(*args, indexing='ij')) - - -def sinc(x): - return np.sinc(x / np.pi) - - -def round_even(x): - """ - Rounds to the nearest even integer. - - Args: - x (float): inpput value - - Returns: - (int): nearest odd integer. - """ - return 2 * round(x / 2) - - -def round_odd(x): - """ - Rounds to the nearest odd integer. - - Args: - x (float): input value - - Returns: - (int): nearest odd integer. - - """ - return 2 * round((x + 1) / 2) - 1 - - -def focused_bowl_oneil(radius: float, diameter: float, velocity: float, frequency: float, sound_speed: float, - density: float, axial_positions: Union[np.array, float, list] = None, - lateral_positions: Union[np.array, float, list] = None) -> [float, float]: - """ - focused_bowl_oneil calculates O'Neil's solution (O'Neil, H. Theory of - focusing radiators. J. Acoust. Soc. Am., 21(5), 516-526, 1949) for - the axial and lateral pressure amplitude generated by a focused bowl - transducer when uniformly driven by a continuous wave sinusoid at a - given frequency and normal surface velocity. - - The solution is evaluated at the positions along the beam axis given - by axial_position (where 0 corresponds to the transducer surface), - and lateral positions through the geometric focus given by - lateral_position (where 0 corresponds to the beam axis). To return - only the axial or lateral pressure, set the either axial_position or - lateral_position to []. - - Note, O'Neil's formulae are derived under the assumptions of the - Rayleigh integral, which are valid when the transducer diameter is - large compared to both the transducer height and the acoustic - wavelength. - - Args: - radius: - diameter: - velocity: - frequency: - sound_speed: - density: - axial_positions: - lateral_positions: - - Example: - # define transducer parameters - radius = 140e-3 # [m] - diameter = 120e-3 # [m] - velocity = 100e-3 # [m / s] - frequency = 1e6 # [Hz] - sound_speed = 1500 # [m / s] - density = 1000 # [kg / m ^ 3] - - # define position vectors - axial_position = np.arange(0, 250e-3 + 1e-4, 1e-4) # [m] - lateral_position = np.arange(-15e-3, 15e-3 + 1e-4, 1e-4) # [m] - - # evaluate pressure - [p_axial, p_lateral] = focused_bowl_oneil(radius, diameter, - velocity, frequency, sound_speed, density, - axial_position, lateral_position) - Returns: - p_axial: pressure amplitude at the axial_position [Pa] - p_lateral: pressure amplitude at the lateral_position [Pa] - """ - float_eps = np.finfo(float).eps - - def calculate_axial_pressure() -> float: - # calculate distances - B = np.sqrt((axial_positions - h) ** 2 + (diameter / 2) ** 2) - d = B - axial_positions - E = 2 / (1 - axial_positions / radius) - - # compute pressure - P = E * np.sin(k * d / 2) - - # replace values where axial_position is equal to the radius with limit - P[np.abs(axial_positions - radius) < float_eps] = k * h - - # calculate magnitude of the on - axis pressure - axial_pressure = density * sound_speed * velocity * np.abs(P) - return axial_pressure - - def calculate_lateral_pressure() -> float: - # calculate magnitude of the lateral pressure at the geometric focus - Z = k * lateral_positions * diameter / (2 * radius) - lateral_pressure = 2. * density * sound_speed * velocity * k * h * scipy.special.jv(1, Z) / Z - - # replace origin with limit - lateral_pressure[lateral_positions == 0] = density * sound_speed * velocity * k * h - return lateral_pressure - - # wave number - k = 2 * np.pi * frequency / sound_speed - - # height of rim - h = radius - np.sqrt(radius ** 2 - (diameter / 2) ** 2) - - p_axial = None - p_lateral = None - - if lateral_positions is not None: - p_lateral = calculate_lateral_pressure() - if axial_positions is not None: - p_axial = calculate_axial_pressure() - - return p_axial, p_lateral - - return [p_axial, p_lateral] - - -def find_closest(A, a): - """ - find_closest returns the value and index of the item in A that is - closest to the value a. For vectors, value and index correspond to - the closest element in A. For matrices, value and index are row - vectors corresponding to the closest element from each column. For - N-D arrays, the function finds the closest value along the first - matrix dimension (singleton dimensions are removed before the - search). If there is more than one element with the closest value, - the index of the first one is returned. - - Args: - A: matrix to search - a: value to find - - Returns: - val - idx - """ - - assert isinstance(A, np.ndarray), "A must be an np.array" - - idx = np.unravel_index(np.argmin(abs(A - a)), A.shape) - return A[idx], idx diff --git a/kwave/utils/osutils.py b/kwave/utils/osutils.py deleted file mode 100644 index 5e0aac4f..00000000 --- a/kwave/utils/osutils.py +++ /dev/null @@ -1,5 +0,0 @@ -import platform - - -def is_unix(): - return platform.system() in ['Linux', 'Darwin'] diff --git a/kwave/utils/pmlutils.py b/kwave/utils/pml.py similarity index 71% rename from kwave/utils/pmlutils.py rename to kwave/utils/pml.py index c7de3d13..ef1aa195 100644 --- a/kwave/utils/pmlutils.py +++ b/kwave/utils/pml.py @@ -1,22 +1,30 @@ import numpy as np +from kwave.utils.math import largest_prime_factor -def get_pml(Nx, dx, dt, c, pml_size, pml_alpha, staggered, dimension, axisymmetric=False): + +def get_pml(Nx: int, dx: float, dt: float, c: float, pml_size: int, pml_alpha: float, + staggered: bool, dimension: int, axisymmetric: bool = False) -> np.ndarray: """ - getPML returns a 1D perfectly matched layer variable based on the given size and absorption coefficient. + Returns a 1D perfectly matched layer variable based on the given size and absorption coefficient. + + This function calculates a 1D perfectly matched layer (PML) variable based on the specified size and absorption coefficient. + It uses the given parameters to create an absorption profile, which is then exponentiated and reshaped in the desired direction. + If the axisymmetric argument is set to True, the axial side of the radial PML will not be added. + Args: - Nx: - dx: - dt: - c: - pml_size: - pml_alpha: - staggered: - dimension: - axisymmetric: + Nx (int): The number of grid points in the x direction. + dx (float): The spacing between grid points in the x direction. + dt (float): The time step size. + c (float): The wave speed in the medium. + pml_size (int): The size of the PML layer in grid points. + pml_alpha (float): The absorption coefficient of the PML layer. + staggered (bool): Whether to use a staggered grid for calculating the varying components of the PML. + dimension (int): The dimension of the PML (1, 2, or 3). + axisymmetric (bool, optional): Whether to use axisymmetry when calculating the PML. Defaults to False. Returns: - + A 1D numpy array representing the PML variable. """ # define x-axis Nx = int(Nx) @@ -25,27 +33,18 @@ def get_pml(Nx, dx, dt, c, pml_size, pml_alpha, staggered, dimension, axisymmetr # create absorption profile if staggered: - - # calculate the varying components of the pml using a staggered grid - pml_left = pml_alpha * (c / dx) * (( ((x + 0.5) - pml_size - 1) / (0 - pml_size) ) ** 4) - pml_right = pml_alpha * (c / dx) * (( (x + 0.5) / pml_size ) ** 4) - + pml_left = pml_alpha * (c / dx) * ((((x + 0.5) - pml_size - 1) / (0 - pml_size)) ** 4) + pml_right = pml_alpha * (c / dx) * (((x + 0.5) / pml_size) ** 4) else: + pml_left = pml_alpha * (c / dx) * (((x - pml_size - 1) / (0 - pml_size)) ** 4) + pml_right = pml_alpha * (c / dx) * ((x / pml_size) ** 4) - # calculate the varying components of the pml using a regular grid - pml_left = pml_alpha * (c / dx) * (( (x - pml_size - 1) / (0 - pml_size) ) ** 4) - pml_right = pml_alpha * (c / dx) * (( x / pml_size ) ** 4) - - # exponentiation - pml_left = np.exp(-pml_left * dt / 2) + # exponentiate and add the components of the pml to the total function + pml_left = np.exp(-pml_left * dt / 2) pml_right = np.exp(-pml_right * dt / 2) - - # add the components of the pml to the total function, not adding the axial - # side of the radial PML if axisymmetric pml = np.ones((1, Nx)) if not axisymmetric: pml[:, :pml_size] = pml_left - pml[:, Nx - pml_size:] = pml_right # reshape the pml vector to be in the desired direction @@ -54,7 +53,6 @@ def get_pml(Nx, dx, dt, c, pml_size, pml_alpha, staggered, dimension, axisymmetr elif dimension == 3: pml = np.reshape(pml, (1, 1, Nx)) return pml - # ------------ # Other forms: # ------------ @@ -131,7 +129,6 @@ def get_optimal_pml_size(grid_size, pml_range=None, axisymmetric=None): # extract the largest prime factor for each dimension for each pml size facs = np.zeros((grid_dim, len(pml_size))) - from kwave.utils import largest_prime_factor for dim in range(0, grid_dim): for index in range(0, len(pml_size)): if isinstance(axisymmetric, str) and dim == 2: diff --git a/kwave/utils/kutils.py b/kwave/utils/signals.py similarity index 67% rename from kwave/utils/kutils.py rename to kwave/utils/signals.py index ac0a30fa..8da75b6c 100644 --- a/kwave/utils/kutils.py +++ b/kwave/utils/signals.py @@ -1,213 +1,22 @@ -import math -import warnings -from copy import deepcopy -from math import floor +from math import floor, pi from typing import Union, List, Optional import numpy as np import scipy -from numpy import ndarray from numpy.fft import ifftshift, fft, ifft from kwave.kgrid import kWaveGrid -from kwave.utils.checkutils import num_dim -from kwave.utils.conversionutils import scale_SI -from kwave.utils.matrixutils import unflatten_matlab_mask, matlab_mask -from .conversionutils import db2neper -from .misc import sinc, ndgrid, gaussian - - -def primefactors(n): - # even number divisible - factors = [] - while n % 2 == 0: - factors.append(2), - n = n / 2 - - # n became odd - for i in range(3, int(math.sqrt(n)) + 1, 2): - - while (n % i == 0): - factors.append(i) - n = n / i - - if n > 2: - factors.append(n) - - return factors - - -def check_factors(min_number, max_number): - """ - Return the maximum prime factor for a range of numbers. - - checkFactors loops through the given range of numbers and finds the - numbers with the smallest maximum prime factors. This allows suitable - grid sizes to be selected to maximise the speed of the FFT (this is - fastest for FFT lengths with small prime factors). The output is - printed to the command line, and a plot of the factors is generated. - - Args: - min_number: integer specifying the lower bound of values to test - max_number: integer specifying the upper bound of values to test - - Returns: - - """ - - # extract factors - facs = np.zeros(1, max_number - min_number) - fac_max = facs - for index in range(min_number, max_number): - facs[index - min_number + 1] = len(primefactors(index)) - fac_max[index - min_number + 1] = max(primefactors(index)) - - # compute best factors in range - print('Numbers with a maximum prime factor of 2') - ind = min_number + np.argwhere(fac_max == 2) - print(ind) - print('Numbers with a maximum prime factor of 3') - ind = min_number + np.argwhere(fac_max == 3) - print(ind) - print('Numbers with a maximum prime factor of 5') - ind = min_number + np.argwhere(fac_max == 5) - print(ind) - print('Numbers with a maximum prime factor of 7') - ind = min_number + np.argwhere(fac_max == 7) - print(ind) - print('Numbers to avoid (prime numbers)') - nums = np.arange(min_number, max_number) - print(nums[fac_max == nums]) - - -def check_stability(kgrid, medium): - """ - checkStability calculates the maximum time step for which the k-space - propagation models kspaceFirstOrder1D, kspaceFirstOrder2D and - kspaceFirstOrder3D are stable. These models are unconditionally - stable when the reference sound speed is equal to or greater than the - maximum sound speed in the medium and there is no absorption. - However, when the reference sound speed is less than the maximum - sound speed the model is only stable for sufficiently small time - steps. The criterion is more stringent (the time step is smaller) in - the absorbing case. - - The time steps given are accurate when the medium properties are - homogeneous. For a heterogeneous media they give a useful, but not - exact, estimate. - Args: - kgrid: k-Wave grid object return by kWaveGrid - medium: structure containing the medium properties - - Returns: the maximum time step for which the models are stable. - This is set to Inf when the model is unconditionally stable. - """ - # why? : this function was migrated from Matlab. - # Matlab would treat the 'medium' as a "pass by value" argument. - # In python argument is passed by reference and changes in this function will cause original data to be changed. - # Instead of making significant changes to the function, we make a deep copy of the argument - medium = deepcopy(medium) - - # define literals - FIXED_POINT_ACCURACY = 1e-12 - - # find the maximum wavenumber - kmax = kgrid.k.max() - - # calculate the reference sound speed for the fluid code, using the - # maximum by default which ensures the model is unconditionally stable - reductions = { - 'min': np.min, - 'max': np.max, - 'mean': np.mean - } - - if medium.sound_speed_ref is not None: - ss_ref = medium.sound_speed_ref - if np.isscalar(ss_ref): - c_ref = ss_ref - else: - try: - c_ref = reductions[ss_ref](medium.sound_speed) - except KeyError: - raise NotImplementedError('Unknown input for medium.sound_speed_ref.') - else: - c_ref = reductions['max'](medium.sound_speed) - - # calculate the timesteps required for stability - if medium.alpha_coeff is None or np.all(medium.alpha_coeff == 0): - - # ===================================================================== - # NON-ABSORBING CASE - # ===================================================================== - - medium.sound_speed = np.atleast_1d(medium.sound_speed) - if c_ref >= medium.sound_speed.max(): - # set the timestep to Inf when the model is unconditionally stable - dt_stability_limit = float('inf') - - else: - # set the timestep required for stability when c_ref~=max(medium.sound_speed(:)) - dt_stability_limit = 2 / (c_ref * kmax) * np.asin(c_ref / medium.sound_speed.max()) - - else: - - # ===================================================================== - # ABSORBING CASE - # ===================================================================== - - # convert the absorption coefficient to nepers.(rad/s)^-y.m^-1 - medium.alpha_coeff = db2neper(medium.alpha_coeff, medium.alpha_power) - - # calculate the absorption constant - if medium.alpha_mode == 'no_absorption': - absorb_tau = -2 * medium.alpha_coeff * medium.sound_speed ** (medium.alpha_power - 1) - else: - absorb_tau = np.array([0]) - - # calculate the dispersion constant - if medium.alpha_mode == 'no_dispersion': - absorb_eta = 2 * medium.alpha_coeff * medium.sound_speed ** medium.alpha_power * np.tan( - np.pi * medium.alpha_power / 2) - else: - absorb_eta = np.array([0]) - - # estimate the timestep required for stability in the absorbing case by - # assuming the k-space correction factor, kappa = 1 (note that - # absorb_tau and absorb_eta are negative quantities) - medium.sound_speed = np.atleast_1d(medium.sound_speed) - - temp1 = medium.sound_speed.max() * absorb_tau.min() * kmax ** (medium.alpha_power - 1) - temp2 = 1 - absorb_eta.min() * kmax ** (medium.alpha_power - 1) - dt_estimate = (temp1 + np.sqrt(temp1 ** 2 + 4 * temp2)) / (temp2 * kmax * medium.sound_speed.max()) - - # use a fixed point iteration to find the correct timestep, assuming - # now that kappa = kappa(dt), using the previous estimate as a starting - # point - - # first define the function to iterate - def kappa(dt): - return sinc(c_ref * kmax * dt / 2) - - def temp3(dt): - return medium.sound_speed.max() * absorb_tau.min() * kappa(dt) * kmax ** (medium.alpha_power - 1) - - def func_to_solve(dt): - return (temp3(dt) + np.sqrt((temp3(dt)) ** 2 + 4 * temp2)) / ( - temp2 * kmax * kappa(dt) * medium.sound_speed.max()) - - # run the fixed point iteration - dt_stability_limit = dt_estimate - dt_old = 0 - while abs(dt_stability_limit - dt_old) > FIXED_POINT_ACCURACY: - dt_old = dt_stability_limit - dt_stability_limit = func_to_solve(dt_stability_limit) - - return dt_stability_limit +from .checks import num_dim +from .conversion import scale_SI, freq2wavenumber +from .mapgen import ndgrid +from .math import sinc +from .matlab import matlab_mask, unflatten_matlab_mask +from .matrix import broadcast_axis def add_noise(signal, snr, mode="rms"): """ + Add Gaussian noise to a signal. Args: signal (np.array): input signal @@ -241,43 +50,6 @@ def add_noise(signal, snr, mode="rms"): return signal -def grid2cart(input_kgrid: kWaveGrid, grid_selection: ndarray): - """ - Returns the Cartesian coordinates of the non-zero points of a binary grid. - - DESCRIPTION: - grid2cart returns the set of Cartesian coordinates corresponding to - the non-zero elements in the binary matrix grid_data, in the - coordinate framework defined in kgrid. - - USAGE: - [cart_data, order_index] = grid2cart(kgrid, grid_data) - - args: - input_kgrid: k-Wave grid object returned by kWaveGrid - grid_selection: binary grid with the same dimensions as the k-Wave grid kgrid - - Returns: - cart_data: 1 x N, 2 x N, or 3 x N (for 1, 2, and 3 - dimensions) array of Cartesian sensor points - order_index: returns a list of indices of the returned card_data coordinates. - """ - grid_data = np.array((grid_selection != 0), dtype=bool) - cart_data = np.zeros((input_kgrid.dim, np.sum(grid_data))) - - if input_kgrid.dim > 0: - cart_data[0, :] = input_kgrid.x[grid_data] - if input_kgrid.dim > 1: - cart_data[1, :] = input_kgrid.y[grid_data] - if input_kgrid.dim > 2: - cart_data[2, :] = input_kgrid.z[grid_data] - if 0 <= input_kgrid.dim > 3: - raise ValueError("kGrid with unsupported size passed.") - - order_index = np.argwhere(grid_data.squeeze() != 0) - return cart_data.squeeze(), order_index - - def get_win(N: Union[int, List[int]], # TODO: replace and refactor for scipy.signal.get_window # https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.get_window.html#scipy.signal.get_window @@ -362,7 +134,7 @@ def cosine_series(n, N, coeffs): """ series = coeffs[0] for index in range(1, len(coeffs)): - series = series + (-1) ** (index) * coeffs[index] * np.cos(index * 2 * np.pi * n / (N - 1)) + series = series + (-1) ** index * coeffs[index] * np.cos(index * 2 * np.pi * n / (N - 1)) return series.T # Check if N is either `int` or `list of ints` @@ -599,12 +371,11 @@ def tone_burst(sample_freq, signal_freq, num_cycles, envelope='Gaussian', plot_s sample_freq: sampling frequency [Hz] signal_freq: frequency of the tone burst signal [Hz] num_cycles: number of sinusoidal oscillations - envelope: OPTIONAL INPUTS: Optional 'string', value pairs that may be used to modify the default computational settings. - 'Envelope' - Envelope used to taper the tone burst. Valid inputs + envelope: - Envelope used to taper the tone burst. Valid inputs are: 'Gaussian' (the default) @@ -615,12 +386,12 @@ def tone_burst(sample_freq, signal_freq, num_cycles, envelope='Gaussian', plot_s with a cosine taper of the specified length at the beginning and end. - 'Plot' - Boolean controlling whether the created tone + plot: Boolean controlling whether the created tone burst is plotted. - 'SignalLength' - Signal length in number of samples, if longer + signal_length: Signal length in number of samples, if longer than the tone burst length, the signal is appended with zeros. - 'SignalOffset' - Signal offset before the tone burst starts in + signal_offset: Signal offset before the tone burst starts in number of samples. Returns: created tone burst @@ -674,6 +445,7 @@ def tone_burst(sample_freq, signal_freq, num_cycles, envelope='Gaussian', plot_s # create the envelope if envelope == 'Gaussian': + from kwave.utils.filters import gaussian x_lim = 3 window_x = np.arange(-x_lim, x_lim + 1e-8, 2 * x_lim / (len(tone_burst) - 1)) window = gaussian(window_x, 1, 0, 1) @@ -736,31 +508,6 @@ def calc_max_freq(max_spat_freq, c): return filter_cutoff_freq -def freq2wavenumber(N, k_max, filter_cutoff, c, k_dim): - """ - Args: - N: - k_max: - filter_cutoff: - c: - k_dim: - - Returns: - - """ - k_cutoff = 2 * np.pi * filter_cutoff / c - - # set the alpha_filter size - filter_size = round(N * k_cutoff / k_dim[-1]) - - # check the alpha_filter size - if filter_size > N: - # set the alpha_filter size to be the same as the grid size - filter_size = N - filter_cutoff = k_max * c / (2 * np.pi) - return filter_size, filter_cutoff - - def get_alpha_filter(kgrid, medium, filter_cutoff, taper_ratio=0.5): """ get_alpha_filter uses get_win to create a Tukey window via rotation to @@ -806,7 +553,7 @@ def get_alpha_filter(kgrid, medium, filter_cutoff, taper_ratio=0.5): indexes = [round((kgrid.N[idx] - filter_size[idx]) / 2) for idx in range(len(filter_size))] if dim == 1: - alpha_filter[indexes[0]: indexes[0] + filter_size[0]] + alpha_filter[indexes[0]: indexes[0] + filter_size[0]] = np.squeeze(filter_sec) elif dim == 2: alpha_filter[indexes[0]: indexes[0] + filter_size[0], indexes[1]: indexes[1] + filter_size[1]] = filter_sec elif dim == 3: @@ -820,68 +567,6 @@ def get_alpha_filter(kgrid, medium, filter_cutoff, taper_ratio=0.5): return alpha_filter -def focus(kgrid, input_signal, source_mask, focus_position, sound_speed): - """ - focus Create input signal based on source mask and focus position. - focus takes a single input signal and a source mask and creates an - input signal matrix (with one input signal for each source point). - The appropriate time delays required to focus the signals at a given - position in Cartesian space are automatically added based on the user - inputs for focus_position and sound_speed. - - Args: - kgrid: k-Wave grid object returned by kWaveGrid - input_signal: single time series input - source_mask: matrix specifying the positions of the time - varying source distribution (i.e., source.p_mask - or source.u_mask) - focus_position: position of the focus in Cartesian coordinates - sound_speed: scalar sound speed - - Returns: - input_signal_mat: matrix of time series following the source points - """ - - assert kgrid.t_array != 'auto', "kgrid.t_array must be defined." - if isinstance(sound_speed, int): - sound_speed = float(sound_speed) - - assert isinstance(sound_speed, float), "sound_speed must be a scalar." - - positions = [kgrid.x.flatten(), kgrid.y.flatten(), kgrid.z.flatten()] - - # filter_positions - positions = [position for position in positions if (position != np.nan).any()] - assert len(positions) == kgrid.dim - positions = np.array(positions) - - if isinstance(focus_position, list): - focus_position = np.array(focus_position) - assert isinstance(focus_position, np.ndarray) - - dist = np.linalg.norm(positions[:, source_mask.flatten() == 1] - focus_position[:, np.newaxis]) - - # distance to delays - delay = int(np.round(dist / (kgrid.dt * sound_speed))) - max_delay = np.max(delay) - rel_delay = -(delay - max_delay) - - signal_mat = np.zeros((rel_delay.size, input_signal.size + max_delay)) - - # for src_idx, delay in enumerate(rel_delay): - # signal_mat[src_idx, delay:max_delay - delay] = input_signal - # signal_mat[rel_delay, delay:max_delay - delay] = input_signal - - warnings.warn("This method is not fully migrated, might be depricated and is untested.", PendingDeprecationWarning) - return signal_mat - - -def broadcast_axis(data, ndims, axis): - newshape = [1] * ndims - newshape[axis] = -1 - return data.reshape(*newshape) - - def get_wave_number(Nx, dx, dim): if Nx % 2 == 0: # even @@ -946,6 +631,7 @@ def gradient_spect(f, dn, dim=None, deriv_order=1): # get the wave number kx = get_wave_number(sz[dim], dn[dim], dim) # calculate derivative and assign output + # TODO: replace this with numpy broadcasting kx = broadcast_axis(kx, num_dim(f), dim) grads.append(np.real(ifft((1j * kx) ** deriv_order * fft(f, axis=dim), axis=dim))) @@ -1011,4 +697,75 @@ def reorder_sensor_data(kgrid, sensor, sensor_data: np.ndarray) -> np.ndarray: # reorder the measure time series so that adjacent time series correspond # to adjacent sensor points. reordered_sensor_data = sensor_data[indices_new] - return reordered_sensor_data \ No newline at end of file + return reordered_sensor_data + + +def create_cw_signals(t_array, freq, amp, phase, ramp_length=4): + """ + create_cw_signals generates a series of continuous wave (CW) signals + based on the 1D or 2D input matrices amp and phase, where each signal + is given by: + + amp(i, j) .* sin(2 .* pi .* freq .* t_array + phase(i, j)); + + To avoid startup transients, a cosine tapered up-ramp is applied to + the beginning of the signal. By default, the length of this ramp is + four periods of the wave. The up-ramp can be turned off by setting + the ramp_length to 0. + + Example: + + # define sampling parameters + f = 5e6 + T = 1/f + Fs = 100e6 + dt = 1/Fs + t_array = np.arange(0, 10*T, dt) + + # define amplitude and phase + amp = get_win(9, 'Gaussian') + phase = np.arange(0, 2*pi, 9).T + + # create signals and plot + cw_signal = create_cw_signals(t_array, f, amp, phase) + + Args: + t_array: + freq: + amp: + phase: + ramp_length: + + Returns: + cw_signals: + + """ + if len(phase) == 1: + phase = phase * np.ones(amp.shape) + + N1, N2 = amp.T.shape + + cw_signals = np.zeros([N1, N2, len(t_array)]) + + for idx1 in range(N1 - 1): + for idx2 in range(N2 - 1): + cw_signals[idx1, idx2, :] = amp[idx1, idx2] * np.sin(2 * pi * freq * t_array + phase[idx1, idx2]) + + if ramp_length != 0: + # get period and time-step + period = 1 / freq + dt = t_array[1] - t_array[0] + + # create ramp x-axis between 0 and pi + ramp_length_points = int(np.round(ramp_length * period / dt)) + ramp_axis = np.arange(0, pi, pi / (ramp_length_points)) + + # create ramp using a shifted cosine + ramp = (-np.cos(ramp_axis) + 1) * 0.5 + ramp = np.reshape(ramp, (1, 1, -1)) + + # apply ramp to all signals simultaneously + + cw_signals[:, :, :ramp_length_points] *= ramp + + return np.squeeze(cw_signals) diff --git a/kwave/utils/tictoc.py b/kwave/utils/tictoc.py index a27b77ae..74894dcd 100644 --- a/kwave/utils/tictoc.py +++ b/kwave/utils/tictoc.py @@ -2,14 +2,40 @@ class TicToc(object): + """A class for measuring the execution time of a code block. + + This class uses the perf_counter function from the time module to measure the + execution time of a code block. It provides a simple interface with two methods: + tic and toc. You can use the tic method to start the timer, and then use the + toc method to stop the timer and get the elapsed time. + """ + start_time = -1 @staticmethod def tic(): + """Start the timer. + + This method sets the start_time attribute to the current time, as measured + by the perf_counter function from the time module. + """ TicToc.start_time = perf_counter() @staticmethod def toc(reset: bool = False) -> float: + """Stop the timer and return the elapsed time. + + This method calculates the elapsed time since the timer was started by + subtracting the start_time attribute from the current time, as measured by + the perf_counter function from the time module. If the reset argument is + True, the timer will be restarted automatically. + + Args: + reset: Whether to reset the timer after stopping it. + + Returns: + The elapsed time in seconds. + """ passed_time = perf_counter() - TicToc.start_time if reset: TicToc.tic() diff --git a/make_docs.sh b/make_docs.sh new file mode 100755 index 00000000..ef0362cc --- /dev/null +++ b/make_docs.sh @@ -0,0 +1,4 @@ +#! /bin/bash + + # sphinx-apidoc -o ./docs -efT $pwd + sphinx-build -b html docs docs/_build diff --git a/pyproject.toml b/pyproject.toml index b8598f74..de090854 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,8 @@ build-backend = "hatchling.build" name = "k-Wave-python" dynamic = ["version"] description = "Acoustics toolbox for time domain acoustic and ultrasound simulations in complex and tissue-realistic media." -readme = "README.md" -license = {file = "LICENSE"} +readme = "docs/README.md" +license = { file = "LICENSE" } requires-python = ">=3.8" authors = [ { name = "Farid Yagubbayli", email = "farid.yagubbayli@tum.de" }, @@ -42,8 +42,11 @@ Bug-tracker = "https://github.com/waltsims/k-wave-python/issues" [project.optional-dependencies] test = ["pytest", "phantominator"] example = ["gdown==4.5.3"] -docs = ["sphinx_rtd_theme", - "sphinx-toolbox"] +docs = ["m2r2", + "sphinx-copybutton", + "sphinx-toolbox", + "sphinx-copybutton", + "furo"] [tool.hatch.version] path = "kwave/__init__.py" diff --git a/run_tests.sh b/run_tests.sh index 8707ae82..70d413d8 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -5,7 +5,7 @@ project_dir_in_container='/k-wave-python' docker build -t $image_name . docker run -it \ - --volume $(pwd):$project_dir_in_container \ + --volume "$(pwd)":$project_dir_in_container \ --workdir $project_dir_in_container \ $image_name \ pytest # run pytest diff --git a/tests/matlab_test_data_collectors/python_testers/cart2grid_test.py b/tests/matlab_test_data_collectors/python_testers/cart2grid_test.py index 1df734ce..9a702708 100644 --- a/tests/matlab_test_data_collectors/python_testers/cart2grid_test.py +++ b/tests/matlab_test_data_collectors/python_testers/cart2grid_test.py @@ -5,7 +5,7 @@ import numpy as np from scipy.io import loadmat -from kwave.utils.interputils import cart2grid +from kwave.utils.interp import cart2grid def test_cart2grid(): diff --git a/tests/matlab_test_data_collectors/python_testers/expandMatrix_test.py b/tests/matlab_test_data_collectors/python_testers/expandMatrix_test.py index adbc4beb..3e40cb50 100644 --- a/tests/matlab_test_data_collectors/python_testers/expandMatrix_test.py +++ b/tests/matlab_test_data_collectors/python_testers/expandMatrix_test.py @@ -1,13 +1,10 @@ -from kwave.utils.matrixutils import expand_matrix - -from kwave.utils import min_nd - -from scipy.io import loadmat -import numpy as np import os from pathlib import Path -import pytest +import numpy as np +from scipy.io import loadmat + +from kwave.utils.matrix import expand_matrix def test_expand_matrix_test(): diff --git a/tests/matlab_test_data_collectors/python_testers/fourierShift_test.py b/tests/matlab_test_data_collectors/python_testers/fourierShift_test.py index 95405067..8c35b775 100644 --- a/tests/matlab_test_data_collectors/python_testers/fourierShift_test.py +++ b/tests/matlab_test_data_collectors/python_testers/fourierShift_test.py @@ -1,10 +1,11 @@ -from kwave.utils import fourier_shift - -from scipy.io import loadmat -import numpy as np import os from pathlib import Path +import numpy as np +from scipy.io import loadmat + +from kwave.utils.math import fourier_shift + def test_fourier_shift(): collected_values_folder = os.path.join(Path(__file__).parent, 'collectedValues/fourierShift') diff --git a/tests/matlab_test_data_collectors/python_testers/gaussianFilter_test.py b/tests/matlab_test_data_collectors/python_testers/gaussianFilter_test.py index e2a20763..2525b2a2 100644 --- a/tests/matlab_test_data_collectors/python_testers/gaussianFilter_test.py +++ b/tests/matlab_test_data_collectors/python_testers/gaussianFilter_test.py @@ -3,7 +3,7 @@ import os from pathlib import Path -from kwave.utils.filterutils import gaussian_filter +from kwave.utils.filters import gaussian_filter def test_gaussianFilter(): diff --git a/tests/matlab_test_data_collectors/python_testers/getWin_test.py b/tests/matlab_test_data_collectors/python_testers/getWin_test.py index c089fbbd..d3d5eb95 100644 --- a/tests/matlab_test_data_collectors/python_testers/getWin_test.py +++ b/tests/matlab_test_data_collectors/python_testers/getWin_test.py @@ -1,9 +1,10 @@ -from kwave.utils import get_win +import os +from pathlib import Path -from scipy.io import loadmat import numpy as np -from pathlib import Path -import os +from scipy.io import loadmat + +from kwave.utils.filters import get_win def test_get_win(): diff --git a/tests/matlab_test_data_collectors/python_testers/h5io_test.py b/tests/matlab_test_data_collectors/python_testers/h5io_test.py index 09bdf4e7..f384f039 100644 --- a/tests/matlab_test_data_collectors/python_testers/h5io_test.py +++ b/tests/matlab_test_data_collectors/python_testers/h5io_test.py @@ -1,4 +1,4 @@ -from kwave.utils.ioutils import * +from kwave.utils.io import * import numpy as np from pathlib import Path @@ -10,6 +10,7 @@ def compare_h5_attributes(local_h5_path, ref_path): assert key in ref_h5.attrs.keys() assert np.isclose(local_h5.attrs[key], ref_h5.attrs[key]) + def compare_h5_values(local_h5_path, ref_path): local_h5 = h5py.File(local_h5_path, 'r') ref_h5 = h5py.File(ref_path, 'r') @@ -62,7 +63,8 @@ def test_write_attributes(tmp_path_factory): idx = idx + 1 pass -def test_writeGrid(tmp_path_factory): + +def test_write_grid(tmp_path_factory): idx = 0 for dim in range(1, 3): tmp_path = tmp_path_factory.mktemp("flags") / f"{idx}.h5" @@ -77,6 +79,3 @@ def test_writeGrid(tmp_path_factory): compare_h5_values(tmp_path, ref_path) idx = idx + 1 pass - - - diff --git a/tests/matlab_test_data_collectors/python_testers/makeArc_test.py b/tests/matlab_test_data_collectors/python_testers/makeArc_test.py index baacfaf9..273d3f71 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeArc_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeArc_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_arc +from kwave.utils.mapgen import make_arc from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeBall_test.py b/tests/matlab_test_data_collectors/python_testers/makeBall_test.py index 4b64a28a..755c386e 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeBall_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeBall_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_ball +from kwave.utils.mapgen import make_ball from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeBowl_test.py b/tests/matlab_test_data_collectors/python_testers/makeBowl_test.py index 166e2c29..e41f95f8 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeBowl_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeBowl_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_bowl +from kwave.utils.mapgen import make_bowl from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeCartCircle_test.py b/tests/matlab_test_data_collectors/python_testers/makeCartCircle_test.py index 928d249e..ef763b75 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeCartCircle_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeCartCircle_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_cart_circle +from kwave.utils.mapgen import make_cart_circle from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeCartSphere_test.py b/tests/matlab_test_data_collectors/python_testers/makeCartSphere_test.py index 22d102c0..ae391df7 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeCartSphere_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeCartSphere_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_cart_sphere +from kwave.utils.mapgen import make_cart_sphere from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeCircle_test.py b/tests/matlab_test_data_collectors/python_testers/makeCircle_test.py index f7d5d042..65e47ea7 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeCircle_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeCircle_test.py @@ -1,10 +1,10 @@ from unittest.mock import Mock -from kwave.utils.maputils import make_circle +from kwave.utils.mapgen import make_circle -from kwave.utils.interputils import cart2grid +from kwave.utils.interp import cart2grid -from kwave.utils.conversionutils import scale_time +from kwave.utils.conversion import scale_time from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeDisc_test.py b/tests/matlab_test_data_collectors/python_testers/makeDisc_test.py index 4143dbe0..bffaad4b 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeDisc_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeDisc_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_disc +from kwave.utils.mapgen import make_disc from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeLine_test.py b/tests/matlab_test_data_collectors/python_testers/makeLine_test.py index 5bb2cfca..66b2b7b9 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeLine_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeLine_test.py @@ -1,10 +1,10 @@ from unittest.mock import Mock -from kwave.utils.maputils import make_circle, make_line +from kwave.utils.mapgen import make_circle, make_line -from kwave.utils.interputils import cart2grid +from kwave.utils.interp import cart2grid -from kwave.utils.conversionutils import scale_time +from kwave.utils.conversion import scale_time from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeMultiArc_test.py b/tests/matlab_test_data_collectors/python_testers/makeMultiArc_test.py index 0014c010..7ba563a7 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeMultiArc_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeMultiArc_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_multi_arc +from kwave.utils.mapgen import make_multi_arc from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeMultiBowl_test.py b/tests/matlab_test_data_collectors/python_testers/makeMultiBowl_test.py index 7b74c62d..468010d5 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeMultiBowl_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeMultiBowl_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_multi_bowl +from kwave.utils.mapgen import make_multi_bowl from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeSphere_test.py b/tests/matlab_test_data_collectors/python_testers/makeSphere_test.py index ee73e185..91ad84f9 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeSphere_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeSphere_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_sphere +from kwave.utils.mapgen import make_sphere from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/makeSphericalSection_test.py b/tests/matlab_test_data_collectors/python_testers/makeSphericalSection_test.py index b4072ee7..367b5d8b 100644 --- a/tests/matlab_test_data_collectors/python_testers/makeSphericalSection_test.py +++ b/tests/matlab_test_data_collectors/python_testers/makeSphericalSection_test.py @@ -1,4 +1,4 @@ -from kwave.utils.maputils import make_spherical_section +from kwave.utils.mapgen import make_spherical_section from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/maxND_test.py b/tests/matlab_test_data_collectors/python_testers/maxND_test.py index c1b05dcf..05eaf98e 100644 --- a/tests/matlab_test_data_collectors/python_testers/maxND_test.py +++ b/tests/matlab_test_data_collectors/python_testers/maxND_test.py @@ -1,10 +1,10 @@ -from kwave.utils import max_nd - -from scipy.io import loadmat -import numpy as np import os from pathlib import Path -import pytest + +import numpy as np +from scipy.io import loadmat + +from kwave.utils.matrix import max_nd def test_maxND(): diff --git a/tests/matlab_test_data_collectors/python_testers/minND_test.py b/tests/matlab_test_data_collectors/python_testers/minND_test.py index 91ce3382..4526efc0 100644 --- a/tests/matlab_test_data_collectors/python_testers/minND_test.py +++ b/tests/matlab_test_data_collectors/python_testers/minND_test.py @@ -1,10 +1,11 @@ -from kwave.utils import min_nd - -from scipy.io import loadmat -import numpy as np import os from pathlib import Path +import numpy as np +from scipy.io import loadmat + +from kwave.utils.matrix import min_nd + def test_minND(): collected_values_folder = os.path.join(Path(__file__).parent, 'collectedValues/minND') diff --git a/tests/matlab_test_data_collectors/python_testers/reorderBinarySensorData_test.py b/tests/matlab_test_data_collectors/python_testers/reorderBinarySensorData_test.py index ef8968a5..032da4fa 100644 --- a/tests/matlab_test_data_collectors/python_testers/reorderBinarySensorData_test.py +++ b/tests/matlab_test_data_collectors/python_testers/reorderBinarySensorData_test.py @@ -1,9 +1,11 @@ -from kwave.utils import reorder_binary_sensor_data -from scipy.io import loadmat -import numpy as np import os from pathlib import Path +import numpy as np +from scipy.io import loadmat + +from kwave.utils.signals import reorder_binary_sensor_data + def test_binary_sensor_data(): collected_values_folder = os.path.join(Path(__file__).parent, 'collectedValues/reorderBinarySensorData') diff --git a/tests/matlab_test_data_collectors/python_testers/reorderSensorData_test.py b/tests/matlab_test_data_collectors/python_testers/reorderSensorData_test.py index 362616ec..f43f34f4 100644 --- a/tests/matlab_test_data_collectors/python_testers/reorderSensorData_test.py +++ b/tests/matlab_test_data_collectors/python_testers/reorderSensorData_test.py @@ -5,10 +5,11 @@ from scipy.io import loadmat from kwave.kgrid import kWaveGrid -from kwave.utils import reorder_sensor_data, dotdict +from kwave.utils.dotdictionary import dotdict +from kwave.utils.signals import reorder_sensor_data -def test_reorderSensorData(): +def test_reorder_sensor_data(): collected_values_folder = os.path.join(Path(__file__).parent, 'collectedValues/reorderSensorData') num_collected_values = len(os.listdir(collected_values_folder)) diff --git a/tests/matlab_test_data_collectors/python_testers/revolve2D_test.py b/tests/matlab_test_data_collectors/python_testers/revolve2D_test.py index ddb3a789..a2e6866e 100644 --- a/tests/matlab_test_data_collectors/python_testers/revolve2D_test.py +++ b/tests/matlab_test_data_collectors/python_testers/revolve2D_test.py @@ -1,10 +1,10 @@ - -from scipy.io import loadmat -import numpy as np import os from pathlib import Path -from kwave.utils import revolve2D +import numpy as np +from scipy.io import loadmat + +from kwave.utils.matrix import revolve2d def test_revolve2D(): @@ -22,8 +22,8 @@ def test_revolve2D(): expected_mat3D = recorded_data['mat3D'] - mat3D = revolve2D(mat2D) + mat3D = revolve2d(mat2D) assert np.allclose(expected_mat3D, mat3D) - print('revolve2D(..) works as expected!') + print('revolve2d(..) works as expected!') diff --git a/tests/matlab_test_data_collectors/python_testers/scaleSI_test.py b/tests/matlab_test_data_collectors/python_testers/scaleSI_test.py index 25070d4c..44a50804 100644 --- a/tests/matlab_test_data_collectors/python_testers/scaleSI_test.py +++ b/tests/matlab_test_data_collectors/python_testers/scaleSI_test.py @@ -1,4 +1,4 @@ -from kwave.utils.conversionutils import scale_SI +from kwave.utils.conversion import scale_SI from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/scaleTime_test.py b/tests/matlab_test_data_collectors/python_testers/scaleTime_test.py index 5c65e817..22fe1790 100644 --- a/tests/matlab_test_data_collectors/python_testers/scaleTime_test.py +++ b/tests/matlab_test_data_collectors/python_testers/scaleTime_test.py @@ -1,4 +1,4 @@ -from kwave.utils.conversionutils import scale_time +from kwave.utils.conversion import scale_time from scipy.io import loadmat import numpy as np diff --git a/tests/matlab_test_data_collectors/python_testers/scanConversion_test.py b/tests/matlab_test_data_collectors/python_testers/scanConversion_test.py index d813a33c..ccc2ed8f 100644 --- a/tests/matlab_test_data_collectors/python_testers/scanConversion_test.py +++ b/tests/matlab_test_data_collectors/python_testers/scanConversion_test.py @@ -1,10 +1,11 @@ -from kwave.utils import scan_conversion - -from scipy.io import loadmat -import numpy as np import os from pathlib import Path +import numpy as np +from scipy.io import loadmat + +from kwave.reconstruction.beamform import scan_conversion + def test_scanConversion(): collected_values_folder = os.path.join(Path(__file__).parent, 'collectedValues/scanConversion') diff --git a/tests/matlab_test_data_collectors/python_testers/test_interpcartdata.py b/tests/matlab_test_data_collectors/python_testers/test_interpcartdata.py index 03129465..481a5b89 100644 --- a/tests/matlab_test_data_collectors/python_testers/test_interpcartdata.py +++ b/tests/matlab_test_data_collectors/python_testers/test_interpcartdata.py @@ -3,7 +3,7 @@ import numpy as np import os -from kwave.utils.interputils import interp_cart_data +from kwave.utils.interp import interp_cart_data from kwave.kgrid import kWaveGrid diff --git a/tests/matlab_test_data_collectors/python_testers/test_resize.py b/tests/matlab_test_data_collectors/python_testers/test_resize.py index 9fc5b2e0..24279259 100644 --- a/tests/matlab_test_data_collectors/python_testers/test_resize.py +++ b/tests/matlab_test_data_collectors/python_testers/test_resize.py @@ -1,9 +1,10 @@ import os -from scipy.io import loadmat from pathlib import Path + import numpy as np +from scipy.io import loadmat -from kwave.utils import resize +from kwave.utils.matrix import resize def test_resize(): @@ -25,4 +26,4 @@ def test_resize(): assert np.allclose(expected_mat, resized_mat), f"Results do not match for {i + 1} dimensional case." - print('revolve2D(..) works as expected!') + print('revolve2d(..) works as expected!') diff --git a/tests/matlab_test_data_collectors/python_testers/unmaskSensorData_test.py b/tests/matlab_test_data_collectors/python_testers/unmaskSensorData_test.py index 48c32f0a..22e64ee9 100644 --- a/tests/matlab_test_data_collectors/python_testers/unmaskSensorData_test.py +++ b/tests/matlab_test_data_collectors/python_testers/unmaskSensorData_test.py @@ -1,11 +1,11 @@ +import os +from pathlib import Path from unittest.mock import Mock -from scipy.io import loadmat import numpy as np -import os -from pathlib import Path +from scipy.io import loadmat -from kwave.utils import unmask_sensor_data +from kwave.utils.signals import unmask_sensor_data def test_unmask_sensor_data(): diff --git a/tests/test_cpp_io_in_parts.py b/tests/test_cpp_io_in_parts.py index 25e37571..ae5367a0 100644 --- a/tests/test_cpp_io_in_parts.py +++ b/tests/test_cpp_io_in_parts.py @@ -4,15 +4,21 @@ This example demonstrates how to save the HDF5 input files required by the C++ code in parts. It builds on the Running C++ Simulations Example. """ -# noinspection PyUnresolvedReferences -import setup_test +import os from tempfile import gettempdir + import numpy as np +# noinspection PyUnresolvedReferences +import setup_test from kwave.kgrid import kWaveGrid -from kwave.utils import * +from kwave.utils.conversion import cast_to_type +from kwave.utils.interp import interpolate3d +from kwave.utils.io import get_h5_literals, write_matrix, write_attributes, write_flags, write_grid +from kwave.utils.mapgen import make_ball +from kwave.utils.matlab import matlab_find +from kwave.utils.tictoc import TicToc from tests.diff_utils import compare_against_ref -import os def test_cpp_io_in_parts(): diff --git a/tests/test_cpp_running_simulations.py b/tests/test_cpp_running_simulations.py index 19f73c13..a647f228 100755 --- a/tests/test_cpp_running_simulations.py +++ b/tests/test_cpp_running_simulations.py @@ -6,20 +6,23 @@ downloaded from http://www.k-wave.org/download.php and placed in the binaries folder of the toolbox. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from tempfile import gettempdir + import h5py import numpy as np -from tempfile import gettempdir -from kwave.ksource import kSource + +# noinspection PyUnresolvedReferences +import setup_test from kwave.kgrid import kWaveGrid +from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor +from kwave.ksource import kSource from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC, kspaceFirstOrder3DG -from kwave.utils import * +from kwave.utils.dotdictionary import dotdict +from kwave.utils.filters import filter_time_series +from kwave.utils.mapgen import make_ball from tests.diff_utils import compare_against_ref -from kwave.utils import dotdict -from kwave.kmedium import kWaveMedium def test_cpp_running_simulations(): diff --git a/tests/test_filterutils.py b/tests/test_filterutils.py index a512bb8c..aceff390 100644 --- a/tests/test_filterutils.py +++ b/tests/test_filterutils.py @@ -1,8 +1,12 @@ +from math import pi + import numpy as np -from kwave.utils.filterutils import gaussian, gaussian_filter, create_cw_signals, fwhm, envelope_detection, sharpness, \ + +from kwave.reconstruction.beamform import envelope_detection +from kwave.utils.filters import gaussian, fwhm, sharpness, \ smooth -from kwave.utils.kutils import get_win -from math import pi +from kwave.utils.signals import create_cw_signals +from kwave.utils.signals import get_win def test_gaussian(): diff --git a/tests/test_ivp_3D_simulation.py b/tests/test_ivp_3D_simulation.py index 349b1710..2cf95e94 100644 --- a/tests/test_ivp_3D_simulation.py +++ b/tests/test_ivp_3D_simulation.py @@ -6,18 +6,17 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils.maputils import make_ball -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.mapgen import make_ball from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_ivp_3D_simulation(): diff --git a/tests/test_ivp_axisymmetric_simulation.py b/tests/test_ivp_axisymmetric_simulation.py index df78fcb4..98a54506 100644 --- a/tests/test_ivp_axisymmetric_simulation.py +++ b/tests/test_ivp_axisymmetric_simulation.py @@ -6,18 +6,17 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrderAS import kspaceFirstOrderASC -from kwave.utils.maputils import make_disc -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.mapgen import make_disc from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_ivp_axisymmetric_simulation(): diff --git a/tests/test_ivp_binary_sensor_mask.py b/tests/test_ivp_binary_sensor_mask.py index 0b7ae536..cb1af324 100644 --- a/tests/test_ivp_binary_sensor_mask.py +++ b/tests/test_ivp_binary_sensor_mask.py @@ -6,6 +6,8 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ +import os +from copy import deepcopy from tempfile import gettempdir # noinspection PyUnresolvedReferences @@ -14,6 +16,7 @@ from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC from kwave.ktransducer import * +from kwave.utils.mapgen import make_disc, make_circle from tests.diff_utils import compare_against_ref @@ -21,20 +24,20 @@ def test_ivp_binary_sensor_mask(): pathname = gettempdir() # create the computational grid - Nx = 128 # number of grid points in the x (row) direction - Ny = 128 # number of grid points in the y (column) direction - dx = 0.1e-3 # grid point spacing in the x direction [m] - dy = 0.1e-3 # grid point spacing in the y direction [m] + Nx = 128 # number of grid points in the x (row) direction + Ny = 128 # number of grid points in the y (column) direction + dx = 0.1e-3 # grid point spacing in the x direction [m] + dy = 0.1e-3 # grid point spacing in the y direction [m] kgrid = kWaveGrid([Nx, Ny], [dx, dy]) # define the properties of the propagation medium medium = kWaveMedium(sound_speed=1500, alpha_coeff=0.75, alpha_power=1.5) # create initial pressure distribution using make_disc - disc_magnitude = 5 # [Pa] - disc_x_pos = 50 # [grid points] - disc_y_pos = 50 # [grid points] - disc_radius = 8 # [grid points] + disc_magnitude = 5 # [Pa] + disc_x_pos = 50 # [grid points] + disc_y_pos = 50 # [grid points] + disc_radius = 8 # [grid points] disc_1 = disc_magnitude * make_disc(Nx, Ny, disc_x_pos, disc_y_pos, disc_radius) disc_magnitude = 3 # [Pa] diff --git a/tests/test_ivp_comparison_modelling_functions.py b/tests/test_ivp_comparison_modelling_functions.py index 04aa0942..e3d81069 100644 --- a/tests/test_ivp_comparison_modelling_functions.py +++ b/tests/test_ivp_comparison_modelling_functions.py @@ -6,19 +6,19 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils import * -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.interp import cart2grid +from kwave.utils.mapgen import make_disc, make_cart_circle from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_ivp_comparison_modelling_functions(): diff --git a/tests/test_ivp_heterogeneous_medium.py b/tests/test_ivp_heterogeneous_medium.py index 4cf9fac6..913ad473 100644 --- a/tests/test_ivp_heterogeneous_medium.py +++ b/tests/test_ivp_heterogeneous_medium.py @@ -6,19 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.maputils import make_disc, make_cart_circle -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.mapgen import make_disc, make_cart_circle from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_ivp_heterogeneous_medium(): diff --git a/tests/test_ivp_homogeneous_medium.py b/tests/test_ivp_homogeneous_medium.py index 9b4c4573..ba446ec2 100644 --- a/tests/test_ivp_homogeneous_medium.py +++ b/tests/test_ivp_homogeneous_medium.py @@ -6,19 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.maputils import make_disc, make_cart_circle -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.mapgen import make_disc, make_cart_circle from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_ivp_homogeneous_medium(): diff --git a/tests/test_ivp_loading_external_image.py b/tests/test_ivp_loading_external_image.py index 9d8e7513..293262db 100644 --- a/tests/test_ivp_loading_external_image.py +++ b/tests/test_ivp_loading_external_image.py @@ -6,19 +6,19 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils import * -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.io import load_image +from kwave.utils.mapgen import make_cart_circle from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_ivp_loading_external_image(): diff --git a/tests/test_ivp_opposing_corners_sensor_mask.py b/tests/test_ivp_opposing_corners_sensor_mask.py index 4a444fff..7f9acca2 100644 --- a/tests/test_ivp_opposing_corners_sensor_mask.py +++ b/tests/test_ivp_opposing_corners_sensor_mask.py @@ -6,18 +6,17 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.maputils import make_disc -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.mapgen import make_disc from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_ivp_opposing_corners_sensor_mask(): diff --git a/tests/test_ivp_photoacoustic_waveforms.py b/tests/test_ivp_photoacoustic_waveforms.py index f400a24f..d2477df6 100644 --- a/tests/test_ivp_photoacoustic_waveforms.py +++ b/tests/test_ivp_photoacoustic_waveforms.py @@ -6,20 +6,19 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils.maputils import make_ball, make_disc -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.mapgen import make_ball, make_disc from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_ivp_photoacoustic_waveforms(): diff --git a/tests/test_ivp_sensor_frequency_response.py b/tests/test_ivp_sensor_frequency_response.py index 4c705f30..46390018 100644 --- a/tests/test_ivp_sensor_frequency_response.py +++ b/tests/test_ivp_sensor_frequency_response.py @@ -6,19 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.maputils import make_disc, make_cart_circle -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.mapgen import make_disc, make_cart_circle from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_ivp_sensor_frequency_response(): diff --git a/tests/test_kutils.py b/tests/test_kutils.py index 1582db2b..04727336 100644 --- a/tests/test_kutils.py +++ b/tests/test_kutils.py @@ -1,9 +1,10 @@ -from kwave.utils.kutils import check_stability, primefactors, tone_burst, get_alpha_filter, focus -from kwave import kWaveMedium, kWaveGrid -from kwave.ksource import kSource +from kwave import kWaveMedium from kwave.ktransducer import * - -import numpy as np +from kwave.reconstruction.beamform import focus +from kwave.utils.checks import check_stability +from kwave.utils.dotdictionary import dotdict +from kwave.utils.math import primefactors +from kwave.utils.signals import get_alpha_filter, tone_burst def test_check_stability(): @@ -133,98 +134,6 @@ def test_get_alpha_filters_1D(): get_alpha_filter(kgrid, medium, ['max']) -def test_focus(): - # simulation settings - DATA_CAST = 'single' - RUN_SIMULATION = True - - # ========================================================================= - # DEFINE THE K-WAVE GRID - # ========================================================================= - - # set the size of the perfectly matched layer (PML) - PML_X_SIZE = 20 # [grid points] - PML_Y_SIZE = 10 # [grid points] - PML_Z_SIZE = 10 # [grid points] - - # set total number of grid points not including the PML - Nx = 256 - 2 * PML_X_SIZE # [grid points] - Ny = 128 - 2 * PML_Y_SIZE # [grid points] - Nz = 128 - 2 * PML_Z_SIZE # [grid points] - - # set desired grid size in the x-direction not including the PML - x = 40e-3 # [m] - - # calculate the spacing between the grid points - dx = x / Nx # [m] - dy = dx # [m] - dz = dx # [m] - - # create the k-space grid - kgrid = kWaveGrid([Nx, Ny, Nz], [dx, dy, dz]) - - # ========================================================================= - # DEFINE THE MEDIUM PARAMETERS - # ========================================================================= - - # define the properties of the propagation medium - c0 = 1540 - rho0 = 1000 - - medium = kWaveMedium( - sound_speed=None, # will be set later - alpha_coeff=0.75, - alpha_power=1.5, - BonA=6 - ) - - # create the time array - t_end = (Nx * dx) * 2.2 / c0 # [s] - kgrid.makeTime(c0, t_end=t_end) - - # ========================================================================= - # DEFINE THE INPUT SIGNAL - # ========================================================================= - - # define properties of the input signal - source_strength = 1e6 # [Pa] - tone_burst_freq = 1.5e6 # [Hz] - tone_burst_cycles = 4 - - # create the input signal using tone_burst - input_signal = tone_burst(1 / kgrid.dt, tone_burst_freq, tone_burst_cycles) - - # scale the source magnitude by the source_strength divided by the - # impedance (the source is assigned to the particle velocity) - input_signal = (source_strength / (c0 * rho0)) * input_signal - - # ========================================================================= - # DEFINE THE ULTRASOUND TRANSDUCER - # ========================================================================= - - # physical properties of the transducer - transducer = dotdict() - transducer.number_elements = 32 # total number of transducer elements - transducer.element_width = 2 # width of each element [grid points/voxels] - transducer.element_length = 24 # length of each element [grid points/voxels] - transducer.element_spacing = 0 # spacing (kerf width) between the elements [grid points/voxels] - transducer.radius = float('inf') # radius of curvature of the transducer [m] - - # calculate the width of the transducer in grid points - transducer_width = transducer.number_elements * transducer.element_width + ( - transducer.number_elements - 1) * transducer.element_spacing - - # use this to position the transducer in the middle of the computational grid - transducer.position = np.round([1, Ny / 2 - transducer_width / 2, Nz / 2 - transducer.element_length / 2]) - - transducer = kWaveTransducerSimple(kgrid, **transducer) - - - source = kSource() - - focus(kgrid, input_signal, source.mask, [0.5, 0.5, 0.5], 1540) - - def test_focus(): # simulation settings DATA_CAST = 'single' diff --git a/tests/test_misc.py b/tests/test_misc.py index fc0be8ca..df2d0bb4 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,7 +1,9 @@ -from kwave.utils.misc import round_even, round_odd, find_closest, focused_bowl_oneil import numpy as np +from kwave.utils.mapgen import focused_bowl_oneil +from kwave.utils.math import round_odd, round_even, find_closest + def test_round_odd_down(): assert round_odd(3.9) == 3 diff --git a/tests/test_na_controlling_the_PML.py b/tests/test_na_controlling_the_PML.py index 188b7bb7..6997d5f2 100644 --- a/tests/test_na_controlling_the_PML.py +++ b/tests/test_na_controlling_the_PML.py @@ -6,19 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.maputils import make_disc, make_cart_circle -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.mapgen import make_disc, make_cart_circle from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_na_controlling_the_PML(): diff --git a/tests/test_na_optimising_performance.py b/tests/test_na_optimising_performance.py index 76edc872..350325e4 100644 --- a/tests/test_na_optimising_performance.py +++ b/tests/test_na_optimising_performance.py @@ -6,19 +6,21 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils import * -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.interp import cart2grid +from kwave.utils.io import load_image +from kwave.utils.mapgen import make_cart_circle +from kwave.utils.matrix import resize from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_na_optimising_performance(): diff --git a/tests/test_pmlutils.py b/tests/test_pmlutils.py index 03ae5299..3be98619 100644 --- a/tests/test_pmlutils.py +++ b/tests/test_pmlutils.py @@ -1,8 +1,8 @@ -from kwave.utils.pmlutils import get_optimal_pml_size from kwave.kgrid import kWaveGrid +from kwave.utils.pml import get_optimal_pml_size -def test_get_optimal_pml_size_1D(): +def test_get_optimal_pml_size_1d(): # size of the computational grid nx = 64 # number of grid points in the x (row) direction x = 1e-3 # size of the domain in the x direction [m] diff --git a/tests/test_pr_2D_FFT_line_sensor.py b/tests/test_pr_2D_FFT_line_sensor.py index d8e33aa4..3a7597dd 100644 --- a/tests/test_pr_2D_FFT_line_sensor.py +++ b/tests/test_pr_2D_FFT_line_sensor.py @@ -6,18 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils import * -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import smooth +from kwave.utils.mapgen import make_disc from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_pr_2D_FFT_line_sensor(): diff --git a/tests/test_pr_2D_TR_circular_sensor.py b/tests/test_pr_2D_TR_circular_sensor.py index a2879d16..33b4b2f8 100644 --- a/tests/test_pr_2D_TR_circular_sensor.py +++ b/tests/test_pr_2D_TR_circular_sensor.py @@ -6,19 +6,20 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ +import os from tempfile import gettempdir # noinspection PyUnresolvedReferences import setup_test - +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC from kwave.ktransducer import * -from kwave.utils import * +from kwave.utils.filters import smooth +from kwave.utils.io import load_image +from kwave.utils.mapgen import make_cart_circle +from kwave.utils.matrix import resize from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from kwave.utils import dotdict -import os def test_pr_2D_TR_circular_sensor(): diff --git a/tests/test_pr_2D_TR_directional_sensors.py b/tests/test_pr_2D_TR_directional_sensors.py index 37319380..903a388e 100644 --- a/tests/test_pr_2D_TR_directional_sensors.py +++ b/tests/test_pr_2D_TR_directional_sensors.py @@ -6,21 +6,20 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensorDirectivity from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.maputils import make_disc -from kwave.utils.filterutils import smooth -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import smooth +from kwave.utils.mapgen import make_disc from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_pr_2D_TR_directional_sensors(): diff --git a/tests/test_pr_2D_TR_line_sensor.py b/tests/test_pr_2D_TR_line_sensor.py index af066d05..128b9569 100644 --- a/tests/test_pr_2D_TR_line_sensor.py +++ b/tests/test_pr_2D_TR_line_sensor.py @@ -6,19 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.maputils import make_disc -from kwave.utils.filterutils import smooth -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import smooth +from kwave.utils.mapgen import make_disc from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_pr_2D_TR_line_sensor(): diff --git a/tests/test_pr_3D_FFT_planar_sensor.py b/tests/test_pr_3D_FFT_planar_sensor.py index ab584dce..4786c083 100644 --- a/tests/test_pr_3D_FFT_planar_sensor.py +++ b/tests/test_pr_3D_FFT_planar_sensor.py @@ -6,19 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir -from kwave.kgrid import kWaveGrid -from kwave.ksensor import kSensor +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils import * from kwave.ktransducer import * +from kwave.utils.filters import smooth +from kwave.utils.mapgen import make_ball from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_pr_3D_FFT_planar_sensor(): diff --git a/tests/test_sd_directional_array_elements.py b/tests/test_sd_directional_array_elements.py index 226cbd89..2c99a62c 100644 --- a/tests/test_sd_directional_array_elements.py +++ b/tests/test_sd_directional_array_elements.py @@ -6,6 +6,7 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ +import os from tempfile import gettempdir # noinspection PyUnresolvedReferences @@ -14,6 +15,9 @@ from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC from kwave.ktransducer import * +from kwave.utils.filters import filter_time_series +from kwave.utils.mapgen import make_circle +from kwave.utils.matlab import matlab_find, matlab_mask, unflatten_matlab_mask from tests.diff_utils import compare_against_ref diff --git a/tests/test_sd_directivity_modelling_2D.py b/tests/test_sd_directivity_modelling_2D.py index c9195349..b2a6354c 100644 --- a/tests/test_sd_directivity_modelling_2D.py +++ b/tests/test_sd_directivity_modelling_2D.py @@ -6,19 +6,21 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils import * -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import filter_time_series +from kwave.utils.interp import cart2grid +from kwave.utils.mapgen import make_cart_circle +from kwave.utils.matlab import matlab_find, unflatten_matlab_mask from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_sd_directivity_modelling_2D(): diff --git a/tests/test_sd_directivity_modelling_3D.py b/tests/test_sd_directivity_modelling_3D.py index d1db7668..f1f4d809 100644 --- a/tests/test_sd_directivity_modelling_3D.py +++ b/tests/test_sd_directivity_modelling_3D.py @@ -6,19 +6,21 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils.filterutils import * -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import * +from kwave.utils.interp import cart2grid +from kwave.utils.mapgen import make_cart_circle +from kwave.utils.matlab import matlab_find, unflatten_matlab_mask from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_sd_directivity_modelling_3D(): diff --git a/tests/test_sd_focussed_detector_2D.py b/tests/test_sd_focussed_detector_2D.py index bf55879d..13e5f1d7 100644 --- a/tests/test_sd_focussed_detector_2D.py +++ b/tests/test_sd_focussed_detector_2D.py @@ -6,6 +6,8 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ +import os +from copy import deepcopy from tempfile import gettempdir # noinspection PyUnresolvedReferences @@ -14,6 +16,7 @@ from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC from kwave.ktransducer import * +from kwave.utils.mapgen import make_disc, make_circle from tests.diff_utils import compare_against_ref diff --git a/tests/test_sd_sensor_directivity_2D.py b/tests/test_sd_sensor_directivity_2D.py index ce504554..01ed4303 100644 --- a/tests/test_sd_sensor_directivity_2D.py +++ b/tests/test_sd_sensor_directivity_2D.py @@ -6,20 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os -import numpy as np +from copy import deepcopy from tempfile import gettempdir -from kwave.kgrid import * + +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensorDirectivity from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils import dotdict from kwave.ktransducer import * from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_sd_sensor_directivity_2D(): diff --git a/tests/test_tvsp_3D_simulation.py b/tests/test_tvsp_3D_simulation.py index 0fc5f446..5cd4aefe 100644 --- a/tests/test_tvsp_3D_simulation.py +++ b/tests/test_tvsp_3D_simulation.py @@ -6,19 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils.filterutils import filter_time_series -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import filter_time_series from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_tvsp_3D_simulation(): diff --git a/tests/test_tvsp_doppler_effect.py b/tests/test_tvsp_doppler_effect.py index 4797f799..6cca975f 100644 --- a/tests/test_tvsp_doppler_effect.py +++ b/tests/test_tvsp_doppler_effect.py @@ -6,19 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os +from copy import deepcopy from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.filterutils import filter_time_series -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import filter_time_series from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium -from copy import deepcopy def test_tvsp_doppler_effect(): diff --git a/tests/test_tvsp_homogeneous_medium_dipole.py b/tests/test_tvsp_homogeneous_medium_dipole.py index 89b90b73..1bf5667a 100644 --- a/tests/test_tvsp_homogeneous_medium_dipole.py +++ b/tests/test_tvsp_homogeneous_medium_dipole.py @@ -6,18 +6,17 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.filterutils import filter_time_series -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import filter_time_series from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_tvsp_homogeneous_medium_dipole(): diff --git a/tests/test_tvsp_homogeneous_medium_monopole.py b/tests/test_tvsp_homogeneous_medium_monopole.py index 08b2b73b..f037aaea 100644 --- a/tests/test_tvsp_homogeneous_medium_monopole.py +++ b/tests/test_tvsp_homogeneous_medium_monopole.py @@ -6,18 +6,17 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.utils.filterutils import filter_time_series -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.filters import filter_time_series from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_tvsp_homogeneous_medium_monopole(): diff --git a/tests/test_us_beam_patterns.py b/tests/test_us_beam_patterns.py index 56d77b56..94a064ad 100644 --- a/tests/test_us_beam_patterns.py +++ b/tests/test_us_beam_patterns.py @@ -6,17 +6,17 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir -from kwave.kgrid import * + +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils.kutils import tone_burst -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.dotdictionary import dotdict +from kwave.utils.signals import tone_burst from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_us_beam_patterns(): diff --git a/tests/test_us_bmode_linear_transducer.py b/tests/test_us_bmode_linear_transducer.py index c8b35eb3..84a9a190 100644 --- a/tests/test_us_bmode_linear_transducer.py +++ b/tests/test_us_bmode_linear_transducer.py @@ -6,18 +6,18 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir + +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils.kutils import tone_burst -from kwave.utils.maputils import make_ball -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.dotdictionary import dotdict +from kwave.utils.mapgen import make_ball +from kwave.utils.signals import tone_burst from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium - """ Differences compared to original example: diff --git a/tests/test_us_bmode_phased_array.py b/tests/test_us_bmode_phased_array.py index e3bfa284..169f2b57 100644 --- a/tests/test_us_bmode_phased_array.py +++ b/tests/test_us_bmode_phased_array.py @@ -6,14 +6,19 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ +import os +from copy import deepcopy +from tempfile import gettempdir + # noinspection PyUnresolvedReferences import setup_test -import pytest -from tempfile import gettempdir +from kwave.kmedium import kWaveMedium from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC from kwave.ktransducer import * +from kwave.utils.dotdictionary import dotdict +from kwave.utils.mapgen import make_ball +from kwave.utils.signals import tone_burst from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium """ Differences compared to original example: diff --git a/tests/test_us_defining_transducer.py b/tests/test_us_defining_transducer.py index b1302f6f..343f246b 100644 --- a/tests/test_us_defining_transducer.py +++ b/tests/test_us_defining_transducer.py @@ -6,17 +6,17 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir -from kwave.kgrid import kWaveGrid + +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils.kutils import tone_burst -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.dotdictionary import dotdict +from kwave.utils.signals import tone_burst from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_us_defining_transducer(): @@ -82,7 +82,7 @@ def test_us_defining_transducer(): tone_burst_cycles = 5 # create the input signal using tone_burst - input_signal = tone_burst(1 / kgrid.dt, tone_burst_freq, tone_burst_cycles); + input_signal = tone_burst(1 / kgrid.dt, tone_burst_freq, tone_burst_cycles) # scale the source magnitude by the source_strength divided by the # impedance (the source is assigned to the particle velocity) diff --git a/tests/test_us_transducer_as_sensor.py b/tests/test_us_transducer_as_sensor.py index 209744fa..e5d5354a 100644 --- a/tests/test_us_transducer_as_sensor.py +++ b/tests/test_us_transducer_as_sensor.py @@ -6,19 +6,19 @@ structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples. """ -# noinspection PyUnresolvedReferences -import setup_test import os from tempfile import gettempdir +# noinspection PyUnresolvedReferences +import setup_test +from kwave.kmedium import kWaveMedium from kwave.ksource import kSource from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.utils.kutils import tone_burst -from kwave.utils.maputils import make_ball -from kwave.utils import dotdict from kwave.ktransducer import * +from kwave.utils.dotdictionary import dotdict +from kwave.utils.mapgen import make_ball +from kwave.utils.signals import tone_burst from tests.diff_utils import compare_against_ref -from kwave.kmedium import kWaveMedium def test_us_transducer_as_sensor(): diff --git a/tests/test_utils.py b/tests/test_utils.py index 1acd2adf..1d496955 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,16 +1,16 @@ -from kwave.utils.checkutils import num_dim -from kwave.utils.maputils import hounsfield2density, fit_power_law_params, power_law_kramers_kronig, make_cart_circle, make_cart_sphere -from kwave.utils.conversionutils import db2neper, neper2db -from kwave.utils.kutils import tone_burst, add_noise, gradient_spect -from kwave.utils.interputils import get_bli -from kwave.utils.filterutils import extract_amp_phase, spect, apply_filter -from kwave.utils.matrixutils import gradient_fd, resize -from kwave.utils.ioutils import load_image import numpy as np -from utils import * import pytest from phantominator import shepp_logan +from kwave.utils.checks import num_dim +from kwave.utils.conversion import db2neper, neper2db +from kwave.utils.filters import extract_amp_phase, spect, apply_filter +from kwave.utils.interp import get_bli +from kwave.utils.mapgen import hounsfield2density, fit_power_law_params, power_law_kramers_kronig, make_cart_circle, \ + make_cart_sphere +from kwave.utils.matrix import gradient_fd, resize +from kwave.utils.signals import tone_burst, add_noise, gradient_spect + input_signal = np.array([0., 0.00099663, 0.00646706, 0.01316044, 0.01851998, 0.02355139, 0.02753738, 0.02966112, 0.02907933, 0.02502059, 0.01690274, 0.00445909, -0.01214073, -0.03219275, -0.05440197, diff --git a/tests/test_water.py b/tests/test_water.py index 609feeea..897a30bf 100644 --- a/tests/test_water.py +++ b/tests/test_water.py @@ -1,4 +1,4 @@ -from kwave.utils import water_sound_speed, water_non_linearity, water_density, water_absorption +from kwave.utils.mapgen import water_sound_speed, water_non_linearity, water_density, water_absorption def test_water_absorption():