-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add us_defining_transducer * add to list of examples * remove unrequired imports * add beam pattern example upto broken spect call * burst freq to 500Hz * working beam pattern example * add README.md * fixes to us_beam_patterns * fix linting issues raised by ruff * update us_beam_pattern to use spect * remove useless expression * update notebook * update color map and default values --------- Co-authored-by: Walter Simson <[email protected]> Co-authored-by: Walter Simson <[email protected]>
- Loading branch information
1 parent
4fa273f
commit bfd998b
Showing
9 changed files
with
654 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
|
||
# Virtual environments | ||
.venv/ | ||
venv/ | ||
|
||
# Compiled files | ||
*.pyc | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Simulating Ultrasound Beam Patterns Example | ||
|
||
[](https://colab.research.google.com/github/waltsims/k-wave-python/blob/master/examples/us_beam_patterns/us_beam_patterns.ipynb) | ||
|
||
This example shows how the nonlinear beam pattern from an ultrasound transducer can be modelled. It builds on the Defining An Ultrasound Transducer and Simulating Transducer Field Patterns examples. | ||
|
||
To read more, visit the [original example page](http://www.k-wave.org/documentation/example_us_beam_patterns.php). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,346 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"%%capture\n", | ||
"# !pip install k-wave-python" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import matplotlib.pyplot as plt\n", | ||
"import numpy as np\n", | ||
"\n", | ||
"from kwave.data import Vector\n", | ||
"from kwave.kgrid import kWaveGrid\n", | ||
"from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", | ||
"from kwave.ksensor import kSensor\n", | ||
"from kwave.ktransducer import kWaveTransducerSimple, NotATransducer\n", | ||
"from kwave.kWaveSimulation import SimulationOptions\n", | ||
"from kwave.kmedium import kWaveMedium\n", | ||
"from kwave.utils.filters import spect\n", | ||
"\n", | ||
"from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", | ||
"from kwave.utils.dotdictionary import dotdict\n", | ||
"from kwave.utils.math import find_closest\n", | ||
"from kwave.utils.signals import tone_burst" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# simulation settings\n", | ||
"DATA_CAST = \"single\"\n", | ||
"# set to 'xy' or 'xz' to generate the beam pattern in different planes\n", | ||
"MASK_PLANE = \"xy\"\n", | ||
"# set to true to compute the rms or peak beam patterns, set to false to compute the harmonic beam patterns\n", | ||
"USE_STATISTICS = True\n", | ||
"\n", | ||
"# define the grid\n", | ||
"PML_X_SIZE = 20\n", | ||
"PML_Y_SIZE = 10\n", | ||
"PML_Z_SIZE = 10\n", | ||
"Nx = 128 - 2 * PML_X_SIZE\n", | ||
"Ny = 64 - 2 * PML_Y_SIZE\n", | ||
"Nz = 64 - 2 * PML_Z_SIZE\n", | ||
"x = 40e-3\n", | ||
"dx = x / Nx\n", | ||
"dy = dx\n", | ||
"dz = dx\n", | ||
"\n", | ||
"kgrid = kWaveGrid([Nx, Ny, Nz], [dx, dy, dz])\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# define the medium\n", | ||
"medium = kWaveMedium(sound_speed=1540, density=1000, alpha_coeff=0.75, alpha_power=1.5, BonA=6)\n", | ||
"\n", | ||
"# create the time array - using a time == time to travel the hypot of the grid\n", | ||
"t_end = 45e-6 # alternatively, use np.sqrt(kgrid.x_size ** 2 + kgrid.y_size ** 2) / medium.sound_speed\n", | ||
"\n", | ||
"kgrid.makeTime(medium.sound_speed, t_end=t_end)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# define the input signal\n", | ||
"source_strength = 1e6\n", | ||
"tone_burst_freq = 0.5e6\n", | ||
"tone_burst_cycles = 5\n", | ||
"input_signal = tone_burst(1 / kgrid.dt, tone_burst_freq, tone_burst_cycles)\n", | ||
"input_signal = (source_strength / (medium.sound_speed * medium.density)) * input_signal\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# define the transducer\n", | ||
"transducer = dotdict()\n", | ||
"transducer.number_elements = 32\n", | ||
"transducer.element_width = 1\n", | ||
"transducer.element_length = 12\n", | ||
"transducer.element_spacing = 0\n", | ||
"transducer.radius = np.inf\n", | ||
"\n", | ||
"# calculate the width of the transducer in grid points\n", | ||
"transducer_width = transducer.number_elements * transducer.element_width + (transducer.number_elements - 1) * transducer.element_spacing\n", | ||
"\n", | ||
"# use this to position the transducer in the middle of the computational grid\n", | ||
"transducer.position = np.round([1, Ny / 2 - transducer_width / 2, Nz / 2 - transducer.element_length / 2])\n", | ||
"transducer = kWaveTransducerSimple(kgrid, **transducer)\n", | ||
"\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"not_transducer = dotdict()\n", | ||
"not_transducer.sound_speed = medium.sound_speed # sound speed [m/s]\n", | ||
"not_transducer.focus_distance = 20e-3 # focus distance [m]\n", | ||
"not_transducer.elevation_focus_distance = 19e-3 # focus distance in the elevation plane [m]\n", | ||
"not_transducer.steering_angle = 0 # steering angle [degrees]\n", | ||
"not_transducer.transmit_apodization = \"Rectangular\"\n", | ||
"not_transducer.receive_apodization = \"Rectangular\"\n", | ||
"not_transducer.active_elements = np.ones((transducer.number_elements, 1))\n", | ||
"not_transducer.input_signal = input_signal\n", | ||
"\n", | ||
"not_transducer = NotATransducer(transducer, kgrid, **not_transducer)\n", | ||
"\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"sensor_mask = np.zeros((Nx, Ny, Nz))\n", | ||
"\n", | ||
"if MASK_PLANE == \"xy\":\n", | ||
" sensor_mask[:, :, Nz // 2] = 1\n", | ||
" # store y axis properties\n", | ||
" Nj = Ny\n", | ||
" j_vec = kgrid.y_vec\n", | ||
" j_label = \"y\"\n", | ||
"elif MASK_PLANE == \"xz\":\n", | ||
" sensor_mask[:, Ny // 2, :] = 1\n", | ||
" # store z axis properties\n", | ||
" Nj = Nz\n", | ||
" j_vec = kgrid.z_vec\n", | ||
" j_label = \"z\"\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"sensor = kSensor(sensor_mask)\n", | ||
"if USE_STATISTICS:\n", | ||
" sensor.record = [\"p_rms\", \"p_max\"]\n", | ||
"\n", | ||
"simulation_options = SimulationOptions(\n", | ||
" pml_inside=False,\n", | ||
" pml_size=Vector([PML_X_SIZE, PML_Y_SIZE, PML_Z_SIZE]),\n", | ||
" data_cast=DATA_CAST,\n", | ||
" save_to_disk=True,\n", | ||
")\n", | ||
"\n", | ||
"if not USE_STATISTICS:\n", | ||
" simulation_options.stream_to_disk = \"harmonic_data.h5\"\n", | ||
"\n", | ||
"execution_options = SimulationExecutionOptions(is_gpu_simulation=True)\n", | ||
"\n", | ||
"sensor_data = kspaceFirstOrder3D(\n", | ||
" medium=medium,\n", | ||
" kgrid=kgrid,\n", | ||
" source=not_transducer,\n", | ||
" sensor=sensor,\n", | ||
" simulation_options=simulation_options,\n", | ||
" execution_options=execution_options,\n", | ||
")\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"if USE_STATISTICS:\n", | ||
" fig, axes = plt.subplots(1, 2)\n", | ||
" fig.set_figwidth(8)\n", | ||
" fig.set_figheight(6)\n", | ||
"\n", | ||
" for ind, measures in enumerate(sensor.record):\n", | ||
" im1 = axes[ind].imshow(\n", | ||
" sensor_data[measures].reshape([Nj, Nx]).T * 1e-6,\n", | ||
" extent=[\n", | ||
" min(j_vec * 1e3),\n", | ||
" max(j_vec * 1e3),\n", | ||
" min((kgrid.x_vec - min(kgrid.x_vec)) * 1e3),\n", | ||
" max((kgrid.x_vec - min(kgrid.x_vec)) * 1e3),\n", | ||
" ],\n", | ||
" aspect=\"auto\",\n", | ||
" cmap=\"jet\",\n", | ||
" )\n", | ||
" axes[ind].set_xlabel(\"y-position (mm)\")\n", | ||
" axes[ind].set_ylabel(\"x-position (mm)\")\n", | ||
" title_text = f\"Total Beam Pattern Using {str.upper(measures.split('_')[1])} \\n of Recorded Pressure\"\n", | ||
" axes[ind].set_title(title_text)\n", | ||
" axes[ind].set_yticks(axes[ind].get_yticks().tolist())\n", | ||
" axes[ind].set_yticklabels(axes[ind].get_yticklabels()[::-1])\n", | ||
" axes[ind].set_xlim([-10, 10])\n", | ||
" fig.colorbar(im1, label=\"Pressure [MPa]\", orientation=\"vertical\")\n", | ||
"\n", | ||
" plt.tight_layout()\n", | ||
" plt.show()" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"if not USE_STATISTICS:\n", | ||
" sensor_data_array = np.reshape(sensor_data[\"p\"], [kgrid.Nt, kgrid.Ny, kgrid.Nx]).transpose(2, 1, 0)\n", | ||
" # compute the amplitude spectrum\n", | ||
" [freq, amp_spect, _] = spect(sensor_data_array, 1 / kgrid.dt, dim=2)\n", | ||
"\n", | ||
" # compute the index at which the source frequency and its harmonics occur\n", | ||
" [f1_value, f1_index] = find_closest(freq, tone_burst_freq)\n", | ||
" [f2_value, f2_index] = find_closest(freq, 2 * tone_burst_freq)\n", | ||
"\n", | ||
" # extract the amplitude at the source frequency and store\n", | ||
" beam_pattern_f1 = np.squeeze(amp_spect[:, :, f1_index])\n", | ||
"\n", | ||
" # extract the amplitude at the second harmonic and store\n", | ||
" beam_pattern_f2 = np.squeeze(amp_spect[:, :, f2_index])\n", | ||
"\n", | ||
" # extract the integral of the total amplitude spectrum\n", | ||
" beam_pattern_total = np.squeeze(np.sum(amp_spect, axis=2))\n", | ||
"\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"if not USE_STATISTICS:\n", | ||
" fig, axes = plt.subplots(1, 3)\n", | ||
" fig.set_figwidth(8)\n", | ||
" fig.set_figheight(6)\n", | ||
"\n", | ||
" for ind, measure in enumerate([beam_pattern_f1, beam_pattern_f2, beam_pattern_total]):\n", | ||
" im1 = axes[ind].imshow(\n", | ||
" np.squeeze(measure) * 1e-6,\n", | ||
" extent=[\n", | ||
" min(j_vec * 1e3),\n", | ||
" max(j_vec * 1e3),\n", | ||
" min((kgrid.x_vec - min(kgrid.x_vec)) * 1e3),\n", | ||
" max((kgrid.x_vec - min(kgrid.x_vec)) * 1e3),\n", | ||
" ],\n", | ||
" aspect=\"auto\",\n", | ||
" cmap=\"jet\",\n", | ||
" )\n", | ||
" axes[ind].set_xlabel(\"y-position (mm)\")\n", | ||
" axes[ind].set_ylabel(\"x-position (mm)\")\n", | ||
"\n", | ||
" axes[ind].set_yticks(axes[ind].get_yticks().tolist())\n", | ||
" axes[ind].set_yticklabels(axes[ind].get_yticklabels()[::-1])\n", | ||
"\n", | ||
" axes[0].set_title(\"Beam Pattern \\nAt Source Fundamental\")\n", | ||
" axes[1].set_title(\"Beam Pattern \\nAt Second Harmonic\")\n", | ||
" axes[2].set_title(\"Total Beam Pattern\\nUsing Integral Of Recorded Pressure\")\n", | ||
"\n", | ||
" plt.tight_layout()\n", | ||
" plt.show()\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"if not USE_STATISTICS:\n", | ||
" # Compute the directivity at each of the harmonics\n", | ||
" directivity_f1 = beam_pattern_f1[round(not_transducer.focus_distance / dx), :]\n", | ||
" directivity_f2 = beam_pattern_f2[round(not_transducer.focus_distance / dx), :]\n", | ||
"\n", | ||
" # Normalize the directivity\n", | ||
" directivity_f1 /= np.max(directivity_f1)\n", | ||
" directivity_f2 /= np.max(directivity_f2)\n", | ||
"\n", | ||
" # Compute relative angles from the transducer\n", | ||
" if MASK_PLANE == \"xy\":\n", | ||
" horz_axis = ((np.arange(Ny) + 1) - Ny / 2) * dy\n", | ||
" else:\n", | ||
" horz_axis = ((np.arange(Nz) + 1) - Nz / 2) * dz\n", | ||
"\n", | ||
" angles = 180 * np.arctan2(horz_axis, not_transducer.focus_distance) / np.pi\n", | ||
"\n", | ||
" # Plot the directivity\n", | ||
" plt.figure()\n", | ||
" plt.plot(angles, directivity_f1, \"k-\", label=\"Fundamental\")\n", | ||
" plt.plot(angles, directivity_f2, \"k--\", label=\"Second Harmonic\")\n", | ||
" plt.axis(\"tight\")\n", | ||
" plt.xlabel(\"Angle [deg]\")\n", | ||
" plt.ylabel(\"Normalized Amplitude\")\n", | ||
" plt.legend(loc=2)\n", | ||
" plt.xlim([-25, 25])\n", | ||
" plt.show()" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 2 | ||
} |
Oops, something went wrong.