From 1ac01dee20100dd3a46d991259f56f92e1c9af83 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:11:19 -0700 Subject: [PATCH 01/21] Add beartype to kspaceFirstOrder, convert Cartesian sensor masks for cpp backend - @typechecker on kspaceFirstOrder() catches wrong positional arg order - Auto-convert Cartesian sensor masks to binary via cart2grid for cpp backend (python backend handles Cartesian masks natively via interpolation) Co-Authored-By: Claude Opus 4.6 (1M context) --- kwave/kspaceFirstOrder.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/kwave/kspaceFirstOrder.py b/kwave/kspaceFirstOrder.py index 806c1336a..da8eb8158 100644 --- a/kwave/kspaceFirstOrder.py +++ b/kwave/kspaceFirstOrder.py @@ -3,6 +3,7 @@ from typing import Optional, Union import numpy as np +from beartype import beartype as typechecker from kwave.kgrid import kWaveGrid from kwave.kmedium import kWaveMedium @@ -25,6 +26,7 @@ def _normalize_pml(val, ndim, name="pml_size"): return t + (t[-1],) * (ndim - len(t)) +@typechecker def kspaceFirstOrder( kgrid: kWaveGrid, medium: kWaveMedium, @@ -143,6 +145,15 @@ def kspaceFirstOrder( stacklevel=2, ) + # Convert Cartesian sensor mask to binary grid (cpp binary requires binary masks) + if sensor is not None and sensor.mask is not None: + mask_arr = np.asarray(sensor.mask) + if mask_arr.ndim == 2 and mask_arr.shape[0] == kgrid.dim: + from kwave.utils.conversion import cart2grid + + sensor = copy.copy(sensor) + sensor.mask, _, _ = cart2grid(kgrid, mask_arr) + # Apply p0 smoothing before HDF5 serialization (matches MATLAB legacy path) if smooth_p0 and source.p0 is not None and kgrid.dim >= 2: from kwave.utils.filters import smooth From bed8f7f0989b53a9961e2ab8a3d56dab3871b551 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:16:13 -0700 Subject: [PATCH 02/21] Port all examples to kspaceFirstOrder() unified API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace kspaceFirstOrder2DC/3D with kspaceFirstOrder() - Remove SimulationOptions/SimulationExecutionOptions usage - Map is_gpu_simulation → device="gpu"|"cpu", save_to_disk → implicit for backend="cpp" - Drop data_cast="single" (handled internally) - Note unsupported features: pml_inside, stream_to_disk, checkpointing - Time-reversal examples keep legacy imports (TimeReversal class requires them) - checkpointing example unchanged (uses checkpoint_file/checkpoint_timesteps not yet in new API) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../at_array_as_sensor/at_array_as_sensor.py | 19 +- .../at_array_as_source/at_array_as_source.py | 12 +- .../at_circular_piston_3D.py | 410 ++++++++-------- .../at_circular_piston_AS.py | 460 +++++++++-------- .../at_focused_annular_array_3D.py | 424 ++++++++-------- .../at_focused_bowl_3D/at_focused_bowl_3D.py | 462 +++++++++--------- .../at_focused_bowl_AS/at_focused_bowl_AS.py | 460 +++++++++-------- .../at_linear_array_transducer.py | 24 +- .../ivp_photoacoustic_waveforms.py | 79 +-- .../pr_2D_FFT_line_sensor.py | 21 +- .../pr_2D_TR_line_sensor.py | 36 +- .../pr_3D_FFT_planar_sensor.py | 21 +- .../pr_3D_TR_planar_sensor.py | 35 +- .../sd_directivity_modelling_2D.py | 26 +- .../sd_focussed_detector_2D.py | 47 +- .../sd_focussed_detector_3D.py | 43 +- examples/us_beam_patterns/us_beam_patterns.py | 35 +- .../us_bmode_linear_transducer.py | 33 +- .../us_bmode_phased_array.py | 34 +- .../us_defining_transducer.py | 33 +- 20 files changed, 1281 insertions(+), 1433 deletions(-) diff --git a/examples/at_array_as_sensor/at_array_as_sensor.py b/examples/at_array_as_sensor/at_array_as_sensor.py index a93458cae..0d5666032 100644 --- a/examples/at_array_as_sensor/at_array_as_sensor.py +++ b/examples/at_array_as_sensor/at_array_as_sensor.py @@ -7,9 +7,7 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.utils.colormap import get_color_map from kwave.utils.conversion import cart2grid from kwave.utils.kwave_array import kWaveArray @@ -54,13 +52,8 @@ def main(): logical_p0 = source.p0.astype(bool) sensor = kSensor() sensor.mask = element_pos - simulation_options = SimulationOptions( - save_to_disk=True, - data_cast="single", - ) - - execution_options = SimulationExecutionOptions(is_gpu_simulation=True) - output = kspaceFirstOrder2D(kgrid, source, sensor, medium, simulation_options, execution_options) + # NOTE: data_cast="single" not supported in new API + output = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp", device="gpu") # Reorder the sensor data returned by k-Wave to match the order of the elements in the array _, _, reorder_index = cart2grid(kgrid, element_pos) sensor_data_point = reorder_binary_sensor_data(output["p"].T, reorder_index=reorder_index) @@ -68,7 +61,7 @@ def main(): # assign binary mask from karray to the source mask sensor.mask = karray.get_array_binary_mask(kgrid) - output = kspaceFirstOrder2D(kgrid, source, sensor, medium, simulation_options, execution_options) + output = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp", device="gpu") sensor_data = output["p"].T combined_sensor_data = karray.combine_sensor_data(kgrid, sensor_data) @@ -76,8 +69,8 @@ def main(): # VISUALIZATION # ========================================================================= - # create pml mask (reuse default size of 20 grid points from simulation_options) - pml_size = simulation_options.pml_x_size # 20 [grid_points] + # create pml mask (default size of 20 grid points) + pml_size = 20 # [grid_points] pml_mask = np.zeros((N.x, N.y), dtype=bool) pml_mask[:pml_size, :] = 1 pml_mask[:, :pml_size] = 1 diff --git a/examples/at_array_as_source/at_array_as_source.py b/examples/at_array_as_source/at_array_as_source.py index 9ebbb7318..b90a0be85 100644 --- a/examples/at_array_as_source/at_array_as_source.py +++ b/examples/at_array_as_source/at_array_as_source.py @@ -8,9 +8,7 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspace_first_order_2d_gpu -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.utils.colormap import get_color_map from kwave.utils.kwave_array import kWaveArray from kwave.utils.signals import tone_burst @@ -87,11 +85,7 @@ def main(): # source signal for each grid point that forms part of the source) source_p = karray.get_distributed_source_signal(kgrid, source_signal) - simulation_options = SimulationOptions( - save_to_disk=True, - data_cast="single", - ) - execution_options = SimulationExecutionOptions(is_gpu_simulation=True) + # NOTE: data_cast="single" not supported in new API # run k-Wave simulation (no sensor is used for this example) # TODO: I would say proper behaviour would be to return the entire pressure field if sensor is None sensor = kSensor() @@ -101,7 +95,7 @@ def main(): source.p_mask = source_p_mask source.p = source_p - p = kspace_first_order_2d_gpu(kgrid, source, sensor, medium, simulation_options, execution_options) + p = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp", device="gpu") p_field = np.reshape(p["p"], (kgrid.Nt, Nx, Ny)) p_field = np.transpose(p_field, (0, 2, 1)) diff --git a/examples/at_circular_piston_3D/at_circular_piston_3D.py b/examples/at_circular_piston_3D/at_circular_piston_3D.py index 4d7bc1398..8e11b9a55 100644 --- a/examples/at_circular_piston_3D/at_circular_piston_3D.py +++ b/examples/at_circular_piston_3D/at_circular_piston_3D.py @@ -1,207 +1,203 @@ -from copy import deepcopy - -import matplotlib.pyplot as plt -import numpy as np - -from kwave.data import Vector -from kwave.kgrid import kWaveGrid -from kwave.kmedium import kWaveMedium -from kwave.ksensor import kSensor -from kwave.ksource import kSource -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions -from kwave.utils.filters import extract_amp_phase -from kwave.utils.kwave_array import kWaveArray -from kwave.utils.math import round_even -from kwave.utils.signals import create_cw_signals - -# material values -c0: float = 1500.0 # sound speed [m/s] -rho0: float = 1000.0 # density [kg/m^3] - -# source parameters -source_f0: float = 1e6 # source frequency [Hz] -source_diam: float = 10e-3 # piston diameter [m] -source_amp: float = 1e6 # source pressure [Pa] -source_phase: float = 0.0 # source pressure [Pa] - -# grid parameters -axial_size: float = 32e-3 # total grid size in the axial dimension [m] -lateral_size: float = 23e-3 # total grid size in the lateral dimension [m] - -# computational parameters -ppw = 3 # number of points per wavelength -t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state) -record_periods: int = 1 # number of periods to record -cfl: float = 0.5 # CFL number -bli_tolerance: float = 0.03 # tolerance for truncation of the off-grid source points -upsampling_rate: int = 10 # density of integration points relative to grid - -verbose: bool = False - -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - -# calculate the grid spacing based on the PPW and F0 -dx: float = c0 / (ppw * source_f0) # [m] - -# compute the size of the grid -# is round_even needed? -Nx: int = round_even(axial_size / dx) -Ny: int = round_even(lateral_size / dx) -Nz: int = Ny - -grid_size_points = Vector([Nx, Ny, Nz]) -grid_spacing_meters = Vector([dx, dx, dx]) - -# create the k-space grid -kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) - -# compute points per temporal period -ppp: int = round(ppw / cfl) - -# compute corresponding time spacing -dt: float = 1.0 / (ppp * source_f0) - -# create the time array using an integer number of points per period -Nt: int = int(np.round(t_end / dt)) -kgrid.setTime(Nt, dt) - -# calculate the actual CFL and PPW -if verbose: - print("PPW = " + str(c0 / (dx * source_f0))) - print("CFL = " + str(c0 * dt / dx)) - -# -------------------- -# SOURCE -# -------------------- - -source = kSource() - -# create time varying source -source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, np.array([source_amp]), np.array([source_phase])) - -# create empty kWaveArray -karray = kWaveArray(bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate) - -# add disc shaped element at one end of the grid -karray.add_disc_element([kgrid.x_vec[0].item(), 0.0, 0.0], source_diam, [0.0, 0.0, 0.0]) - -# assign binary mask -source.p_mask = karray.get_array_binary_mask(kgrid) - -# assign source signals -source.p = karray.get_distributed_source_signal(kgrid, source_sig) - -# -------------------- -# MEDIUM -# -------------------- - -# assign medium properties -medium = kWaveMedium(sound_speed=c0, density=rho0) - -# -------------------- -# SENSOR -# -------------------- - -sensor = kSensor() - -# set sensor mask to record central plane, not including the source point -sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool) -sensor.mask[1:, :, Nz // 2] = True - -# record the pressure -sensor.record = ["p"] - -# record only the final few periods when the field is in steady state -sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1 - -# -------------------- -# SIMULATION -# -------------------- - -simulation_options = SimulationOptions(pml_auto=True, data_recast=True, save_to_disk=True, save_to_disk_exit=False, pml_inside=False) - -execution_options = SimulationExecutionOptions(is_gpu_simulation=True, delete_data=False, verbose_level=2) - -sensor_data = kspaceFirstOrder3D( - medium=deepcopy(medium), - kgrid=deepcopy(kgrid), - source=deepcopy(source), - sensor=deepcopy(sensor), - simulation_options=simulation_options, - execution_options=execution_options, -) - -# extract amplitude from the sensor data -amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") - -# reshape data -amp = np.reshape(amp, (Nx - 1, Ny), order="F") - -# extract pressure on axis -amp_on_axis = amp[:, Ny // 2] - -# define axis vectors for plotting -x_vec = kgrid.x_vec[1:, :] - kgrid.x_vec[0] -y_vec = kgrid.y_vec - -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - -# calculate the wavenumber -k: float = 2.0 * np.pi * source_f0 / c0 - -# define radius and axis -a: float = source_diam / 2.0 -x_max: float = (Nx - 1) * dx -delta_x: float = x_max / 10000.0 -x_ref: float = np.arange(0.0, x_max + delta_x, delta_x, dtype=float) - -# calculate the analytical solution for a piston in an infinite baffle -# for comparison (Eq 5-7.3 in Pierce) -r_ref = np.sqrt(x_ref**2 + a**2) -p_ref = source_amp * np.abs(2.0 * np.sin((k * r_ref - k * x_ref) / 2.0)) - -# get analytical solution at exactly the same points as k-Wave -r = np.sqrt(x_vec**2 + a**2) -p_ref_kw = source_amp * np.abs(2.0 * np.sin((k * r - k * x_vec) / 2.0)) - -# calculate error -L2_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=2) -Linf_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=np.inf) -# ========================================================================= -# VISUALISATION -# ========================================================================= - -# plot the pressure along the focal axis of the piston -fig1, ax1 = plt.subplots(1, 1) -ax1.plot(1e3 * x_ref, 1e-6 * p_ref, "k-", label="Exact") -ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") -ax1.legend() -ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") -ax1.set_xlim(0.0, 1e3 * axial_size) -ax1.set_ylim(0.0, 1.1 * source_amp * 2e-6) -ax1.grid() - -# plot the source mask (pml is outside the grid in this example) -fig2, ax2 = plt.subplots(1, 1) -ax2.pcolormesh( - 1e3 * np.squeeze(kgrid.y_vec), 1e3 * np.squeeze(kgrid.x_vec), np.flip(source.p_mask[:, :, Nz // 2], axis=0), shading="nearest" -) -ax2.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") - -# plot the pressure field -fig3, ax3 = plt.subplots(1, 1) -ax3.pcolormesh(1e3 * np.squeeze(y_vec), 1e3 * np.squeeze(x_vec), np.flip(amp, axis=1), shading="gouraud") -ax3.set(xlabel="Lateral Position [mm]", ylabel="Axial Position [mm]", title="Pressure Field") -ax3.set_ylim(1e3 * x_vec[-1], 1e3 * x_vec[0]) - -plt.show() +from copy import deepcopy + +import matplotlib.pyplot as plt +import numpy as np + +from kwave.data import Vector +from kwave.kgrid import kWaveGrid +from kwave.kmedium import kWaveMedium +from kwave.ksensor import kSensor +from kwave.ksource import kSource +from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.utils.filters import extract_amp_phase +from kwave.utils.kwave_array import kWaveArray +from kwave.utils.math import round_even +from kwave.utils.signals import create_cw_signals + +# material values +c0: float = 1500.0 # sound speed [m/s] +rho0: float = 1000.0 # density [kg/m^3] + +# source parameters +source_f0: float = 1e6 # source frequency [Hz] +source_diam: float = 10e-3 # piston diameter [m] +source_amp: float = 1e6 # source pressure [Pa] +source_phase: float = 0.0 # source pressure [Pa] + +# grid parameters +axial_size: float = 32e-3 # total grid size in the axial dimension [m] +lateral_size: float = 23e-3 # total grid size in the lateral dimension [m] + +# computational parameters +ppw = 3 # number of points per wavelength +t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state) +record_periods: int = 1 # number of periods to record +cfl: float = 0.5 # CFL number +bli_tolerance: float = 0.03 # tolerance for truncation of the off-grid source points +upsampling_rate: int = 10 # density of integration points relative to grid + +verbose: bool = False + +# ========================================================================= +# RUN SIMULATION +# ========================================================================= + +# -------------------- +# GRID +# -------------------- + +# calculate the grid spacing based on the PPW and F0 +dx: float = c0 / (ppw * source_f0) # [m] + +# compute the size of the grid +# is round_even needed? +Nx: int = round_even(axial_size / dx) +Ny: int = round_even(lateral_size / dx) +Nz: int = Ny + +grid_size_points = Vector([Nx, Ny, Nz]) +grid_spacing_meters = Vector([dx, dx, dx]) + +# create the k-space grid +kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) + +# compute points per temporal period +ppp: int = round(ppw / cfl) + +# compute corresponding time spacing +dt: float = 1.0 / (ppp * source_f0) + +# create the time array using an integer number of points per period +Nt: int = int(np.round(t_end / dt)) +kgrid.setTime(Nt, dt) + +# calculate the actual CFL and PPW +if verbose: + print("PPW = " + str(c0 / (dx * source_f0))) + print("CFL = " + str(c0 * dt / dx)) + +# -------------------- +# SOURCE +# -------------------- + +source = kSource() + +# create time varying source +source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, np.array([source_amp]), np.array([source_phase])) + +# create empty kWaveArray +karray = kWaveArray(bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate) + +# add disc shaped element at one end of the grid +karray.add_disc_element([kgrid.x_vec[0].item(), 0.0, 0.0], source_diam, [0.0, 0.0, 0.0]) + +# assign binary mask +source.p_mask = karray.get_array_binary_mask(kgrid) + +# assign source signals +source.p = karray.get_distributed_source_signal(kgrid, source_sig) + +# -------------------- +# MEDIUM +# -------------------- + +# assign medium properties +medium = kWaveMedium(sound_speed=c0, density=rho0) + +# -------------------- +# SENSOR +# -------------------- + +sensor = kSensor() + +# set sensor mask to record central plane, not including the source point +sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool) +sensor.mask[1:, :, Nz // 2] = True + +# record the pressure +sensor.record = ["p"] + +# record only the final few periods when the field is in steady state +sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1 + +# -------------------- +# SIMULATION +# -------------------- + +# NOTE: pml_inside=False not supported in new API, pml_auto maps to pml_size="auto" +sensor_data = kspaceFirstOrder( + deepcopy(kgrid), + deepcopy(medium), + deepcopy(source), + deepcopy(sensor), + pml_size="auto", + backend="cpp", + device="gpu", +) + +# extract amplitude from the sensor data +amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") + +# reshape data +amp = np.reshape(amp, (Nx - 1, Ny), order="F") + +# extract pressure on axis +amp_on_axis = amp[:, Ny // 2] + +# define axis vectors for plotting +x_vec = kgrid.x_vec[1:, :] - kgrid.x_vec[0] +y_vec = kgrid.y_vec + +# ========================================================================= +# ANALYTICAL SOLUTION +# ========================================================================= + +# calculate the wavenumber +k: float = 2.0 * np.pi * source_f0 / c0 + +# define radius and axis +a: float = source_diam / 2.0 +x_max: float = (Nx - 1) * dx +delta_x: float = x_max / 10000.0 +x_ref: float = np.arange(0.0, x_max + delta_x, delta_x, dtype=float) + +# calculate the analytical solution for a piston in an infinite baffle +# for comparison (Eq 5-7.3 in Pierce) +r_ref = np.sqrt(x_ref**2 + a**2) +p_ref = source_amp * np.abs(2.0 * np.sin((k * r_ref - k * x_ref) / 2.0)) + +# get analytical solution at exactly the same points as k-Wave +r = np.sqrt(x_vec**2 + a**2) +p_ref_kw = source_amp * np.abs(2.0 * np.sin((k * r - k * x_vec) / 2.0)) + +# calculate error +L2_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=2) +Linf_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=np.inf) +# ========================================================================= +# VISUALISATION +# ========================================================================= + +# plot the pressure along the focal axis of the piston +fig1, ax1 = plt.subplots(1, 1) +ax1.plot(1e3 * x_ref, 1e-6 * p_ref, "k-", label="Exact") +ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") +ax1.legend() +ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") +ax1.set_xlim(0.0, 1e3 * axial_size) +ax1.set_ylim(0.0, 1.1 * source_amp * 2e-6) +ax1.grid() + +# plot the source mask (pml is outside the grid in this example) +fig2, ax2 = plt.subplots(1, 1) +ax2.pcolormesh( + 1e3 * np.squeeze(kgrid.y_vec), 1e3 * np.squeeze(kgrid.x_vec), np.flip(source.p_mask[:, :, Nz // 2], axis=0), shading="nearest" +) +ax2.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") + +# plot the pressure field +fig3, ax3 = plt.subplots(1, 1) +ax3.pcolormesh(1e3 * np.squeeze(y_vec), 1e3 * np.squeeze(x_vec), np.flip(amp, axis=1), shading="gouraud") +ax3.set(xlabel="Lateral Position [mm]", ylabel="Axial Position [mm]", title="Pressure Field") +ax3.set_ylim(1e3 * x_vec[-1], 1e3 * x_vec[0]) + +plt.show() diff --git a/examples/at_circular_piston_AS/at_circular_piston_AS.py b/examples/at_circular_piston_AS/at_circular_piston_AS.py index b29ed0095..ac245b9dd 100644 --- a/examples/at_circular_piston_AS/at_circular_piston_AS.py +++ b/examples/at_circular_piston_AS/at_circular_piston_AS.py @@ -1,233 +1,227 @@ -""" -Modelling A Circular Plane Piston Transducer Assuming Axisymmetry Example - -This example models a circular piston transducer assuming axisymmetry. -The on-axis pressure is compared with the analytical expression from [1]. -Compared to the 3D simulation, a lower CFL (which gives a smaller time -step) is used, as the k-space correction for the axisymmetric code is not -exact in the radial direction. -[1] A. D. Pierce, Acoustics: An Introduction to its Physical Principles -and Applications. New York: Acoustical Society of America, 1989. -""" - -from copy import deepcopy - -import matplotlib.pyplot as plt -import numpy as np - -from kwave.data import Vector -from kwave.kgrid import kWaveGrid -from kwave.kmedium import kWaveMedium -from kwave.ksensor import kSensor -from kwave.ksource import kSource -from kwave.kspaceFirstOrderAS import kspaceFirstOrderASC -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions, SimulationType -from kwave.utils.filters import extract_amp_phase -from kwave.utils.kwave_array import kWaveArray -from kwave.utils.math import round_even -from kwave.utils.signals import create_cw_signals - -# medium parameters -c0 = 1500.0 # sound speed [m/s] -rho0 = 1000.0 # density [kg/m^3] - -# piston diameter [m] -source_diam = 10e-3 - -# source frequency [Hz] -source_f0 = 1e6 - -# source pressure [Pa] -source_mag = np.array([1e6]) - -# phase [rad] -source_phase = np.array([0.0]) - -# grid parameters -axial_size = 32e-3 # total grid size in the axial dimension [m] -lateral_size = 8e-3 # total grid size in the lateral dimension [m] - -# computational parameters -ppw = 4 # number of points per wavelength -t_end = 40e-6 # total compute time [s] (this must be long enough to reach steady state) -record_periods = 1 # number of periods to record -cfl = 0.05 # CFL number -bli_tolerance = 0.05 # tolerance for truncation of the off-grid source points -upsampling_rate = 10 # density of integration points relative to grid - -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - -# grid resolution -dx = c0 / (ppw * source_f0) # [m] - -# compute the size of the grid -Nx = round_even(axial_size / dx) -Ny = round_even(lateral_size / dx) - -grid_size_points = Vector([Nx, Ny]) -grid_spacing_meters = Vector([dx, dx]) - -# create the k-space grid -kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) - -# compute points per period -ppp = round(ppw / cfl) - -# compute time step -dt = 1.0 / (ppp * source_f0) - -# create the time array using an integer number of points per period -Nt = round(t_end / dt) -kgrid.setTime(Nt, dt) - - -# -------------------- -# SOURCE -# -------------------- - -# create time varying continuous wave source -source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_mag, source_phase) - -# create empty kWaveArray this specifies the transducer properties in -# axisymmetric coordinate system -karray = kWaveArray(axisymmetric=True, bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate, single_precision=True) - -# add line shaped element for transducer -karray.add_line_element([kgrid.x_vec[0].item(), -source_diam / 2.0], [kgrid.x_vec[0].item(), source_diam / 2.0]) - - -# make a source object -source = kSource() - -# assign binary mask using the karray -source.p_mask = karray.get_array_binary_mask(kgrid) - -# assign source pressure output in time -source.p = karray.get_distributed_source_signal(kgrid, source_sig) - - -# -------------------- -# MEDIUM -# -------------------- - -# water -medium = kWaveMedium(sound_speed=c0, density=rho0) - -# -------------------- -# SENSOR -# -------------------- - -sensor = kSensor() - -# set sensor mask to record central plane, not including the source point -# sensor.mask = np.zeros((Nx, Ny), dtype=bool) -# sensor.mask[1:, :] = True - -sensor.mask = np.ones((Nx, Ny), dtype=bool) - -# set the record type: record the pressure waveform -sensor.record = ["p"] - -# record only the final few periods when the field is in steady state -sensor.record_start_index = kgrid.Nt - record_periods * ppp + 1 - -# ========================================================================= -# DEFINE THE SIMULATION PARAMETERS -# ========================================================================= - - -# options for writing to file, but not doing simulations -simulation_options = SimulationOptions( - simulation_type=SimulationType.AXISYMMETRIC, pml_auto=True, save_to_disk=True, save_to_disk_exit=False, pml_inside=False -) - -execution_options = SimulationExecutionOptions(is_gpu_simulation=False, delete_data=False, verbose_level=2) - -# ========================================================================= -# RUN THE SIMULATION -# ========================================================================= - -sensor_data = kspaceFirstOrderASC( - medium=medium, - kgrid=kgrid, - source=deepcopy(source), - sensor=sensor, - simulation_options=simulation_options, - execution_options=execution_options, -) - -# extract amplitude from the sensor data -amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") - -# reshape data -amp = np.reshape(amp, (Nx, Ny), order="F") - -# extract pressure on axis -amp_on_axis = amp[:, 0] - -# define axis vectors for plotting -yvec = np.squeeze(kgrid.y_vec) - kgrid.y_vec[0].item() -y_vec = 1e3 * np.hstack((-np.flip(yvec)[:-1], yvec)) -x_vec = 1e3 * np.squeeze(kgrid.x_vec[:, :] - kgrid.x_vec[0]) - -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - -# calculate the wavenumber -k: float = 2.0 * np.pi * source_f0 / c0 - -# define radius and axis -a: float = source_diam / 2.0 -x_max: float = (Nx - 1) * dx -delta_x: float = x_max / 10000.0 -x_ref: float = np.arange(0.0, x_max + delta_x, delta_x, dtype=float) - -# calculate the analytical solution for a piston in an infinite baffle -# for comparison (Eq 5-7.3 in Pierce) -r_ref = np.sqrt(x_ref**2 + a**2) -p_ref = source_mag[0] * np.abs(2.0 * np.sin((k * r_ref - k * x_ref) / 2.0)) - -# # get analytical solution at exactly the same points as k-Wave -# r = np.sqrt(x_vec**2 + a**2) -# p_ref_kw = source_mag[0] * np.abs(2.0 * np.sin((k * r - k * x_vec) / 2.0)) - -# # calculate error -# L2_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=2) -# Linf_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=np.inf) - -# ========================================================================= -# VISUALISATION -# ========================================================================= - -data = np.hstack((np.fliplr(amp[:, :-1]), amp)) / 1e6 -sp = np.hstack((np.fliplr(source.p_mask[:, :])[:, :-1], source.p_mask)) - -# plot the pressure along the focal axis of the piston -fig1, ax1 = plt.subplots(1, 1) -ax1.plot(1e3 * x_ref, 1e-6 * p_ref, "k-", label="Exact") -ax1.plot(x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") -ax1.legend() -ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") -ax1.grid() - -# plot the source mask -fig2, ax2 = plt.subplots(1, 1) -ax2.pcolormesh(y_vec, x_vec, sp[:, :], shading="nearest") -ax2.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") -ax2.invert_yaxis() - -fig3, ax3 = plt.subplots(1, 1) -im3 = ax3.pcolormesh(y_vec, x_vec, data, shading="gouraud") -cbar3 = fig3.colorbar(im3, ax=ax3) -_ = cbar3.ax.set_title("[MPa]", fontsize="small") -ax3.invert_yaxis() - -plt.show() +""" +Modelling A Circular Plane Piston Transducer Assuming Axisymmetry Example + +This example models a circular piston transducer assuming axisymmetry. +The on-axis pressure is compared with the analytical expression from [1]. +Compared to the 3D simulation, a lower CFL (which gives a smaller time +step) is used, as the k-space correction for the axisymmetric code is not +exact in the radial direction. +[1] A. D. Pierce, Acoustics: An Introduction to its Physical Principles +and Applications. New York: Acoustical Society of America, 1989. +""" + +from copy import deepcopy + +import matplotlib.pyplot as plt +import numpy as np + +from kwave.data import Vector +from kwave.kgrid import kWaveGrid +from kwave.kmedium import kWaveMedium +from kwave.ksensor import kSensor +from kwave.ksource import kSource +from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.utils.filters import extract_amp_phase +from kwave.utils.kwave_array import kWaveArray +from kwave.utils.math import round_even +from kwave.utils.signals import create_cw_signals + +# medium parameters +c0 = 1500.0 # sound speed [m/s] +rho0 = 1000.0 # density [kg/m^3] + +# piston diameter [m] +source_diam = 10e-3 + +# source frequency [Hz] +source_f0 = 1e6 + +# source pressure [Pa] +source_mag = np.array([1e6]) + +# phase [rad] +source_phase = np.array([0.0]) + +# grid parameters +axial_size = 32e-3 # total grid size in the axial dimension [m] +lateral_size = 8e-3 # total grid size in the lateral dimension [m] + +# computational parameters +ppw = 4 # number of points per wavelength +t_end = 40e-6 # total compute time [s] (this must be long enough to reach steady state) +record_periods = 1 # number of periods to record +cfl = 0.05 # CFL number +bli_tolerance = 0.05 # tolerance for truncation of the off-grid source points +upsampling_rate = 10 # density of integration points relative to grid + +# ========================================================================= +# RUN SIMULATION +# ========================================================================= + +# -------------------- +# GRID +# -------------------- + +# grid resolution +dx = c0 / (ppw * source_f0) # [m] + +# compute the size of the grid +Nx = round_even(axial_size / dx) +Ny = round_even(lateral_size / dx) + +grid_size_points = Vector([Nx, Ny]) +grid_spacing_meters = Vector([dx, dx]) + +# create the k-space grid +kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) + +# compute points per period +ppp = round(ppw / cfl) + +# compute time step +dt = 1.0 / (ppp * source_f0) + +# create the time array using an integer number of points per period +Nt = round(t_end / dt) +kgrid.setTime(Nt, dt) + + +# -------------------- +# SOURCE +# -------------------- + +# create time varying continuous wave source +source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_mag, source_phase) + +# create empty kWaveArray this specifies the transducer properties in +# axisymmetric coordinate system +karray = kWaveArray(axisymmetric=True, bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate, single_precision=True) + +# add line shaped element for transducer +karray.add_line_element([kgrid.x_vec[0].item(), -source_diam / 2.0], [kgrid.x_vec[0].item(), source_diam / 2.0]) + + +# make a source object +source = kSource() + +# assign binary mask using the karray +source.p_mask = karray.get_array_binary_mask(kgrid) + +# assign source pressure output in time +source.p = karray.get_distributed_source_signal(kgrid, source_sig) + + +# -------------------- +# MEDIUM +# -------------------- + +# water +medium = kWaveMedium(sound_speed=c0, density=rho0) + +# -------------------- +# SENSOR +# -------------------- + +sensor = kSensor() + +# set sensor mask to record central plane, not including the source point +# sensor.mask = np.zeros((Nx, Ny), dtype=bool) +# sensor.mask[1:, :] = True + +sensor.mask = np.ones((Nx, Ny), dtype=bool) + +# set the record type: record the pressure waveform +sensor.record = ["p"] + +# record only the final few periods when the field is in steady state +sensor.record_start_index = kgrid.Nt - record_periods * ppp + 1 + +# ========================================================================= +# DEFINE THE SIMULATION PARAMETERS +# ========================================================================= + +# NOTE: pml_inside=False not supported in new API +# NOTE: simulation_type=SimulationType.AXISYMMETRIC is handled automatically by the new API + +# ========================================================================= +# RUN THE SIMULATION +# ========================================================================= + +sensor_data = kspaceFirstOrder( + kgrid, + medium, + deepcopy(source), + sensor, + pml_size="auto", + backend="cpp", + device="cpu", +) + +# extract amplitude from the sensor data +amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") + +# reshape data +amp = np.reshape(amp, (Nx, Ny), order="F") + +# extract pressure on axis +amp_on_axis = amp[:, 0] + +# define axis vectors for plotting +yvec = np.squeeze(kgrid.y_vec) - kgrid.y_vec[0].item() +y_vec = 1e3 * np.hstack((-np.flip(yvec)[:-1], yvec)) +x_vec = 1e3 * np.squeeze(kgrid.x_vec[:, :] - kgrid.x_vec[0]) + +# ========================================================================= +# ANALYTICAL SOLUTION +# ========================================================================= + +# calculate the wavenumber +k: float = 2.0 * np.pi * source_f0 / c0 + +# define radius and axis +a: float = source_diam / 2.0 +x_max: float = (Nx - 1) * dx +delta_x: float = x_max / 10000.0 +x_ref: float = np.arange(0.0, x_max + delta_x, delta_x, dtype=float) + +# calculate the analytical solution for a piston in an infinite baffle +# for comparison (Eq 5-7.3 in Pierce) +r_ref = np.sqrt(x_ref**2 + a**2) +p_ref = source_mag[0] * np.abs(2.0 * np.sin((k * r_ref - k * x_ref) / 2.0)) + +# # get analytical solution at exactly the same points as k-Wave +# r = np.sqrt(x_vec**2 + a**2) +# p_ref_kw = source_mag[0] * np.abs(2.0 * np.sin((k * r - k * x_vec) / 2.0)) + +# # calculate error +# L2_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=2) +# Linf_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=np.inf) + +# ========================================================================= +# VISUALISATION +# ========================================================================= + +data = np.hstack((np.fliplr(amp[:, :-1]), amp)) / 1e6 +sp = np.hstack((np.fliplr(source.p_mask[:, :])[:, :-1], source.p_mask)) + +# plot the pressure along the focal axis of the piston +fig1, ax1 = plt.subplots(1, 1) +ax1.plot(1e3 * x_ref, 1e-6 * p_ref, "k-", label="Exact") +ax1.plot(x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") +ax1.legend() +ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") +ax1.grid() + +# plot the source mask +fig2, ax2 = plt.subplots(1, 1) +ax2.pcolormesh(y_vec, x_vec, sp[:, :], shading="nearest") +ax2.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") +ax2.invert_yaxis() + +fig3, ax3 = plt.subplots(1, 1) +im3 = ax3.pcolormesh(y_vec, x_vec, data, shading="gouraud") +cbar3 = fig3.colorbar(im3, ax=ax3) +_ = cbar3.ax.set_title("[MPa]", fontsize="small") +ax3.invert_yaxis() + +plt.show() diff --git a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py b/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py index 3f019f40c..d9097b902 100644 --- a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py +++ b/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py @@ -1,214 +1,210 @@ -from copy import deepcopy - -import matplotlib.pyplot as plt -import numpy as np - -from kwave.data import Vector -from kwave.kgrid import kWaveGrid -from kwave.kmedium import kWaveMedium -from kwave.ksensor import kSensor -from kwave.ksource import kSource -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions -from kwave.utils.filters import extract_amp_phase -from kwave.utils.kwave_array import kWaveArray -from kwave.utils.mapgen import focused_annulus_oneil -from kwave.utils.math import round_even -from kwave.utils.signals import create_cw_signals - -verbose: bool = False - -# medium parameters -c0: float = 1500.0 # sound speed [m/s] -rho0: float = 1000.0 # density [kg/m^3] - -# source parameters -source_f0 = 1.0e6 # source frequency [Hz] -source_roc = 30e-3 # bowl radius of curvature [m] -source_amp = np.array([0.5e6, 1e6, 0.75e6]) # source pressure [Pa] -source_phase = np.deg2rad(np.array([0.0, 10.0, 20.0])) # source phase [radians] - -# aperture diameters of the elements given an inner, outer pairs [m] -diameters = np.array([[0.0, 5.0], [10.0, 15.0], [20.0, 25.0]]) * 1e-3 -diameters = diameters.tolist() - -# grid parameters -axial_size: float = 40e-3 # total grid size in the axial dimension [m] -lateral_size: float = 45e-3 # total grid size in the lateral dimension [m] - -# computational parameters -ppw: int = 3 # number of points per wavelength -t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state) -record_periods: int = 1 # number of periods to record -cfl: float = 0.5 # CFL number -source_x_offset: int = 20 # grid points to offset the source -bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points -upsampling_rate: int = 10 # density of integration points relative to grid -verbose_level: int = 0 # verbosity of k-wave executable - -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - -# calculate the grid spacing based on the points per wavelength and the frequency -dx: float = c0 / (ppw * source_f0) # [m] - -# compute the size of the grid -Nx: int = round_even(axial_size / dx) + source_x_offset -Ny: int = round_even(lateral_size / dx) -Nz: int = Ny - -# create the k-space grid -grid_size_points = Vector([Nx, Ny, Nz]) -grid_spacing_meters = Vector([dx, dx, dx]) -kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) - -# compute points per temporal period. -ppp: int = round(ppw / cfl) - -# compute corresponding time spacing -dt = 1.0 / (ppp * source_f0) - -# create the time array using an integer number of points per period -Nt = int(np.round(t_end / dt)) -kgrid.setTime(Nt, dt) - -# calculate the actual CFL and PPW -if verbose: - print("PPW = " + str(c0 / (dx * source_f0))) - print("CFL = " + str(c0 * dt / dx)) - -# -------------------- -# SOURCE -# -------------------- - -# create empty kSource -source = kSource() - -# create time varying source based on time, frequency, amplitude and phase -source_signal = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_amp, source_phase) - -# set bowl position and orientation -bowl_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0, 0] -focus_pos = [kgrid.x_vec[-1].item(), 0, 0] - -# create empty kWaveArray -karray = kWaveArray(bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate, single_precision=True) - -# add bowl shaped element to array -karray.add_annular_array(bowl_pos, source_roc, diameters, focus_pos) - -# assign binary mask to source, based on array elements and the grid -source.p_mask = karray.get_array_binary_mask(kgrid) - -# assign source signals, based on array elements, grid and signal -source.p = karray.get_distributed_source_signal(kgrid, source_signal) - -# -------------------- -# MEDIUM -# -------------------- - -# assign medium properties -medium = kWaveMedium(sound_speed=c0, density=rho0) - -# -------------------- -# SENSOR -# -------------------- - -sensor = kSensor() - -# set sensor mask to record central plane, not including the source point -sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool) -sensor.mask[(source_x_offset + 1) :, :, Nz // 2] = True - -# record the pressure -sensor.record = ["p"] - -# record only the final few periods when the field is in steady state -sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1 - -# -------------------- -# SIMULATION -# -------------------- - -simulation_options = SimulationOptions(pml_auto=True, data_recast=True, save_to_disk=True, save_to_disk_exit=False, pml_inside=False) - -execution_options = SimulationExecutionOptions(is_gpu_simulation=True, delete_data=False, verbose_level=2) - -sensor_data = kspaceFirstOrder3D( - medium=deepcopy(medium), - kgrid=deepcopy(kgrid), - source=deepcopy(source), - sensor=deepcopy(sensor), - simulation_options=simulation_options, - execution_options=execution_options, -) - -# extract amplitude from the sensor data -amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") - -# reshape data -amp = np.reshape(amp, (Nx - (source_x_offset + 1), Ny), order="F") - -# extract pressure on axis -amp_on_axis = amp[:, Ny // 2] - -# define axis vectors for plotting -x_vec = kgrid.x_vec[source_x_offset + 1 :, :] - kgrid.x_vec[source_x_offset] -y_vec = kgrid.y_vec - -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - -p_axial = focused_annulus_oneil( - source_roc, np.asarray(diameters).T, source_amp / (c0 * rho0), source_phase, source_f0, c0, rho0, np.squeeze(x_vec) -) - -# ========================================================================= -# VISUALISATION -# ========================================================================= - -# plot the pressure along the focal axis of the piston -fig1, ax1 = plt.subplots(1, 1) -ax1.plot(1e3 * x_vec, 1e-6 * p_axial, "k-", label="Exact") -ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") -ax1.legend() -ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") -ax1.set_xlim(0.0, 1e3 * axial_size) -ax1.grid() - -# plot the source mask (pml is outside the grid in this example) - -# get grid weights -grid_weights = karray.get_array_grid_weights(kgrid) - -fig2, (ax2a, ax2b) = plt.subplots(1, 2) -ax2a.pcolormesh( - 1e3 * np.squeeze(kgrid.y_vec), - 1e3 * np.squeeze(kgrid.x_vec), - np.flip(source.p_mask[:, :, Nz // 2], axis=0), - shading="nearest", -) -ax2a.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") -ax2b.pcolormesh( - 1e3 * np.squeeze(kgrid.y_vec), - 1e3 * np.squeeze(kgrid.x_vec), - np.flip(grid_weights[:, :, Nz // 2], axis=0), - shading="nearest", -) -ax2b.set(xlabel="y [mm]", ylabel="x [mm]", title="Off-Grid Source Weights") -plt.tight_layout(pad=1.2) - -# plot the pressure field -fig3, ax3 = plt.subplots(1, 1) -ax3.pcolormesh(1e3 * np.squeeze(y_vec), 1e3 * np.squeeze(x_vec), np.flip(amp, axis=1), shading="gouraud") -ax3.set(xlabel="Lateral Position [mm]", ylabel="Axial Position [mm]", title="Pressure Field") -ax3.set_ylim(1e3 * x_vec[-1], 1e3 * x_vec[0]) - -plt.show() +from copy import deepcopy + +import matplotlib.pyplot as plt +import numpy as np + +from kwave.data import Vector +from kwave.kgrid import kWaveGrid +from kwave.kmedium import kWaveMedium +from kwave.ksensor import kSensor +from kwave.ksource import kSource +from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.utils.filters import extract_amp_phase +from kwave.utils.kwave_array import kWaveArray +from kwave.utils.mapgen import focused_annulus_oneil +from kwave.utils.math import round_even +from kwave.utils.signals import create_cw_signals + +verbose: bool = False + +# medium parameters +c0: float = 1500.0 # sound speed [m/s] +rho0: float = 1000.0 # density [kg/m^3] + +# source parameters +source_f0 = 1.0e6 # source frequency [Hz] +source_roc = 30e-3 # bowl radius of curvature [m] +source_amp = np.array([0.5e6, 1e6, 0.75e6]) # source pressure [Pa] +source_phase = np.deg2rad(np.array([0.0, 10.0, 20.0])) # source phase [radians] + +# aperture diameters of the elements given an inner, outer pairs [m] +diameters = np.array([[0.0, 5.0], [10.0, 15.0], [20.0, 25.0]]) * 1e-3 +diameters = diameters.tolist() + +# grid parameters +axial_size: float = 40e-3 # total grid size in the axial dimension [m] +lateral_size: float = 45e-3 # total grid size in the lateral dimension [m] + +# computational parameters +ppw: int = 3 # number of points per wavelength +t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state) +record_periods: int = 1 # number of periods to record +cfl: float = 0.5 # CFL number +source_x_offset: int = 20 # grid points to offset the source +bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points +upsampling_rate: int = 10 # density of integration points relative to grid +verbose_level: int = 0 # verbosity of k-wave executable + +# ========================================================================= +# RUN SIMULATION +# ========================================================================= + +# -------------------- +# GRID +# -------------------- + +# calculate the grid spacing based on the points per wavelength and the frequency +dx: float = c0 / (ppw * source_f0) # [m] + +# compute the size of the grid +Nx: int = round_even(axial_size / dx) + source_x_offset +Ny: int = round_even(lateral_size / dx) +Nz: int = Ny + +# create the k-space grid +grid_size_points = Vector([Nx, Ny, Nz]) +grid_spacing_meters = Vector([dx, dx, dx]) +kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) + +# compute points per temporal period. +ppp: int = round(ppw / cfl) + +# compute corresponding time spacing +dt = 1.0 / (ppp * source_f0) + +# create the time array using an integer number of points per period +Nt = int(np.round(t_end / dt)) +kgrid.setTime(Nt, dt) + +# calculate the actual CFL and PPW +if verbose: + print("PPW = " + str(c0 / (dx * source_f0))) + print("CFL = " + str(c0 * dt / dx)) + +# -------------------- +# SOURCE +# -------------------- + +# create empty kSource +source = kSource() + +# create time varying source based on time, frequency, amplitude and phase +source_signal = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_amp, source_phase) + +# set bowl position and orientation +bowl_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0, 0] +focus_pos = [kgrid.x_vec[-1].item(), 0, 0] + +# create empty kWaveArray +karray = kWaveArray(bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate, single_precision=True) + +# add bowl shaped element to array +karray.add_annular_array(bowl_pos, source_roc, diameters, focus_pos) + +# assign binary mask to source, based on array elements and the grid +source.p_mask = karray.get_array_binary_mask(kgrid) + +# assign source signals, based on array elements, grid and signal +source.p = karray.get_distributed_source_signal(kgrid, source_signal) + +# -------------------- +# MEDIUM +# -------------------- + +# assign medium properties +medium = kWaveMedium(sound_speed=c0, density=rho0) + +# -------------------- +# SENSOR +# -------------------- + +sensor = kSensor() + +# set sensor mask to record central plane, not including the source point +sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool) +sensor.mask[(source_x_offset + 1) :, :, Nz // 2] = True + +# record the pressure +sensor.record = ["p"] + +# record only the final few periods when the field is in steady state +sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1 + +# -------------------- +# SIMULATION +# -------------------- + +# NOTE: pml_inside=False not supported in new API +sensor_data = kspaceFirstOrder( + deepcopy(kgrid), + deepcopy(medium), + deepcopy(source), + deepcopy(sensor), + pml_size="auto", + backend="cpp", + device="gpu", +) + +# extract amplitude from the sensor data +amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") + +# reshape data +amp = np.reshape(amp, (Nx - (source_x_offset + 1), Ny), order="F") + +# extract pressure on axis +amp_on_axis = amp[:, Ny // 2] + +# define axis vectors for plotting +x_vec = kgrid.x_vec[source_x_offset + 1 :, :] - kgrid.x_vec[source_x_offset] +y_vec = kgrid.y_vec + +# ========================================================================= +# ANALYTICAL SOLUTION +# ========================================================================= + +p_axial = focused_annulus_oneil( + source_roc, np.asarray(diameters).T, source_amp / (c0 * rho0), source_phase, source_f0, c0, rho0, np.squeeze(x_vec) +) + +# ========================================================================= +# VISUALISATION +# ========================================================================= + +# plot the pressure along the focal axis of the piston +fig1, ax1 = plt.subplots(1, 1) +ax1.plot(1e3 * x_vec, 1e-6 * p_axial, "k-", label="Exact") +ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") +ax1.legend() +ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") +ax1.set_xlim(0.0, 1e3 * axial_size) +ax1.grid() + +# plot the source mask (pml is outside the grid in this example) + +# get grid weights +grid_weights = karray.get_array_grid_weights(kgrid) + +fig2, (ax2a, ax2b) = plt.subplots(1, 2) +ax2a.pcolormesh( + 1e3 * np.squeeze(kgrid.y_vec), + 1e3 * np.squeeze(kgrid.x_vec), + np.flip(source.p_mask[:, :, Nz // 2], axis=0), + shading="nearest", +) +ax2a.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") +ax2b.pcolormesh( + 1e3 * np.squeeze(kgrid.y_vec), + 1e3 * np.squeeze(kgrid.x_vec), + np.flip(grid_weights[:, :, Nz // 2], axis=0), + shading="nearest", +) +ax2b.set(xlabel="y [mm]", ylabel="x [mm]", title="Off-Grid Source Weights") +plt.tight_layout(pad=1.2) + +# plot the pressure field +fig3, ax3 = plt.subplots(1, 1) +ax3.pcolormesh(1e3 * np.squeeze(y_vec), 1e3 * np.squeeze(x_vec), np.flip(amp, axis=1), shading="gouraud") +ax3.set(xlabel="Lateral Position [mm]", ylabel="Axial Position [mm]", title="Pressure Field") +ax3.set_ylim(1e3 * x_vec[-1], 1e3 * x_vec[0]) + +plt.show() diff --git a/examples/at_focused_bowl_3D/at_focused_bowl_3D.py b/examples/at_focused_bowl_3D/at_focused_bowl_3D.py index 83909d7ed..b0ebb7c0e 100644 --- a/examples/at_focused_bowl_3D/at_focused_bowl_3D.py +++ b/examples/at_focused_bowl_3D/at_focused_bowl_3D.py @@ -1,233 +1,229 @@ -from copy import deepcopy - -import matplotlib.pyplot as plt -import numpy as np - -from kwave.data import Vector -from kwave.kgrid import kWaveGrid -from kwave.kmedium import kWaveMedium -from kwave.ksensor import kSensor -from kwave.ksource import kSource -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions -from kwave.utils.filters import extract_amp_phase -from kwave.utils.kwave_array import kWaveArray -from kwave.utils.mapgen import focused_bowl_oneil -from kwave.utils.math import round_even -from kwave.utils.signals import create_cw_signals - -# Modelling A Focused Bowl Transducer In 3D Example - -# This example models a focused bowl transducer in 3D. The on-axis pressure -# is compared with the exact solution calculated using focused_bowl_oneil. - -verbose: bool = False - -# medium parameters -c0: float = 1500.0 # sound speed [m/s] -rho0: float = 1000.0 # density [kg/m^3] - -# source parameters -source_f0 = 1.0e6 # source frequency [Hz] -source_roc = 30e-3 # bowl radius of curvature [m] -source_diameter = 30e-3 # bowl aperture diameter [m] -source_amp = 1.0e6 # source pressure [Pa] -source_phase = 0.0 # source phase [radians] - -# grid parameters -axial_size: float = 50.0e-3 # total grid size in the axial dimension [m] -lateral_size: float = 40.0e-3 # total grid size in the lateral dimension [m] - -# computational parameters -ppw: int = 3 # number of points per wavelength -t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state) -record_periods: int = 1 # number of periods to record -cfl: float = 0.5 # CFL number -source_x_offset: int = 20 # grid points to offset the source -bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points -upsampling_rate: int = 10 # density of integration points relative to grid - -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - -# calculate the grid spacing based on the PPW and F0 -dx: float = c0 / (ppw * source_f0) # [m] - -# compute the size of the grid -Nx: int = round_even(axial_size / dx) + source_x_offset -Ny: int = round_even(lateral_size / dx) -Nz: int = Ny - -grid_size_points = Vector([Nx, Ny, Nz]) -grid_spacing_meters = Vector([dx, dx, dx]) - -# create the k-space grid -kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) - -# compute points per temporal period -ppp: int = round(ppw / cfl) - -# compute corresponding time spacing -dt: float = 1.0 / (ppp * source_f0) - -# create the time array using an integer number of points per period -Nt: int = int(np.round(t_end / dt)) -kgrid.setTime(Nt, dt) - -# calculate the actual CFL and PPW -if verbose: - print("PPW = " + str(c0 / (dx * source_f0))) - print("CFL = " + str(c0 * dt / dx)) - -# -------------------- -# SOURCE -# -------------------- - -source = kSource() - -# create time varying source -source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, np.array([source_amp]), np.array([source_phase])) - -# set bowl position and orientation -bowl_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0.0, 0.0] -focus_pos = [kgrid.x_vec[-1].item(), 0.0, 0.0] - -# create empty kWaveArray -karray = kWaveArray(bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate, single_precision=True) - -# add bowl shaped element -karray.add_bowl_element(bowl_pos, source_roc, source_diameter, focus_pos) - -# assign binary mask -source.p_mask = karray.get_array_binary_mask(kgrid) - -# assign source signals -source.p = karray.get_distributed_source_signal(kgrid, source_sig) - -# -------------------- -# MEDIUM -# -------------------- - -# assign medium properties -medium = kWaveMedium(sound_speed=c0, density=rho0) - -# -------------------- -# SENSOR -# -------------------- - -sensor = kSensor() - -# set sensor mask to record central plane, not including the source point -sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool) -sensor.mask[(source_x_offset + 1) : -1, :, Nz // 2] = True - -# record the pressure -sensor.record = ["p"] - -# record only the final few periods when the field is in steady state -sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1 - - -# -------------------- -# SIMULATION -# -------------------- - -simulation_options = SimulationOptions(pml_auto=True, data_recast=True, save_to_disk=True, save_to_disk_exit=False, pml_inside=False) - -execution_options = SimulationExecutionOptions(is_gpu_simulation=True, delete_data=False, verbose_level=2) - -sensor_data = kspaceFirstOrder3D( - medium=deepcopy(medium), - kgrid=deepcopy(kgrid), - source=deepcopy(source), - sensor=deepcopy(sensor), - simulation_options=simulation_options, - execution_options=execution_options, -) - -# extract amplitude from the sensor data -amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") - -# reshape data -amp = np.reshape(amp, (Nx - (source_x_offset + 2), Ny), order="F") - -# extract pressure on axis -amp_on_axis = amp[:, Ny // 2] - -# define axis vectors for plotting -x_vec = kgrid.x_vec[(source_x_offset + 1) : -1, :] - kgrid.x_vec[source_x_offset] -y_vec = kgrid.y_vec - -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - -# calculate the wavenumber -knumber = 2.0 * np.pi * source_f0 / c0 - -# define axis -x_max = Nx * dx -delta_x = x_max / 10000.0 -x_ref = np.arange(0.0, x_max + delta_x, delta_x) - -Z = source_amp / (c0 * rho0) - -# calculate analytical solution -p_ref_axial, _, _ = focused_bowl_oneil(source_roc, source_diameter, Z, source_f0, c0, rho0, axial_positions=x_ref) - -# calculate analytical solution at exactly the same points as the simulation -p_ref_axial_kw, _, _ = focused_bowl_oneil(source_roc, source_diameter, Z, source_f0, c0, rho0, axial_positions=np.squeeze(x_vec)) - -L2_error = 100 * np.linalg.norm(p_ref_axial_kw - amp_on_axis, ord=2) -Linf_error = 100 * np.linalg.norm(p_ref_axial_kw - amp_on_axis, ord=np.inf) - -# ========================================================================= -# VISUALISATION -# ========================================================================= - -# plot the pressure along the focal axis of the piston -fig1, ax1 = plt.subplots(1, 1) -ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, "k-", label="Exact") -ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") -ax1.legend() -ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") -ax1.set_xlim(0.0, 1e3 * axial_size) -ax1.set_ylim(0.0, 20) -ax1.grid() - -# plot the source mask (pml is outside the grid in this example) - -# get grid weights -grid_weights = karray.get_array_grid_weights(kgrid) - -fig2, (ax2a, ax2b) = plt.subplots(1, 2) -ax2a.pcolormesh( - 1e3 * np.squeeze(kgrid.y_vec), - 1e3 * np.squeeze(kgrid.x_vec), - np.flip(source.p_mask[:, :, Nz // 2], axis=0), - shading="nearest", -) -ax2a.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") -ax2b.pcolormesh( - 1e3 * np.squeeze(kgrid.y_vec), - 1e3 * np.squeeze(kgrid.x_vec), - np.flip(grid_weights[:, :, Nz // 2], axis=0), - shading="nearest", -) -ax2b.set(xlabel="y [mm]", ylabel="x [mm]", title="Off-Grid Source Weights") -plt.tight_layout(pad=1.2) - -# plot the pressure field -fig3, ax3 = plt.subplots(1, 1) -ax3.pcolormesh(1e3 * np.squeeze(y_vec), 1e3 * np.squeeze(x_vec), np.flip(amp, axis=1), shading="gouraud") -ax3.set(xlabel="Lateral Position [mm]", ylabel="Axial Position [mm]", title="Pressure Field") -ax3.set_ylim(1e3 * x_vec[-1], 1e3 * x_vec[0]) - -# show figures -plt.show() +from copy import deepcopy + +import matplotlib.pyplot as plt +import numpy as np + +from kwave.data import Vector +from kwave.kgrid import kWaveGrid +from kwave.kmedium import kWaveMedium +from kwave.ksensor import kSensor +from kwave.ksource import kSource +from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.utils.filters import extract_amp_phase +from kwave.utils.kwave_array import kWaveArray +from kwave.utils.mapgen import focused_bowl_oneil +from kwave.utils.math import round_even +from kwave.utils.signals import create_cw_signals + +# Modelling A Focused Bowl Transducer In 3D Example + +# This example models a focused bowl transducer in 3D. The on-axis pressure +# is compared with the exact solution calculated using focused_bowl_oneil. + +verbose: bool = False + +# medium parameters +c0: float = 1500.0 # sound speed [m/s] +rho0: float = 1000.0 # density [kg/m^3] + +# source parameters +source_f0 = 1.0e6 # source frequency [Hz] +source_roc = 30e-3 # bowl radius of curvature [m] +source_diameter = 30e-3 # bowl aperture diameter [m] +source_amp = 1.0e6 # source pressure [Pa] +source_phase = 0.0 # source phase [radians] + +# grid parameters +axial_size: float = 50.0e-3 # total grid size in the axial dimension [m] +lateral_size: float = 40.0e-3 # total grid size in the lateral dimension [m] + +# computational parameters +ppw: int = 3 # number of points per wavelength +t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state) +record_periods: int = 1 # number of periods to record +cfl: float = 0.5 # CFL number +source_x_offset: int = 20 # grid points to offset the source +bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points +upsampling_rate: int = 10 # density of integration points relative to grid + +# ========================================================================= +# RUN SIMULATION +# ========================================================================= + +# -------------------- +# GRID +# -------------------- + +# calculate the grid spacing based on the PPW and F0 +dx: float = c0 / (ppw * source_f0) # [m] + +# compute the size of the grid +Nx: int = round_even(axial_size / dx) + source_x_offset +Ny: int = round_even(lateral_size / dx) +Nz: int = Ny + +grid_size_points = Vector([Nx, Ny, Nz]) +grid_spacing_meters = Vector([dx, dx, dx]) + +# create the k-space grid +kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) + +# compute points per temporal period +ppp: int = round(ppw / cfl) + +# compute corresponding time spacing +dt: float = 1.0 / (ppp * source_f0) + +# create the time array using an integer number of points per period +Nt: int = int(np.round(t_end / dt)) +kgrid.setTime(Nt, dt) + +# calculate the actual CFL and PPW +if verbose: + print("PPW = " + str(c0 / (dx * source_f0))) + print("CFL = " + str(c0 * dt / dx)) + +# -------------------- +# SOURCE +# -------------------- + +source = kSource() + +# create time varying source +source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, np.array([source_amp]), np.array([source_phase])) + +# set bowl position and orientation +bowl_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0.0, 0.0] +focus_pos = [kgrid.x_vec[-1].item(), 0.0, 0.0] + +# create empty kWaveArray +karray = kWaveArray(bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate, single_precision=True) + +# add bowl shaped element +karray.add_bowl_element(bowl_pos, source_roc, source_diameter, focus_pos) + +# assign binary mask +source.p_mask = karray.get_array_binary_mask(kgrid) + +# assign source signals +source.p = karray.get_distributed_source_signal(kgrid, source_sig) + +# -------------------- +# MEDIUM +# -------------------- + +# assign medium properties +medium = kWaveMedium(sound_speed=c0, density=rho0) + +# -------------------- +# SENSOR +# -------------------- + +sensor = kSensor() + +# set sensor mask to record central plane, not including the source point +sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool) +sensor.mask[(source_x_offset + 1) : -1, :, Nz // 2] = True + +# record the pressure +sensor.record = ["p"] + +# record only the final few periods when the field is in steady state +sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1 + + +# -------------------- +# SIMULATION +# -------------------- + +# NOTE: pml_inside=False not supported in new API +sensor_data = kspaceFirstOrder( + deepcopy(kgrid), + deepcopy(medium), + deepcopy(source), + deepcopy(sensor), + pml_size="auto", + backend="cpp", + device="gpu", +) + +# extract amplitude from the sensor data +amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") + +# reshape data +amp = np.reshape(amp, (Nx - (source_x_offset + 2), Ny), order="F") + +# extract pressure on axis +amp_on_axis = amp[:, Ny // 2] + +# define axis vectors for plotting +x_vec = kgrid.x_vec[(source_x_offset + 1) : -1, :] - kgrid.x_vec[source_x_offset] +y_vec = kgrid.y_vec + +# ========================================================================= +# ANALYTICAL SOLUTION +# ========================================================================= + +# calculate the wavenumber +knumber = 2.0 * np.pi * source_f0 / c0 + +# define axis +x_max = Nx * dx +delta_x = x_max / 10000.0 +x_ref = np.arange(0.0, x_max + delta_x, delta_x) + +Z = source_amp / (c0 * rho0) + +# calculate analytical solution +p_ref_axial, _, _ = focused_bowl_oneil(source_roc, source_diameter, Z, source_f0, c0, rho0, axial_positions=x_ref) + +# calculate analytical solution at exactly the same points as the simulation +p_ref_axial_kw, _, _ = focused_bowl_oneil(source_roc, source_diameter, Z, source_f0, c0, rho0, axial_positions=np.squeeze(x_vec)) + +L2_error = 100 * np.linalg.norm(p_ref_axial_kw - amp_on_axis, ord=2) +Linf_error = 100 * np.linalg.norm(p_ref_axial_kw - amp_on_axis, ord=np.inf) + +# ========================================================================= +# VISUALISATION +# ========================================================================= + +# plot the pressure along the focal axis of the piston +fig1, ax1 = plt.subplots(1, 1) +ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, "k-", label="Exact") +ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") +ax1.legend() +ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") +ax1.set_xlim(0.0, 1e3 * axial_size) +ax1.set_ylim(0.0, 20) +ax1.grid() + +# plot the source mask (pml is outside the grid in this example) + +# get grid weights +grid_weights = karray.get_array_grid_weights(kgrid) + +fig2, (ax2a, ax2b) = plt.subplots(1, 2) +ax2a.pcolormesh( + 1e3 * np.squeeze(kgrid.y_vec), + 1e3 * np.squeeze(kgrid.x_vec), + np.flip(source.p_mask[:, :, Nz // 2], axis=0), + shading="nearest", +) +ax2a.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") +ax2b.pcolormesh( + 1e3 * np.squeeze(kgrid.y_vec), + 1e3 * np.squeeze(kgrid.x_vec), + np.flip(grid_weights[:, :, Nz // 2], axis=0), + shading="nearest", +) +ax2b.set(xlabel="y [mm]", ylabel="x [mm]", title="Off-Grid Source Weights") +plt.tight_layout(pad=1.2) + +# plot the pressure field +fig3, ax3 = plt.subplots(1, 1) +ax3.pcolormesh(1e3 * np.squeeze(y_vec), 1e3 * np.squeeze(x_vec), np.flip(amp, axis=1), shading="gouraud") +ax3.set(xlabel="Lateral Position [mm]", ylabel="Axial Position [mm]", title="Pressure Field") +ax3.set_ylim(1e3 * x_vec[-1], 1e3 * x_vec[0]) + +# show figures +plt.show() diff --git a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py b/examples/at_focused_bowl_AS/at_focused_bowl_AS.py index 731db30af..741bb7949 100644 --- a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py +++ b/examples/at_focused_bowl_AS/at_focused_bowl_AS.py @@ -1,236 +1,224 @@ -from copy import deepcopy - -import matplotlib.pyplot as plt -import numpy as np - -from kwave.data import Vector -from kwave.kgrid import kWaveGrid -from kwave.kmedium import kWaveMedium -from kwave.ksensor import kSensor -from kwave.ksource import kSource -from kwave.kspaceFirstOrderAS import kspaceFirstOrderASC -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions, SimulationType -from kwave.utils.filters import extract_amp_phase -from kwave.utils.kwave_array import kWaveArray -from kwave.utils.mapgen import focused_bowl_oneil -from kwave.utils.math import round_even -from kwave.utils.signals import create_cw_signals - -verbose: bool = False - -# medium parameters -c0: float = 1500.0 # sound speed [m/s] -rho0: float = 1000.0 # density [kg/m^3] - -# source parameters -source_f0 = 1.0e6 # source frequency [Hz] -source_roc = 30e-3 # bowl radius of curvature [m] -source_diameter = 30e-3 # bowl aperture diameter [m] -source_amp = np.array([1.0e6]) # source pressure [Pa] -source_phase = np.array([0.0]) # source phase [radians] - -# grid parameters -axial_size: float = 50.0e-3 # total grid size in the axial dimension [m] -lateral_size: float = 45.0e-3 # total grid size in the lateral dimension [m] - -# computational parameters -ppw: int = 3 # number of points per wavelength -t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state) -record_periods: int = 1 # number of periods to record -cfl: float = 0.05 # CFL number -source_x_offset: int = 20 # grid points to offset the source -bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points -upsampling_rate: int = 10 # density of integration points relative to grid - -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - -# calculate the grid spacing based on the PPW and F0 -dx: float = c0 / (ppw * source_f0) # [m] - -# compute the size of the grid -Nx: int = round_even(axial_size / dx) + source_x_offset -Ny: int = round_even(lateral_size / dx) - -grid_size_points = Vector([Nx, Ny]) -grid_spacing_meters = Vector([dx, dx]) - -# create the k-space grid -kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) - -# compute points per temporal period -ppp: int = round(ppw / cfl) - -# compute corresponding time spacing -dt: float = 1.0 / (ppp * source_f0) - -# create the time array using an integer number of points per period -Nt: int = round(t_end / dt) -kgrid.setTime(Nt, dt) - -# calculate the actual CFL and PPW -if verbose: - print("PPW = " + str(c0 / (dx * source_f0))) - print("CFL = " + str(c0 * dt / dx)) - -# -------------------- -# SOURCE -# -------------------- - -source = kSource() - -# create time varying source -source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_amp, source_phase) - -# set arc position and orientation -arc_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0] -focus_pos = [kgrid.x_vec[-1].item(), 0] - -# create empty kWaveArray -karray = kWaveArray(axisymmetric=True, bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate, single_precision=True) - -# add bowl shaped element -karray.add_arc_element(arc_pos, source_roc, source_diameter, focus_pos) - -# assign binary mask -source.p_mask = karray.get_array_binary_mask(kgrid) - -# assign source signals -source.p = karray.get_distributed_source_signal(kgrid, source_sig) - -# -------------------- -# MEDIUM -# -------------------- - -# assign medium properties -medium = kWaveMedium(sound_speed=c0, density=rho0) - -# -------------------- -# SENSOR -# -------------------- - -sensor = kSensor() - -# set sensor mask to record central plane, not including the source point -sensor.mask = np.zeros((Nx, Ny), dtype=bool) -sensor.mask[(source_x_offset + 1) :, :] = True - -# record the pressure -sensor.record = ["p"] - -# record only the final few periods when the field is in steady state -sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1 - -# -------------------- -# SIMULATION -# -------------------- - -DATA_CAST = "single" - -simulation_options = SimulationOptions( - simulation_type=SimulationType.AXISYMMETRIC, - data_cast=DATA_CAST, - data_recast=False, - save_to_disk=True, - save_to_disk_exit=False, - pml_inside=False, -) - -execution_options = SimulationExecutionOptions(is_gpu_simulation=False, delete_data=False, verbose_level=2) - -# ========================================================================= -# RUN THE SIMULATION -# ========================================================================= - -sensor_data = kspaceFirstOrderASC( - medium=deepcopy(medium), - kgrid=deepcopy(kgrid), - source=deepcopy(source), - sensor=deepcopy(sensor), - simulation_options=simulation_options, - execution_options=execution_options, -) - -# extract amplitude from the sensor data -amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") - -# reshape data -amp = np.reshape(amp, (Nx - (source_x_offset + 1), Ny), order="F") - -# extract pressure on axis -amp_on_axis = amp[:, 0] - -# define axis vectors for plotting -x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1) :, :] - kgrid.x_vec[source_x_offset]) -y_vec = kgrid.y_vec - -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - -# calculate the wavenumber -knumber = 2.0 * np.pi * source_f0 / c0 - -# define radius and axis -x_max = (Nx - 1) * dx -delta_x = x_max / 10000.0 -x_ref = np.arange(0.0, x_max + delta_x, delta_x) - -# calculate analytical solution -p_ref_axial, _, _ = focused_bowl_oneil(source_roc, source_diameter, source_amp[0] / (c0 * rho0), source_f0, c0, rho0, axial_positions=x_ref) - -# calculate analytical solution at exactly the same points as k-Wave -p_ref_axial_kw, _, _ = focused_bowl_oneil( - source_roc, source_diameter, source_amp[0] / (c0 * rho0), source_f0, c0, rho0, axial_positions=x_vec -) - - -# ========================================================================= -# VISUALISATION -# ========================================================================= - -# plot the pressure along the focal axis of the piston -fig1, ax1 = plt.subplots(1, 1) -ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, "k-", label="Exact") -ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") -ax1.legend() -ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") -ax1.set_xlim(0.0, 1e3 * axial_size) -ax1.set_ylim(0.0, 20) -ax1.grid() - -# get grid weights -grid_weights = karray.get_array_grid_weights(kgrid) - -# plot the source mask (pml is outside the grid in this example) -fig2, (ax2a, ax2b) = plt.subplots(1, 2) -ax2a.pcolormesh(1e3 * np.squeeze(kgrid.y_vec), 1e3 * np.squeeze(kgrid.x_vec), source.p_mask[:, :], shading="gouraud") -ax2a.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") -ax2b.pcolormesh(1e3 * np.squeeze(kgrid.y_vec), 1e3 * np.squeeze(kgrid.x_vec), grid_weights[:, :], shading="gouraud") -ax2b.set(xlabel="y [mm]", ylabel="x [mm]", title="Off-Grid Source Weights") - -# define axis vectors for plotting -yvec = np.squeeze(kgrid.y_vec) - kgrid.y_vec[0].item() -y_vec = np.hstack((-np.flip(yvec)[:-1], yvec)) -x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1) :, :] - kgrid.x_vec[source_x_offset]) - -data = np.hstack((np.fliplr(amp[:, :-1]), amp)) / 1e6 -sp = np.hstack((np.fliplr(source.p_mask[:, :])[:, :-1], source.p_mask)) -gw = np.hstack((np.fliplr(grid_weights[:, :])[:, :-1], grid_weights)) - -# plot the pressure field -fig3, ax3 = plt.subplots(1, 1) -im3 = ax3.pcolormesh(1e3 * np.squeeze(y_vec), 1e3 * np.squeeze(x_vec), data, shading="gouraud") -ax3.set(xlabel="Lateral Position [mm]", ylabel="Axial Position [mm]", title="Pressure Field") -cbar3 = fig3.colorbar(im3, ax=ax3) -_ = cbar3.ax.set_title("[MPa]", fontsize="small") -ax3.invert_yaxis() - -# show figures -plt.show() +from copy import deepcopy + +import matplotlib.pyplot as plt +import numpy as np + +from kwave.data import Vector +from kwave.kgrid import kWaveGrid +from kwave.kmedium import kWaveMedium +from kwave.ksensor import kSensor +from kwave.ksource import kSource +from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.utils.filters import extract_amp_phase +from kwave.utils.kwave_array import kWaveArray +from kwave.utils.mapgen import focused_bowl_oneil +from kwave.utils.math import round_even +from kwave.utils.signals import create_cw_signals + +verbose: bool = False + +# medium parameters +c0: float = 1500.0 # sound speed [m/s] +rho0: float = 1000.0 # density [kg/m^3] + +# source parameters +source_f0 = 1.0e6 # source frequency [Hz] +source_roc = 30e-3 # bowl radius of curvature [m] +source_diameter = 30e-3 # bowl aperture diameter [m] +source_amp = np.array([1.0e6]) # source pressure [Pa] +source_phase = np.array([0.0]) # source phase [radians] + +# grid parameters +axial_size: float = 50.0e-3 # total grid size in the axial dimension [m] +lateral_size: float = 45.0e-3 # total grid size in the lateral dimension [m] + +# computational parameters +ppw: int = 3 # number of points per wavelength +t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state) +record_periods: int = 1 # number of periods to record +cfl: float = 0.05 # CFL number +source_x_offset: int = 20 # grid points to offset the source +bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points +upsampling_rate: int = 10 # density of integration points relative to grid + +# ========================================================================= +# RUN SIMULATION +# ========================================================================= + +# -------------------- +# GRID +# -------------------- + +# calculate the grid spacing based on the PPW and F0 +dx: float = c0 / (ppw * source_f0) # [m] + +# compute the size of the grid +Nx: int = round_even(axial_size / dx) + source_x_offset +Ny: int = round_even(lateral_size / dx) + +grid_size_points = Vector([Nx, Ny]) +grid_spacing_meters = Vector([dx, dx]) + +# create the k-space grid +kgrid = kWaveGrid(grid_size_points, grid_spacing_meters) + +# compute points per temporal period +ppp: int = round(ppw / cfl) + +# compute corresponding time spacing +dt: float = 1.0 / (ppp * source_f0) + +# create the time array using an integer number of points per period +Nt: int = round(t_end / dt) +kgrid.setTime(Nt, dt) + +# calculate the actual CFL and PPW +if verbose: + print("PPW = " + str(c0 / (dx * source_f0))) + print("CFL = " + str(c0 * dt / dx)) + +# -------------------- +# SOURCE +# -------------------- + +source = kSource() + +# create time varying source +source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_amp, source_phase) + +# set arc position and orientation +arc_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0] +focus_pos = [kgrid.x_vec[-1].item(), 0] + +# create empty kWaveArray +karray = kWaveArray(axisymmetric=True, bli_tolerance=bli_tolerance, upsampling_rate=upsampling_rate, single_precision=True) + +# add bowl shaped element +karray.add_arc_element(arc_pos, source_roc, source_diameter, focus_pos) + +# assign binary mask +source.p_mask = karray.get_array_binary_mask(kgrid) + +# assign source signals +source.p = karray.get_distributed_source_signal(kgrid, source_sig) + +# -------------------- +# MEDIUM +# -------------------- + +# assign medium properties +medium = kWaveMedium(sound_speed=c0, density=rho0) + +# -------------------- +# SENSOR +# -------------------- + +sensor = kSensor() + +# set sensor mask to record central plane, not including the source point +sensor.mask = np.zeros((Nx, Ny), dtype=bool) +sensor.mask[(source_x_offset + 1) :, :] = True + +# record the pressure +sensor.record = ["p"] + +# record only the final few periods when the field is in steady state +sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1 + +# -------------------- +# SIMULATION +# -------------------- + +# NOTE: pml_inside=False, data_cast="single" not supported in new API +# NOTE: simulation_type=SimulationType.AXISYMMETRIC is handled automatically by the new API + +# ========================================================================= +# RUN THE SIMULATION +# ========================================================================= + +sensor_data = kspaceFirstOrder( + deepcopy(kgrid), + deepcopy(medium), + deepcopy(source), + deepcopy(sensor), + backend="cpp", + device="cpu", +) + +# extract amplitude from the sensor data +amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") + +# reshape data +amp = np.reshape(amp, (Nx - (source_x_offset + 1), Ny), order="F") + +# extract pressure on axis +amp_on_axis = amp[:, 0] + +# define axis vectors for plotting +x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1) :, :] - kgrid.x_vec[source_x_offset]) +y_vec = kgrid.y_vec + +# ========================================================================= +# ANALYTICAL SOLUTION +# ========================================================================= + +# calculate the wavenumber +knumber = 2.0 * np.pi * source_f0 / c0 + +# define radius and axis +x_max = (Nx - 1) * dx +delta_x = x_max / 10000.0 +x_ref = np.arange(0.0, x_max + delta_x, delta_x) + +# calculate analytical solution +p_ref_axial, _, _ = focused_bowl_oneil(source_roc, source_diameter, source_amp[0] / (c0 * rho0), source_f0, c0, rho0, axial_positions=x_ref) + +# calculate analytical solution at exactly the same points as k-Wave +p_ref_axial_kw, _, _ = focused_bowl_oneil( + source_roc, source_diameter, source_amp[0] / (c0 * rho0), source_f0, c0, rho0, axial_positions=x_vec +) + + +# ========================================================================= +# VISUALISATION +# ========================================================================= + +# plot the pressure along the focal axis of the piston +fig1, ax1 = plt.subplots(1, 1) +ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, "k-", label="Exact") +ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, "b.", label="k-Wave") +ax1.legend() +ax1.set(xlabel="Axial Position [mm]", ylabel="Pressure [MPa]", title="Axial Pressure") +ax1.set_xlim(0.0, 1e3 * axial_size) +ax1.set_ylim(0.0, 20) +ax1.grid() + +# get grid weights +grid_weights = karray.get_array_grid_weights(kgrid) + +# plot the source mask (pml is outside the grid in this example) +fig2, (ax2a, ax2b) = plt.subplots(1, 2) +ax2a.pcolormesh(1e3 * np.squeeze(kgrid.y_vec), 1e3 * np.squeeze(kgrid.x_vec), source.p_mask[:, :], shading="gouraud") +ax2a.set(xlabel="y [mm]", ylabel="x [mm]", title="Source Mask") +ax2b.pcolormesh(1e3 * np.squeeze(kgrid.y_vec), 1e3 * np.squeeze(kgrid.x_vec), grid_weights[:, :], shading="gouraud") +ax2b.set(xlabel="y [mm]", ylabel="x [mm]", title="Off-Grid Source Weights") + +# define axis vectors for plotting +yvec = np.squeeze(kgrid.y_vec) - kgrid.y_vec[0].item() +y_vec = np.hstack((-np.flip(yvec)[:-1], yvec)) +x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1) :, :] - kgrid.x_vec[source_x_offset]) + +data = np.hstack((np.fliplr(amp[:, :-1]), amp)) / 1e6 +sp = np.hstack((np.fliplr(source.p_mask[:, :])[:, :-1], source.p_mask)) +gw = np.hstack((np.fliplr(grid_weights[:, :])[:, :-1], grid_weights)) + +# plot the pressure field +fig3, ax3 = plt.subplots(1, 1) +im3 = ax3.pcolormesh(1e3 * np.squeeze(y_vec), 1e3 * np.squeeze(x_vec), data, shading="gouraud") +ax3.set(xlabel="Lateral Position [mm]", ylabel="Axial Position [mm]", title="Pressure Field") +cbar3 = fig3.colorbar(im3, ax=ax3) +_ = cbar3.ax.set_title("[MPa]", fontsize="small") +ax3.invert_yaxis() + +# show figures +plt.show() diff --git a/examples/at_linear_array_transducer/at_linear_array_transducer.py b/examples/at_linear_array_transducer/at_linear_array_transducer.py index f48cd58b3..55b894a91 100644 --- a/examples/at_linear_array_transducer/at_linear_array_transducer.py +++ b/examples/at_linear_array_transducer/at_linear_array_transducer.py @@ -6,9 +6,7 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC -from kwave.kWaveSimulation import SimulationOptions -from kwave.options.simulation_execution_options import SimulationExecutionOptions +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.utils.colormap import get_color_map from kwave.utils.kwave_array import kWaveArray from kwave.utils.plot import voxel_plot @@ -74,17 +72,15 @@ def main(): sensor = kSensor(sensor_mask, record=["p_max"]) # SIMULATION - simulation_options = SimulationOptions( - pml_auto=True, - pml_inside=False, - save_to_disk=True, - data_cast="single", - ) - - execution_options = SimulationExecutionOptions(is_gpu_simulation=True) - - sensor_data = kspaceFirstOrder3DC( - kgrid=kgrid, medium=medium, source=source, sensor=sensor, simulation_options=simulation_options, execution_options=execution_options + # NOTE: pml_inside=False, data_cast="single" not supported in new API + sensor_data = kspaceFirstOrder( + kgrid, + medium, + source, + sensor, + pml_size="auto", + backend="cpp", + device="gpu", ) p_max = np.reshape(sensor_data["p_max"], (Nx, Nz), order="F") diff --git a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py b/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py index b7407a6cd..b50b661cc 100644 --- a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py +++ b/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py @@ -6,10 +6,7 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.utils.data import scale_SI from kwave.utils.mapgen import make_ball, make_disc @@ -35,42 +32,6 @@ dt: float = 2e-9 # [s] t_end: float = 300e-9 # [s] -# # create the medium -# medium1 = kWaveMedium(sound_speed=sound_speed) - -# # create the computational grid -# kgrid1 = kWaveGrid([Nx], [dx]) - -# # create the time array -# kgrid1.setTime(np.round(t_end / dt), dt) - -# # create initial pressure distribution -# source1 = kSource() -# source1.p0[Nx//2 - source_radius:Nx//2 + source_radius] = 1.0 - -# # define a single sensor point -# sensor1 = kSensor() -# sensor1.mask = np.zeros((Nx,), dtype=bool) -# sensor1.mask[Nx // 2 + source_sensor_distance] = True - -# simulation_options1 = SimulationOptions( -# data_cast='single', -# save_to_disk=True) - -# execution_options1 = SimulationExecutionOptions( -# is_gpu_simulation=True, -# delete_data=False, -# verbose_level=2) - -# # run the simulation -# sensor_data_1D = kspaceFirstOrder1D( -# medium=medium1, -# kgrid=kgrid1, -# source=source1, -# sensor=sensor1, -# simulation_options=simulation_options1, -# execution_options=execution_options1) - ####### # medium @@ -98,18 +59,15 @@ source2 = kSource() source2.p0 = make_disc(Vector([Nx, Nx]), Vector([Nx // 2, Nx // 2]), source_radius, plot_disc=False) -simulation_options2 = SimulationOptions(data_cast="single", save_to_disk=True) - -execution_options2 = SimulationExecutionOptions(is_gpu_simulation=True, delete_data=False, verbose_level=2) - +# NOTE: data_cast="single" not supported in new API # run the simulation -sensor_data_2D = kspaceFirstOrder2D( - medium=medium2, - kgrid=kgrid2, - source=source2, - sensor=sensor2, - simulation_options=simulation_options2, - execution_options=execution_options2, +sensor_data_2D = kspaceFirstOrder( + kgrid2, + medium2, + source2, + sensor2, + backend="cpp", + device="gpu", ) ############ @@ -139,18 +97,15 @@ source3 = kSource() source3.p0 = make_ball(Vector([Nx, Nx, Nx]), Vector([Nx // 2, Nx // 2, Nx // 2]), source_radius) -simulation_options3 = SimulationOptions(data_cast="single", save_to_disk=True) - -execution_options3 = SimulationExecutionOptions(is_gpu_simulation=True, delete_data=False, verbose_level=2) - +# NOTE: data_cast="single" not supported in new API # run the simulation -sensor_data_3D = kspaceFirstOrder3D( - medium=medium3, - kgrid=kgrid3, - source=source3, - sensor=sensor3, - simulation_options=simulation_options3, - execution_options=execution_options3, +sensor_data_3D = kspaceFirstOrder( + kgrid3, + medium3, + source3, + sensor3, + backend="cpp", + device="gpu", ) # plot the simulations diff --git a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py b/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py index 59a1b27b0..ae05f527d 100644 --- a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py +++ b/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py @@ -8,10 +8,8 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.kspaceLineRecon import kspaceLineRecon -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions from kwave.utils.colormap import get_color_map from kwave.utils.filters import smooth from kwave.utils.mapgen import make_disc @@ -64,17 +62,18 @@ def main(): # create the time array kgrid.makeTime(medium.sound_speed) - # set the input arguments: force the PML to be outside the computational grid - simulation_options = SimulationOptions( - save_to_disk=True, - pml_inside=False, + # NOTE: pml_inside=False not supported in new API + # run the simulation + sensor_data = kspaceFirstOrder( + kgrid, + medium, + source, + sensor, pml_size=PML_size, smooth_p0=False, + backend="cpp", + device="gpu", ) - execution_options = SimulationExecutionOptions(is_gpu_simulation=True) - - # run the simulation - sensor_data = kspaceFirstOrder2D(kgrid, source, sensor, medium, simulation_options, execution_options) sensor_data = sensor_data["p"] # No transpose needed - fixed in executor # reconstruct the initial pressure diff --git a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py index a4a65856d..96505fbf2 100644 --- a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py +++ b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py @@ -10,10 +10,8 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.kspaceLineRecon import kspaceLineRecon -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions from kwave.reconstruction import TimeReversal from kwave.utils.colormap import get_color_map from kwave.utils.filters import smooth @@ -71,25 +69,37 @@ def main(): inner_mask[0, :] = 1 # Line sensor along the first row sensor.mask = inner_mask sensor.record = ["p", "p_final"] - # set the input arguments: force the PML to be outside the computational - # grid; switch off p0 smoothing within kspaceFirstOrder2D - simulation_options = SimulationOptions( - pml_inside=False, + # NOTE: pml_inside=False, data_cast="single" not supported in new API + # run the simulation + sensor_data = kspaceFirstOrder( + kgrid, + medium, + source, + deepcopy(sensor), pml_size=PML_size, smooth_p0=False, - save_to_disk=True, - data_cast="single", + backend="cpp", + device="gpu", ) - execution_options = SimulationExecutionOptions(is_gpu_simulation=True) - - # run the simulation - sensor_data = kspaceFirstOrder2D(kgrid, source, deepcopy(sensor), medium, simulation_options, execution_options) sensor.recorded_pressure = sensor_data["p"].T # Store the recorded pressure data # reset only the initial pressure, keep the sensor with recorded data source = kSource() # create time reversal handler and run reconstruction tr = TimeReversal(kgrid, medium, sensor) + # NOTE: TimeReversal still uses the legacy API internally — this may need updating separately + from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D + from kwave.options.simulation_execution_options import SimulationExecutionOptions + from kwave.options.simulation_options import SimulationOptions + + simulation_options = SimulationOptions( + pml_inside=False, + pml_size=PML_size, + smooth_p0=False, + save_to_disk=True, + data_cast="single", + ) + execution_options = SimulationExecutionOptions(is_gpu_simulation=True) p0_recon = tr(kspaceFirstOrder2D, simulation_options, execution_options) # repeat the FFT reconstruction for comparison diff --git a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py b/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py index ea7114494..dde4501b7 100644 --- a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py +++ b/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py @@ -7,10 +7,8 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.kspacePlaneRecon import kspacePlaneRecon -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions from kwave.utils.colormap import get_color_map from kwave.utils.filters import smooth from kwave.utils.mapgen import make_ball @@ -55,13 +53,18 @@ def main(): sensor_mask[0] = 1 sensor.mask = sensor_mask - # set the input arguments - simulation_options = SimulationOptions(save_to_disk=True, pml_size=PML_size, pml_inside=False, smooth_p0=False, data_cast="single") - - execution_options = SimulationExecutionOptions(is_gpu_simulation=True) - + # NOTE: pml_inside=False, data_cast="single" not supported in new API # run the simulation - sensor_data = kspaceFirstOrder3D(kgrid, source, sensor, medium, simulation_options, execution_options) + sensor_data = kspaceFirstOrder( + kgrid, + medium, + source, + sensor, + pml_size=PML_size, + smooth_p0=False, + backend="cpp", + device="gpu", + ) sensor_data = sensor_data["p"].T # reshape sensor data to y, z, t diff --git a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py index 7c99efe2b..74bd57fe7 100644 --- a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py +++ b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py @@ -9,9 +9,7 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.reconstruction import TimeReversal from kwave.utils.colormap import get_color_map from kwave.utils.filters import smooth @@ -67,18 +65,18 @@ def main(): sensor.mask[0, :, :] = 1 # Planar sensor along the first x-plane sensor.record = ["p", "p_final"] - # set the input arguments - simulation_options = SimulationOptions( - pml_inside=False, + # NOTE: pml_inside=False, data_cast="single" not supported in new API + # run the simulation + sensor_data = kspaceFirstOrder( + kgrid, + medium, + source, + deepcopy(sensor), pml_size=PML_size, smooth_p0=False, - save_to_disk=True, - data_cast="single", + backend="cpp", + device="gpu", ) - execution_options = SimulationExecutionOptions(is_gpu_simulation=True) - - # run the simulation - sensor_data = kspaceFirstOrder3D(kgrid, source, deepcopy(sensor), medium, simulation_options, execution_options) sensor.recorded_pressure = sensor_data["p"].T # Store the recorded pressure data # reset only the initial pressure source @@ -86,6 +84,19 @@ def main(): # create time reversal handler and run reconstruction tr = TimeReversal(kgrid, medium, sensor) + # NOTE: TimeReversal still uses the legacy API internally — this may need updating separately + from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D + from kwave.options.simulation_execution_options import SimulationExecutionOptions + from kwave.options.simulation_options import SimulationOptions + + simulation_options = SimulationOptions( + pml_inside=False, + pml_size=PML_size, + smooth_p0=False, + save_to_disk=True, + data_cast="single", + ) + execution_options = SimulationExecutionOptions(is_gpu_simulation=True) p0_recon = tr(kspaceFirstOrder3D, simulation_options, execution_options) # -------------------- diff --git a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py b/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py index d7b21e8f7..ff375c35a 100644 --- a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py +++ b/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py @@ -1,6 +1,4 @@ -import os from copy import deepcopy -from tempfile import gettempdir import matplotlib.pyplot as plt import numpy as np @@ -10,9 +8,7 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.utils.colormap import get_color_map from kwave.utils.conversion import cart2grid from kwave.utils.data import scale_SI @@ -71,19 +67,13 @@ source.p_mask[unflatten_matlab_mask(source.p_mask, source_positions[source_loop] - 1)] = 1 # run the simulation - - input_filename = f"example_input_{source_loop + 1}_input.h5" - pathname = gettempdir() - input_file_full_path = os.path.join(pathname, input_filename) - simulation_options = SimulationOptions(save_to_disk=True, input_filename=input_filename, data_path=pathname) - # run the simulation - sensor_data = kspaceFirstOrder2DC( - medium=medium, - kgrid=kgrid, - source=deepcopy(source), - sensor=deepcopy(sensor), - simulation_options=simulation_options, - execution_options=SimulationExecutionOptions(), + sensor_data = kspaceFirstOrder( + kgrid, + medium, + deepcopy(source), + deepcopy(sensor), + backend="cpp", + device="cpu", ) single_element_data[:, source_loop] = sensor_data["p"].sum(axis=1) diff --git a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py b/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py index 38091b941..d2a4945d9 100644 --- a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py +++ b/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py @@ -1,9 +1,7 @@ # # Focussed Detector In 2D Example # This example shows how k-Wave-python can be used to model the output of a focused semicircular detector, where the directionality arises from spatially averaging across the detector surface. Unlike the original example in k-Wave, this example does not visualize the simulation, as this functionality is not intrinsically supported by the accelerated binaries. -import os from copy import deepcopy -from tempfile import gettempdir import matplotlib.pyplot as plt import numpy as np @@ -12,10 +10,8 @@ from kwave.kgrid import kWaveGrid from kwave.kmedium import kWaveMedium from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.ktransducer import kSensor -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions from kwave.utils.data import scale_SI from kwave.utils.mapgen import make_circle, make_disc @@ -41,49 +37,42 @@ _ = kgrid.makeTime(medium.sound_speed, t_end=t_end) -# ## Define simulation parameters -input_filename = "example_sd_focused_2d_input.h5" -pathname = gettempdir() -input_file_full_path = os.path.join(pathname, input_filename) -simulation_options = SimulationOptions(save_to_disk=True, input_filename=input_filename, data_path=pathname) - - -# ## Run simulation with first source +## Run simulation with first source # place a disc-shaped source near the focus of the detector source = kSource() source.p0 = 2 * make_disc(grid_size, grid_size / 2, 4) # run the simulation -sensor_data1 = kspaceFirstOrder2DC( - medium=medium, - kgrid=kgrid, - source=deepcopy(source), - sensor=sensor, - simulation_options=simulation_options, - execution_options=SimulationExecutionOptions(), +sensor_data1 = kspaceFirstOrder( + kgrid, + medium, + deepcopy(source), + sensor, + backend="cpp", + device="cpu", ) sensor_data1["p"].shape -# ## Run simulation with second source +## Run simulation with second source # place a disc-shaped source horizontally shifted from the focus of the detector source.p0 = 2 * make_disc(grid_size, grid_size / 2 + [0, 20], 4) -sensor_data2 = kspaceFirstOrder2DC( - medium=medium, - kgrid=kgrid, - source=deepcopy(source), - sensor=sensor, - simulation_options=simulation_options, - execution_options=SimulationExecutionOptions(), +sensor_data2 = kspaceFirstOrder( + kgrid, + medium, + deepcopy(source), + sensor, + backend="cpp", + device="cpu", ) -# ## Visualize recorded data +## Visualize recorded data sensor_output1 = np.sum(sensor_data1["p"], axis=1) / np.sum(sensor.mask) diff --git a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py b/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py index 03e5fd6e5..c582a2776 100644 --- a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py +++ b/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py @@ -1,9 +1,7 @@ # Focussed Detector In 3D Example # This example shows how k-Wave can be used to model the output of a focussed bowl detector where the directionality arises from spatially averaging across the detector surface. -import os from copy import deepcopy -from tempfile import gettempdir import matplotlib.pyplot as plt import numpy as np @@ -12,10 +10,8 @@ from kwave.kgrid import kWaveGrid from kwave.kmedium import kWaveMedium from kwave.ksource import kSource -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.ktransducer import kSensor -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions from kwave.utils.data import scale_SI from kwave.utils.filters import filter_time_series from kwave.utils.mapgen import make_bowl @@ -32,13 +28,6 @@ # define the array of temporal points _ = kgrid.makeTime(medium.sound_speed) -input_filename = "example_sd_focused_3d_input.h5" -pathname = gettempdir() -input_file_full_path = os.path.join(pathname, input_filename) -simulation_options = SimulationOptions( - save_to_disk=True, input_filename=input_filename, data_path=pathname, pml_size=10, data_cast="single" -) - # create a concave sensor sphere_offset = 10 diameter = grid_size.x / 2 + 1 @@ -64,13 +53,14 @@ # run the first simulation source.p_mask = source1 -sensor_data1 = kspaceFirstOrder3D( - medium=medium, - kgrid=kgrid, - source=deepcopy(source), - sensor=deepcopy(sensor), - simulation_options=simulation_options, - execution_options=SimulationExecutionOptions(is_gpu_simulation=False), +sensor_data1 = kspaceFirstOrder( + kgrid, + medium, + deepcopy(source), + deepcopy(sensor), + backend="cpp", + device="cpu", + pml_size=10, ) # average the data recorded at each grid point to simulate the measured signal from a single element focused detector @@ -83,13 +73,14 @@ # run the second simulation source.p_mask = source2 -sensor_data2 = kspaceFirstOrder3D( - medium=medium, - kgrid=kgrid, - source=source, - sensor=sensor, - simulation_options=simulation_options, - execution_options=SimulationExecutionOptions(is_gpu_simulation=False), +sensor_data2 = kspaceFirstOrder( + kgrid, + medium, + source, + sensor, + backend="cpp", + device="cpu", + pml_size=10, ) # average the data recorded at each grid point to simulate the measured signal from a single element focused detector diff --git a/examples/us_beam_patterns/us_beam_patterns.py b/examples/us_beam_patterns/us_beam_patterns.py index 4afa0269e..631036214 100644 --- a/examples/us_beam_patterns/us_beam_patterns.py +++ b/examples/us_beam_patterns/us_beam_patterns.py @@ -7,14 +7,11 @@ import matplotlib.pyplot as plt import numpy as np -from kwave.data import Vector from kwave.kgrid import kWaveGrid from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.ktransducer import NotATransducer, kWaveTransducerSimple -from kwave.kWaveSimulation import SimulationOptions -from kwave.options.simulation_execution_options import SimulationExecutionOptions from kwave.utils.dotdictionary import dotdict from kwave.utils.filters import spect from kwave.utils.math import find_closest @@ -24,7 +21,6 @@ # simulation settings -DATA_CAST = "single" # set to 'xy' or 'xz' to generate the beam pattern in different planes MASK_PLANE = "xy" # set to true to compute the rms or peak beam patterns, set to false to compute the harmonic beam patterns @@ -129,25 +125,16 @@ if USE_STATISTICS: sensor.record = ["p_rms", "p_max"] -simulation_options = SimulationOptions( - pml_inside=False, - pml_size=Vector([PML_X_SIZE, PML_Y_SIZE, PML_Z_SIZE]), - data_cast=DATA_CAST, - save_to_disk=True, -) - -if not USE_STATISTICS: - simulation_options.stream_to_disk = "harmonic_data.h5" - -execution_options = SimulationExecutionOptions(is_gpu_simulation=True) - -sensor_data = kspaceFirstOrder3D( - medium=medium, - kgrid=kgrid, - source=not_transducer, - sensor=sensor, - simulation_options=simulation_options, - execution_options=execution_options, +# NOTE: pml_inside=False not supported in new API +# NOTE: stream_to_disk not supported in new API +sensor_data = kspaceFirstOrder( + kgrid, + medium, + not_transducer, + sensor, + backend="cpp", + device="gpu", + pml_size=(PML_X_SIZE, PML_Y_SIZE, PML_Z_SIZE), ) diff --git a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py b/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py index 4cd1f8251..a5314dbbd 100644 --- a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py +++ b/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py @@ -8,10 +8,8 @@ from kwave.data import Vector from kwave.kgrid import kWaveGrid from kwave.kmedium import kWaveMedium -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.ktransducer import NotATransducer, kWaveTransducerSimple -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions from kwave.reconstruction.beamform import envelope_detection from kwave.reconstruction.tools import log_compression from kwave.utils.conversion import db2neper @@ -24,7 +22,6 @@ PHANTOM_DATA_PATH = "phantom_data.mat" # simulation settings -DATA_CAST = "single" RUN_SIMULATION = False pml_size_points = Vector([20, 10, 10]) # [grid points] @@ -99,27 +96,17 @@ medium.sound_speed = sound_speed_map[:, medium_position : medium_position + grid_size_points.y, :] medium.density = density_map[:, medium_position : medium_position + grid_size_points.y, :] - # set the input settings - input_filename = f"example_input_{scan_line_index}.h5" - # set the input settings - simulation_options = SimulationOptions( - pml_inside=False, - pml_size=pml_size_points, - data_cast=DATA_CAST, - data_recast=True, - save_to_disk=True, - input_filename=input_filename, - save_to_disk_exit=False, - ) # run the simulation if RUN_SIMULATION: - sensor_data = kspaceFirstOrder3D( - medium=medium, - kgrid=kgrid, - source=not_transducer, - sensor=not_transducer, - simulation_options=simulation_options, - execution_options=SimulationExecutionOptions(is_gpu_simulation=True), + # NOTE: pml_inside=False not supported in new API + sensor_data = kspaceFirstOrder( + kgrid, + medium, + not_transducer, + not_transducer, + backend="cpp", + device="gpu", + pml_size=(pml_size_points.x, pml_size_points.y, pml_size_points.z), ) scan_lines[scan_line_index, :] = not_transducer.scan_line(not_transducer.combine_sensor_data(sensor_data["p"].T)) diff --git a/examples/us_bmode_phased_array/us_bmode_phased_array.py b/examples/us_bmode_phased_array/us_bmode_phased_array.py index 823d17964..7cfd4d354 100644 --- a/examples/us_bmode_phased_array/us_bmode_phased_array.py +++ b/examples/us_bmode_phased_array/us_bmode_phased_array.py @@ -7,10 +7,8 @@ from kwave.data import Vector from kwave.kgrid import kWaveGrid from kwave.kmedium import kWaveMedium -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.ktransducer import NotATransducer, kWaveTransducerSimple -from kwave.options.simulation_execution_options import SimulationExecutionOptions -from kwave.options.simulation_options import SimulationOptions from kwave.reconstruction.beamform import envelope_detection, scan_conversion from kwave.reconstruction.tools import log_compression from kwave.utils.conversion import db2neper @@ -20,7 +18,6 @@ from kwave.utils.signals import get_win, tone_burst # simulation settings -DATA_CAST = "single" RUN_SIMULATION = True @@ -125,29 +122,18 @@ for angle_index in range(number_scan_lines): print(f"Computing scan line {angle_index} of {number_scan_lines}") - # set the input settings - input_filename = f"example_input_{angle_index}.h5" - # set the input settings - simulation_options = SimulationOptions( - pml_inside=False, - pml_size=pml_size_points, - data_cast=DATA_CAST, - data_recast=True, - save_to_disk=True, - input_filename=input_filename, - save_to_disk_exit=False, - ) - # Update the current steering angle not_transducer.steering_angle = steering_angles[angle_index] - sensor_data = kspaceFirstOrder3D( - medium=deepcopy(medium), # Medium is altered in-place in this function - kgrid=kgrid, - source=not_transducer, - sensor=not_transducer, - simulation_options=simulation_options, - execution_options=SimulationExecutionOptions(is_gpu_simulation=True), + # NOTE: pml_inside=False not supported in new API + sensor_data = kspaceFirstOrder( + kgrid, + deepcopy(medium), + not_transducer, + not_transducer, + backend="cpp", + device="gpu", + pml_size=(pml_size_points.x, pml_size_points.y, pml_size_points.z), ) scan_lines[angle_index, :] = not_transducer.scan_line(not_transducer.combine_sensor_data(sensor_data["p"].T)) diff --git a/examples/us_defining_transducer/us_defining_transducer.py b/examples/us_defining_transducer/us_defining_transducer.py index 74373e83d..6beb19c7f 100644 --- a/examples/us_defining_transducer/us_defining_transducer.py +++ b/examples/us_defining_transducer/us_defining_transducer.py @@ -1,22 +1,16 @@ import matplotlib.pyplot as plt import numpy as np -from kwave.data import Vector from kwave.kgrid import kWaveGrid from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor -from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D +from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.ktransducer import NotATransducer, kWaveTransducerSimple -from kwave.kWaveSimulation import SimulationOptions -from kwave.options.simulation_execution_options import SimulationExecutionOptions from kwave.utils.dotdictionary import dotdict from kwave.utils.filters import spect from kwave.utils.plot import voxel_plot from kwave.utils.signals import tone_burst -# simulation settings -DATA_CAST = "single" - # define the grid PML_X_SIZE = 20 PML_Y_SIZE = 10 @@ -81,22 +75,15 @@ sensor.record = ["p"] # SIMULATION -simulation_options = SimulationOptions( - pml_inside=False, - pml_size=Vector([PML_X_SIZE, PML_Y_SIZE, PML_Z_SIZE]), - data_cast=DATA_CAST, - save_to_disk=True, -) - -execution_options = SimulationExecutionOptions(is_gpu_simulation=True) - -sensor_data = kspaceFirstOrder3D( - medium=medium, - kgrid=kgrid, - source=not_transducer, - sensor=sensor, - simulation_options=simulation_options, - execution_options=execution_options, +# NOTE: pml_inside=False not supported in new API +sensor_data = kspaceFirstOrder( + kgrid, + medium, + not_transducer, + sensor, + backend="cpp", + device="gpu", + pml_size=(PML_X_SIZE, PML_Y_SIZE, PML_Z_SIZE), ) padded_input_signal = np.concatenate((input_signal, np.zeros((1, 2 * np.shape(input_signal)[1]))), axis=1) From 191a8494628076ab81f43158efbfd554affb35bb Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:19:46 -0700 Subject: [PATCH 03/21] Remove unnecessary deepcopy from all examples The new kspaceFirstOrder() API does not mutate its inputs (handles copies internally when needed), so deepcopy is never required. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../at_circular_piston_3D/at_circular_piston_3D.py | 10 ++++------ .../at_circular_piston_AS/at_circular_piston_AS.py | 4 +--- .../at_focused_annular_array_3D.py | 10 ++++------ examples/at_focused_bowl_3D/at_focused_bowl_3D.py | 10 ++++------ examples/at_focused_bowl_AS/at_focused_bowl_AS.py | 10 ++++------ examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py | 4 +--- .../pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py | 4 +--- .../sd_directivity_modelling_2D.py | 6 ++---- .../sd_focussed_detector_2D/sd_focussed_detector_2D.py | 6 ++---- .../sd_focussed_detector_3D/sd_focussed_detector_3D.py | 6 ++---- .../us_bmode_phased_array/us_bmode_phased_array.py | 4 +--- 11 files changed, 26 insertions(+), 48 deletions(-) diff --git a/examples/at_circular_piston_3D/at_circular_piston_3D.py b/examples/at_circular_piston_3D/at_circular_piston_3D.py index 8e11b9a55..a1d902fd2 100644 --- a/examples/at_circular_piston_3D/at_circular_piston_3D.py +++ b/examples/at_circular_piston_3D/at_circular_piston_3D.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np @@ -126,10 +124,10 @@ # NOTE: pml_inside=False not supported in new API, pml_auto maps to pml_size="auto" sensor_data = kspaceFirstOrder( - deepcopy(kgrid), - deepcopy(medium), - deepcopy(source), - deepcopy(sensor), + kgrid, + medium, + source, + sensor, pml_size="auto", backend="cpp", device="gpu", diff --git a/examples/at_circular_piston_AS/at_circular_piston_AS.py b/examples/at_circular_piston_AS/at_circular_piston_AS.py index ac245b9dd..eddbc66f2 100644 --- a/examples/at_circular_piston_AS/at_circular_piston_AS.py +++ b/examples/at_circular_piston_AS/at_circular_piston_AS.py @@ -10,8 +10,6 @@ and Applications. New York: Acoustical Society of America, 1989. """ -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np @@ -150,7 +148,7 @@ sensor_data = kspaceFirstOrder( kgrid, medium, - deepcopy(source), + source, sensor, pml_size="auto", backend="cpp", diff --git a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py b/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py index d9097b902..45a93430c 100644 --- a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py +++ b/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np @@ -136,10 +134,10 @@ # NOTE: pml_inside=False not supported in new API sensor_data = kspaceFirstOrder( - deepcopy(kgrid), - deepcopy(medium), - deepcopy(source), - deepcopy(sensor), + kgrid, + medium, + source, + sensor, pml_size="auto", backend="cpp", device="gpu", diff --git a/examples/at_focused_bowl_3D/at_focused_bowl_3D.py b/examples/at_focused_bowl_3D/at_focused_bowl_3D.py index b0ebb7c0e..a4027da29 100644 --- a/examples/at_focused_bowl_3D/at_focused_bowl_3D.py +++ b/examples/at_focused_bowl_3D/at_focused_bowl_3D.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np @@ -138,10 +136,10 @@ # NOTE: pml_inside=False not supported in new API sensor_data = kspaceFirstOrder( - deepcopy(kgrid), - deepcopy(medium), - deepcopy(source), - deepcopy(sensor), + kgrid, + medium, + source, + sensor, pml_size="auto", backend="cpp", device="gpu", diff --git a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py b/examples/at_focused_bowl_AS/at_focused_bowl_AS.py index 741bb7949..2c648db29 100644 --- a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py +++ b/examples/at_focused_bowl_AS/at_focused_bowl_AS.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np @@ -137,10 +135,10 @@ # ========================================================================= sensor_data = kspaceFirstOrder( - deepcopy(kgrid), - deepcopy(medium), - deepcopy(source), - deepcopy(sensor), + kgrid, + medium, + source, + sensor, backend="cpp", device="cpu", ) diff --git a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py index 96505fbf2..468ed0bb6 100644 --- a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py +++ b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.axes_grid1 import make_axes_locatable @@ -75,7 +73,7 @@ def main(): kgrid, medium, source, - deepcopy(sensor), + sensor, pml_size=PML_size, smooth_p0=False, backend="cpp", diff --git a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py index 74bd57fe7..224759126 100644 --- a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py +++ b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.axes_grid1 import make_axes_locatable @@ -71,7 +69,7 @@ def main(): kgrid, medium, source, - deepcopy(sensor), + sensor, pml_size=PML_size, smooth_p0=False, backend="cpp", diff --git a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py b/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py index ff375c35a..9f8533e9f 100644 --- a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py +++ b/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np @@ -70,8 +68,8 @@ sensor_data = kspaceFirstOrder( kgrid, medium, - deepcopy(source), - deepcopy(sensor), + source, + sensor, backend="cpp", device="cpu", ) diff --git a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py b/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py index d2a4945d9..034a84b41 100644 --- a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py +++ b/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py @@ -1,8 +1,6 @@ # # Focussed Detector In 2D Example # This example shows how k-Wave-python can be used to model the output of a focused semicircular detector, where the directionality arises from spatially averaging across the detector surface. Unlike the original example in k-Wave, this example does not visualize the simulation, as this functionality is not intrinsically supported by the accelerated binaries. -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np @@ -46,7 +44,7 @@ sensor_data1 = kspaceFirstOrder( kgrid, medium, - deepcopy(source), + source, sensor, backend="cpp", device="cpu", @@ -65,7 +63,7 @@ sensor_data2 = kspaceFirstOrder( kgrid, medium, - deepcopy(source), + source, sensor, backend="cpp", device="cpu", diff --git a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py b/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py index c582a2776..fc1a13147 100644 --- a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py +++ b/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py @@ -1,8 +1,6 @@ # Focussed Detector In 3D Example # This example shows how k-Wave can be used to model the output of a focussed bowl detector where the directionality arises from spatially averaging across the detector surface. -from copy import deepcopy - import matplotlib.pyplot as plt import numpy as np @@ -56,8 +54,8 @@ sensor_data1 = kspaceFirstOrder( kgrid, medium, - deepcopy(source), - deepcopy(sensor), + source, + sensor, backend="cpp", device="cpu", pml_size=10, diff --git a/examples/us_bmode_phased_array/us_bmode_phased_array.py b/examples/us_bmode_phased_array/us_bmode_phased_array.py index 7cfd4d354..5492506af 100644 --- a/examples/us_bmode_phased_array/us_bmode_phased_array.py +++ b/examples/us_bmode_phased_array/us_bmode_phased_array.py @@ -1,5 +1,3 @@ -from copy import deepcopy - import numpy as np import scipy.io from matplotlib import pyplot as plt @@ -128,7 +126,7 @@ # NOTE: pml_inside=False not supported in new API sensor_data = kspaceFirstOrder( kgrid, - deepcopy(medium), + medium, not_transducer, not_transducer, backend="cpp", From b7b81ae5bdaee61d6d9146655c5f00d04aec86fd Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:29:23 -0700 Subject: [PATCH 04/21] Add # %% cell markers to all examples for interactive notebook support All .py examples now work as interactive notebooks in VS Code/JupyterLab via percent-format cell markers. Each file has a markdown header cell with title and description, followed by logical sections. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../at_array_as_sensor/at_array_as_sensor.py | 14 +++-- .../at_array_as_source/at_array_as_source.py | 24 +++----- .../at_circular_piston_3D.py | 25 ++++---- .../at_circular_piston_AS.py | 35 ++++------- .../at_focused_annular_array_3D.py | 26 ++++---- .../at_focused_bowl_3D/at_focused_bowl_3D.py | 31 ++++------ .../at_focused_bowl_AS/at_focused_bowl_AS.py | 31 ++++------ .../at_linear_array_transducer.py | 11 +++- examples/checkpointing/checkpoint.py | 4 ++ examples/ivp_1D_simulation.py | 13 ++-- .../ivp_photoacoustic_waveforms.py | 13 ++-- examples/new_api_ivp_2D.py | 6 ++ examples/new_api_transducer_3D.py | 6 ++ .../pr_2D_FFT_line_sensor.py | 17 +++--- .../pr_2D_TR_line_sensor.py | 18 +++--- .../pr_3D_FFT_planar_sensor.py | 17 +++--- .../pr_3D_TR_planar_sensor.py | 18 +++--- .../sd_directivity_modelling_2D.py | 8 +++ .../sd_focussed_detector_2D.py | 16 +++-- .../sd_focussed_detector_3D.py | 10 +++- examples/unified_entry_point_demo.py | 29 ++++----- examples/us_beam_patterns/us_beam_patterns.py | 60 ++++--------------- .../example_utils.py | 4 ++ .../us_bmode_linear_transducer.py | 19 +++--- .../us_bmode_phased_array.py | 22 +++---- .../us_defining_transducer.py | 9 +++ 26 files changed, 230 insertions(+), 256 deletions(-) diff --git a/examples/at_array_as_sensor/at_array_as_sensor.py b/examples/at_array_as_sensor/at_array_as_sensor.py index 0d5666032..0f5e233a3 100644 --- a/examples/at_array_as_sensor/at_array_as_sensor.py +++ b/examples/at_array_as_sensor/at_array_as_sensor.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Array As Sensor +# Using a kWaveArray of arc elements as a sensor for photoacoustic imaging. + +# %% import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np @@ -16,6 +21,7 @@ def main(): + # %% Define array and grid # create empty array karray = kWaveArray() @@ -44,6 +50,7 @@ def main(): # time array kgrid.makeTime(medium.sound_speed) + # %% Source and sensor source = kSource() x_offset = 20 # [pixels] # make a small disc in the top left of the domain @@ -52,6 +59,8 @@ def main(): logical_p0 = source.p0.astype(bool) sensor = kSensor() sensor.mask = element_pos + + # %% Run simulations # NOTE: data_cast="single" not supported in new API output = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp", device="gpu") # Reorder the sensor data returned by k-Wave to match the order of the elements in the array @@ -65,10 +74,7 @@ def main(): sensor_data = output["p"].T combined_sensor_data = karray.combine_sensor_data(kgrid, sensor_data) - # ========================================================================= - # VISUALIZATION - # ========================================================================= - + # %% Visualization # create pml mask (default size of 20 grid points) pml_size = 20 # [grid_points] pml_mask = np.zeros((N.x, N.y), dtype=bool) diff --git a/examples/at_array_as_source/at_array_as_source.py b/examples/at_array_as_source/at_array_as_source.py index b90a0be85..83a1eb5c6 100644 --- a/examples/at_array_as_source/at_array_as_source.py +++ b/examples/at_array_as_source/at_array_as_source.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Array As Source +# Using a kWaveArray of arc elements as a pressure source with multiple frequencies. + +# %% import matplotlib.pyplot as plt import numpy as np from matplotlib import colors @@ -15,10 +20,7 @@ def main(): - # ========================================================================= - # DEFINE KWAVEARRAY - # ========================================================================= - + # %% Define kWaveArray # create empty array karray = kWaveArray() @@ -43,10 +45,7 @@ def main(): # elements together) karray.set_array_position([10e-3, 0], 10) - # ========================================================================= - # DEFINE GRID PROPERTIES - # ========================================================================= - + # %% Grid and medium # grid properties Nx = 256 dx = 0.5e-3 @@ -60,10 +59,7 @@ def main(): # time array kgrid.makeTime(medium.sound_speed) - # ========================================================================= - # SIMULATION - # ========================================================================= - + # %% Source and simulation # assign binary mask from karray to the source mask source_p_mask = karray.get_array_binary_mask(kgrid) @@ -99,10 +95,8 @@ def main(): p_field = np.reshape(p["p"], (kgrid.Nt, Nx, Ny)) p_field = np.transpose(p_field, (0, 2, 1)) - # ========================================================================= - # VISUALIZATION - # ========================================================================= + # %% Visualization # Normalize frames based on the maximum value over all frames max_value = np.max(p_field) normalized_frames = p_field / max_value diff --git a/examples/at_circular_piston_3D/at_circular_piston_3D.py b/examples/at_circular_piston_3D/at_circular_piston_3D.py index a1d902fd2..ebd1510a6 100644 --- a/examples/at_circular_piston_3D/at_circular_piston_3D.py +++ b/examples/at_circular_piston_3D/at_circular_piston_3D.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Circular Piston Transducer in 3D +# Simulating a circular piston transducer and comparing on-axis pressure with analytical solution. + +# %% import matplotlib.pyplot as plt import numpy as np @@ -12,6 +17,7 @@ from kwave.utils.math import round_even from kwave.utils.signals import create_cw_signals +# %% Parameters # material values c0: float = 1500.0 # sound speed [m/s] rho0: float = 1000.0 # density [kg/m^3] @@ -36,14 +42,7 @@ verbose: bool = False -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - +# %% Grid setup # calculate the grid spacing based on the PPW and F0 dx: float = c0 / (ppw * source_f0) # [m] @@ -74,6 +73,7 @@ print("PPW = " + str(c0 / (dx * source_f0))) print("CFL = " + str(c0 * dt / dx)) +# %% Source, medium, sensor, and simulation # -------------------- # SOURCE # -------------------- @@ -146,10 +146,7 @@ x_vec = kgrid.x_vec[1:, :] - kgrid.x_vec[0] y_vec = kgrid.y_vec -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - +# %% Analytical solution # calculate the wavenumber k: float = 2.0 * np.pi * source_f0 / c0 @@ -171,10 +168,8 @@ # calculate error L2_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=2) Linf_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=np.inf) -# ========================================================================= -# VISUALISATION -# ========================================================================= +# %% Visualization # plot the pressure along the focal axis of the piston fig1, ax1 = plt.subplots(1, 1) ax1.plot(1e3 * x_ref, 1e-6 * p_ref, "k-", label="Exact") diff --git a/examples/at_circular_piston_AS/at_circular_piston_AS.py b/examples/at_circular_piston_AS/at_circular_piston_AS.py index eddbc66f2..da90877db 100644 --- a/examples/at_circular_piston_AS/at_circular_piston_AS.py +++ b/examples/at_circular_piston_AS/at_circular_piston_AS.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Circular Piston Transducer (Axisymmetric) +# Modelling a circular piston transducer assuming axisymmetry, compared with analytical solution. + +# %% """ Modelling A Circular Plane Piston Transducer Assuming Axisymmetry Example @@ -24,6 +29,7 @@ from kwave.utils.math import round_even from kwave.utils.signals import create_cw_signals +# %% Parameters # medium parameters c0 = 1500.0 # sound speed [m/s] rho0 = 1000.0 # density [kg/m^3] @@ -52,14 +58,7 @@ bli_tolerance = 0.05 # tolerance for truncation of the off-grid source points upsampling_rate = 10 # density of integration points relative to grid -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - +# %% Grid setup # grid resolution dx = c0 / (ppw * source_f0) # [m] @@ -83,7 +82,7 @@ Nt = round(t_end / dt) kgrid.setTime(Nt, dt) - +# %% Source, medium, sensor, and simulation # -------------------- # SOURCE # -------------------- @@ -134,17 +133,9 @@ # record only the final few periods when the field is in steady state sensor.record_start_index = kgrid.Nt - record_periods * ppp + 1 -# ========================================================================= -# DEFINE THE SIMULATION PARAMETERS -# ========================================================================= - # NOTE: pml_inside=False not supported in new API # NOTE: simulation_type=SimulationType.AXISYMMETRIC is handled automatically by the new API -# ========================================================================= -# RUN THE SIMULATION -# ========================================================================= - sensor_data = kspaceFirstOrder( kgrid, medium, @@ -169,10 +160,7 @@ y_vec = 1e3 * np.hstack((-np.flip(yvec)[:-1], yvec)) x_vec = 1e3 * np.squeeze(kgrid.x_vec[:, :] - kgrid.x_vec[0]) -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - +# %% Analytical solution # calculate the wavenumber k: float = 2.0 * np.pi * source_f0 / c0 @@ -195,10 +183,7 @@ # L2_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=2) # Linf_error = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=np.inf) -# ========================================================================= -# VISUALISATION -# ========================================================================= - +# %% Visualization data = np.hstack((np.fliplr(amp[:, :-1]), amp)) / 1e6 sp = np.hstack((np.fliplr(source.p_mask[:, :])[:, :-1], source.p_mask)) diff --git a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py b/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py index 45a93430c..ab22bf1b5 100644 --- a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py +++ b/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Focused Annular Array in 3D +# Simulating a focused annular array transducer and comparing with analytical O'Neil solution. + +# %% import matplotlib.pyplot as plt import numpy as np @@ -13,6 +18,7 @@ from kwave.utils.math import round_even from kwave.utils.signals import create_cw_signals +# %% Parameters verbose: bool = False # medium parameters @@ -43,14 +49,7 @@ upsampling_rate: int = 10 # density of integration points relative to grid verbose_level: int = 0 # verbosity of k-wave executable -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - +# %% Grid setup # calculate the grid spacing based on the points per wavelength and the frequency dx: float = c0 / (ppw * source_f0) # [m] @@ -79,6 +78,7 @@ print("PPW = " + str(c0 / (dx * source_f0))) print("CFL = " + str(c0 * dt / dx)) +# %% Source, medium, sensor, and simulation # -------------------- # SOURCE # -------------------- @@ -156,18 +156,12 @@ x_vec = kgrid.x_vec[source_x_offset + 1 :, :] - kgrid.x_vec[source_x_offset] y_vec = kgrid.y_vec -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - +# %% Analytical solution p_axial = focused_annulus_oneil( source_roc, np.asarray(diameters).T, source_amp / (c0 * rho0), source_phase, source_f0, c0, rho0, np.squeeze(x_vec) ) -# ========================================================================= -# VISUALISATION -# ========================================================================= - +# %% Visualization # plot the pressure along the focal axis of the piston fig1, ax1 = plt.subplots(1, 1) ax1.plot(1e3 * x_vec, 1e-6 * p_axial, "k-", label="Exact") diff --git a/examples/at_focused_bowl_3D/at_focused_bowl_3D.py b/examples/at_focused_bowl_3D/at_focused_bowl_3D.py index a4027da29..476e0f5a6 100644 --- a/examples/at_focused_bowl_3D/at_focused_bowl_3D.py +++ b/examples/at_focused_bowl_3D/at_focused_bowl_3D.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Focused Bowl Transducer in 3D +# Modelling a focused bowl transducer and comparing on-axis pressure with O'Neil analytical solution. + +# %% import matplotlib.pyplot as plt import numpy as np @@ -13,11 +18,7 @@ from kwave.utils.math import round_even from kwave.utils.signals import create_cw_signals -# Modelling A Focused Bowl Transducer In 3D Example - -# This example models a focused bowl transducer in 3D. The on-axis pressure -# is compared with the exact solution calculated using focused_bowl_oneil. - +# %% Parameters verbose: bool = False # medium parameters @@ -44,14 +45,7 @@ bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points upsampling_rate: int = 10 # density of integration points relative to grid -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - +# %% Grid setup # calculate the grid spacing based on the PPW and F0 dx: float = c0 / (ppw * source_f0) # [m] @@ -81,6 +75,7 @@ print("PPW = " + str(c0 / (dx * source_f0))) print("CFL = " + str(c0 * dt / dx)) +# %% Source, medium, sensor, and simulation # -------------------- # SOURCE # -------------------- @@ -158,10 +153,7 @@ x_vec = kgrid.x_vec[(source_x_offset + 1) : -1, :] - kgrid.x_vec[source_x_offset] y_vec = kgrid.y_vec -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - +# %% Analytical solution # calculate the wavenumber knumber = 2.0 * np.pi * source_f0 / c0 @@ -181,10 +173,7 @@ L2_error = 100 * np.linalg.norm(p_ref_axial_kw - amp_on_axis, ord=2) Linf_error = 100 * np.linalg.norm(p_ref_axial_kw - amp_on_axis, ord=np.inf) -# ========================================================================= -# VISUALISATION -# ========================================================================= - +# %% Visualization # plot the pressure along the focal axis of the piston fig1, ax1 = plt.subplots(1, 1) ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, "k-", label="Exact") diff --git a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py b/examples/at_focused_bowl_AS/at_focused_bowl_AS.py index 2c648db29..6c32c3507 100644 --- a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py +++ b/examples/at_focused_bowl_AS/at_focused_bowl_AS.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Focused Bowl Transducer (Axisymmetric) +# Modelling a focused bowl transducer assuming axisymmetry, compared with O'Neil analytical solution. + +# %% import matplotlib.pyplot as plt import numpy as np @@ -13,6 +18,7 @@ from kwave.utils.math import round_even from kwave.utils.signals import create_cw_signals +# %% Parameters verbose: bool = False # medium parameters @@ -39,14 +45,7 @@ bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points upsampling_rate: int = 10 # density of integration points relative to grid -# ========================================================================= -# RUN SIMULATION -# ========================================================================= - -# -------------------- -# GRID -# -------------------- - +# %% Grid setup # calculate the grid spacing based on the PPW and F0 dx: float = c0 / (ppw * source_f0) # [m] @@ -75,6 +74,7 @@ print("PPW = " + str(c0 / (dx * source_f0))) print("CFL = " + str(c0 * dt / dx)) +# %% Source, medium, sensor, and simulation # -------------------- # SOURCE # -------------------- @@ -130,10 +130,6 @@ # NOTE: pml_inside=False, data_cast="single" not supported in new API # NOTE: simulation_type=SimulationType.AXISYMMETRIC is handled automatically by the new API -# ========================================================================= -# RUN THE SIMULATION -# ========================================================================= - sensor_data = kspaceFirstOrder( kgrid, medium, @@ -156,10 +152,7 @@ x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1) :, :] - kgrid.x_vec[source_x_offset]) y_vec = kgrid.y_vec -# ========================================================================= -# ANALYTICAL SOLUTION -# ========================================================================= - +# %% Analytical solution # calculate the wavenumber knumber = 2.0 * np.pi * source_f0 / c0 @@ -176,11 +169,7 @@ source_roc, source_diameter, source_amp[0] / (c0 * rho0), source_f0, c0, rho0, axial_positions=x_vec ) - -# ========================================================================= -# VISUALISATION -# ========================================================================= - +# %% Visualization # plot the pressure along the focal axis of the piston fig1, ax1 = plt.subplots(1, 1) ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, "k-", label="Exact") diff --git a/examples/at_linear_array_transducer/at_linear_array_transducer.py b/examples/at_linear_array_transducer/at_linear_array_transducer.py index 55b894a91..c17f31ef0 100644 --- a/examples/at_linear_array_transducer/at_linear_array_transducer.py +++ b/examples/at_linear_array_transducer/at_linear_array_transducer.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Linear Array Transducer +# Simulating a focused linear array transducer in 3D with time-delayed elements. + +# %% import matplotlib.pyplot as plt import numpy as np @@ -14,6 +19,7 @@ def main(): + # %% Parameters and grid c0 = 1500 rho0 = 1000 source_f0 = 1e6 @@ -41,7 +47,7 @@ def main(): kgrid = kWaveGrid([Nx, Ny, Nz], [dx, dx, dx]) kgrid.makeTime(c0, cfl, t_end) - # SOURCE + # %% Source if element_num % 2 != 0: ids = np.arange(1, element_num + 1) - np.ceil(element_num / 2) else: @@ -63,6 +69,7 @@ def main(): voxel_plot(np.single(source.p_mask)) source.p = karray.get_distributed_source_signal(kgrid, source_sig) + # %% Medium, sensor, and simulation # MEDIUM medium = kWaveMedium(sound_speed=c0, density=rho0) @@ -85,7 +92,7 @@ def main(): p_max = np.reshape(sensor_data["p_max"], (Nx, Nz), order="F") - # VISUALISATION + # %% Visualization plt.figure() plt.imshow( 1e-6 * p_max, diff --git a/examples/checkpointing/checkpoint.py b/examples/checkpointing/checkpoint.py index 1f4590dfd..cd89cac7c 100644 --- a/examples/checkpointing/checkpoint.py +++ b/examples/checkpointing/checkpoint.py @@ -1,3 +1,7 @@ +# %% [markdown] +# # Checkpointing Example +# Demonstrates how to use k-Wave's checkpointing feature to resume a simulation from a saved state. + from copy import copy from pathlib import Path from tempfile import TemporaryDirectory diff --git a/examples/ivp_1D_simulation.py b/examples/ivp_1D_simulation.py index 41e83d26d..daffc29c3 100644 --- a/examples/ivp_1D_simulation.py +++ b/examples/ivp_1D_simulation.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # 1D Initial Value Problem +# Heterogeneous medium with reflections at impedance boundaries. + +# %% """ 1D Initial Value Problem — heterogeneous medium with reflections. @@ -15,7 +20,7 @@ from kwave.ksource import kSource from kwave.kspaceFirstOrder import kspaceFirstOrder -# -- Grid -- +# %% Grid and medium Nx = 512 dx = 0.05e-3 # [m] kgrid = kWaveGrid(Vector([Nx]), Vector([dx])) @@ -32,7 +37,7 @@ # -- Time stepping -- kgrid.makeTime(sound_speed, cfl=0.3) -# -- Source: smooth sinusoidal pulse -- +# %% Source and sensor source = kSource() source.p0 = np.zeros(Nx) x0, width = 280, 100 @@ -45,12 +50,12 @@ sensor_mask[3 * Nx // 4] = 1 # right sensor sensor = kSensor(mask=sensor_mask) -# -- Run -- +# %% Run simulation result = kspaceFirstOrder(kgrid, medium, source, sensor, backend="python") print(f"Sensor data shape: {result['p'].shape}") # (2, Nt) -# -- Plot -- +# %% Visualization t_us = np.arange(kgrid.Nt) * float(kgrid.dt) * 1e6 x_mm = np.arange(Nx) * dx * 1e3 diff --git a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py b/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py index b50b661cc..736da94c2 100644 --- a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py +++ b/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Photoacoustic Waveforms +# Comparing photoacoustic waveforms from 2D disc and 3D ball sources. + +# %% import matplotlib.pyplot as plt import numpy as np @@ -10,6 +15,7 @@ from kwave.utils.data import scale_SI from kwave.utils.mapgen import make_ball, make_disc +# %% Parameters # number of grid points in the x (row) direction Nx: int = 64 @@ -32,8 +38,7 @@ dt: float = 2e-9 # [s] t_end: float = 300e-9 # [s] -####### - +# %% 2D simulation # medium medium2 = kWaveMedium(sound_speed=1500) # create the k-space grid @@ -70,8 +75,7 @@ device="gpu", ) -############ - +# %% 3D simulation # medium medium3 = kWaveMedium(sound_speed=1500) @@ -108,6 +112,7 @@ device="gpu", ) +# %% Visualization # plot the simulations t_sc, t_scale, t_prefix, _ = scale_SI(t_end) _, ax1 = plt.subplots() diff --git a/examples/new_api_ivp_2D.py b/examples/new_api_ivp_2D.py index 46adf6e13..7d0595b8a 100644 --- a/examples/new_api_ivp_2D.py +++ b/examples/new_api_ivp_2D.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # 2D Initial Value Problem (New API) +# A disc-shaped initial pressure propagates outward in a homogeneous medium. + +# %% """ 2D Initial Value Problem using the new unified API. @@ -14,6 +19,7 @@ from kwave.kspaceFirstOrder import kspaceFirstOrder from kwave.utils.mapgen import make_disc +# %% Setup and run # Grid grid_size = Vector([128, 128]) grid_spacing = Vector([0.1e-3, 0.1e-3]) diff --git a/examples/new_api_transducer_3D.py b/examples/new_api_transducer_3D.py index 13cdcd97c..1a791d0a5 100644 --- a/examples/new_api_transducer_3D.py +++ b/examples/new_api_transducer_3D.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # 3D Transducer Simulation (New API) +# Demonstrates 3D wave propagation with the unified kspaceFirstOrder() function. + +# %% """ 3D simulation using the new unified API. @@ -14,6 +19,7 @@ from kwave.ksource import kSource from kwave.kspaceFirstOrder import kspaceFirstOrder +# %% Setup and run # 3D grid for demonstration grid_size = Vector([64, 64, 64]) grid_spacing = Vector([0.1e-3, 0.1e-3, 0.1e-3]) diff --git a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py b/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py index ae05f527d..0a34c9e56 100644 --- a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py +++ b/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # 2D FFT Reconstruction For A Line Sensor Example +# Reconstruct a 2D photoacoustic wave-field from a linear array using kspaceLineRecon. + +# %% import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.axes_grid1 import make_axes_locatable @@ -14,15 +19,8 @@ from kwave.utils.filters import smooth from kwave.utils.mapgen import make_disc -# 2D FFT Reconstruction For A Line Sensor Example - -# This example demonstrates the use of k-Wave for the reconstruction of a -# two-dimensional photoacoustic wave-field recorded over a linear array of -# sensor elements The sensor data is simulated using kspaceFirstOrder2D -# and reconstructed using kspaceLineRecon. It builds on the Homogeneous -# Propagation Medium and Heterogeneous Propagation Medium examples. - +# %% def main(): # -------------------- # SIMULATION @@ -62,6 +60,7 @@ def main(): # create the time array kgrid.makeTime(medium.sound_speed) + # %% # NOTE: pml_inside=False not supported in new API # run the simulation sensor_data = kspaceFirstOrder( @@ -91,6 +90,7 @@ def main(): query_points = np.stack((kgrid.x - kgrid.x.min(), kgrid.y), axis=-1) p_xy_rs = interp_func(query_points) + # %% # -------------------- # VISUALIZATION # -------------------- @@ -149,5 +149,6 @@ def main(): plt.show() +# %% if __name__ == "__main__": main() diff --git a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py index 468ed0bb6..37d1effcc 100644 --- a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py +++ b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # 2D Time Reversal Reconstruction For A Line Sensor Example +# Reconstruct a 2D photoacoustic wave-field using time reversal with a linear array of sensor elements. + +# %% import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.axes_grid1 import make_axes_locatable @@ -15,15 +20,8 @@ from kwave.utils.filters import smooth from kwave.utils.mapgen import make_disc -# 2D Time Reversal Reconstruction For A Line Sensor Example - -# This example demonstrates the use of k-Wave for the time-reversal -# reconstruction of a two-dimensional photoacoustic wave-field recorded -# over a linear array of sensor elements. The sensor data is simulated and -# then time-reversed using kspaceFirstOrder2D. It builds on the 2D FFT -# Reconstruction For A Line Sensor Example. - +# %% def main(): # -------------------- # SIMULATION @@ -67,6 +65,8 @@ def main(): inner_mask[0, :] = 1 # Line sensor along the first row sensor.mask = inner_mask sensor.record = ["p", "p_final"] + + # %% # NOTE: pml_inside=False, data_cast="single" not supported in new API # run the simulation sensor_data = kspaceFirstOrder( @@ -115,6 +115,7 @@ def main(): query_points = np.stack((kgrid.x - kgrid.x.min(), kgrid.y), axis=-1) p_xy_rs = interp_func(query_points) + # %% # -------------------- # VISUALIZATION # -------------------- @@ -186,5 +187,6 @@ def main(): plt.show() +# %% if __name__ == "__main__": main() diff --git a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py b/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py index dde4501b7..e264f0eb5 100644 --- a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py +++ b/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # 3D FFT Reconstruction For A Planar Sensor Example +# Reconstruct a 3D photoacoustic wave-field from a planar sensor array using kspacePlaneRecon. + +# %% import matplotlib.pyplot as plt import numpy as np from scipy.interpolate import RegularGridInterpolator @@ -13,15 +18,8 @@ from kwave.utils.filters import smooth from kwave.utils.mapgen import make_ball -# 3D FFT Reconstruction For A Planar Sensor Example - -# This example demonstrates the use of k-Wave for the reconstruction of a -# three-dimensional photoacoustic wave-field recorded over a planar array -# of sensor elements. The sensor data is simulated using kspaceFirstOrder3D -# and reconstructed using kspacePlaneRecon. It builds on the Simulations In -# Three Dimensions and 2D FFT Reconstruction For A Line Sensor examples. - +# %% def main(): # -------------------- # SIMULATION @@ -53,6 +51,7 @@ def main(): sensor_mask[0] = 1 sensor.mask = sensor_mask + # %% # NOTE: pml_inside=False, data_cast="single" not supported in new API # run the simulation sensor_data = kspaceFirstOrder( @@ -98,6 +97,7 @@ def main(): ) p_xyz_rs = interp_func(query_points) + # %% # -------------------- # VISUALIZATION # -------------------- @@ -204,5 +204,6 @@ def main(): plt.show() +# %% if __name__ == "__main__": main() diff --git a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py index 224759126..cfd50e435 100644 --- a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py +++ b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # 3D Time Reversal Reconstruction For A Planar Sensor Example +# Reconstruct a 3D photoacoustic wave-field using time reversal with a planar sensor array. + +# %% import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.axes_grid1 import make_axes_locatable @@ -13,16 +18,8 @@ from kwave.utils.filters import smooth from kwave.utils.mapgen import make_ball -# 3D Time Reversal Reconstruction For A Planar Sensor Example - -# This example demonstrates the use of k-Wave for the reconstruction -# of a three-dimensional photoacoustic wave-field recorded over a planar -# array of sensor elements. The sensor data is simulated and then -# time-reversed using kspaceFirstOrder3D. It builds on the 3D FFT -# Reconstruction For A Planar Sensor and 2D Time Reversal Reconstruction -# For A Line Sensor examples. - +# %% def main(): # -------------------- # SIMULATION @@ -63,6 +60,7 @@ def main(): sensor.mask[0, :, :] = 1 # Planar sensor along the first x-plane sensor.record = ["p", "p_final"] + # %% # NOTE: pml_inside=False, data_cast="single" not supported in new API # run the simulation sensor_data = kspaceFirstOrder( @@ -97,6 +95,7 @@ def main(): execution_options = SimulationExecutionOptions(is_gpu_simulation=True) p0_recon = tr(kspaceFirstOrder3D, simulation_options, execution_options) + # %% # -------------------- # VISUALIZATION # -------------------- @@ -219,5 +218,6 @@ def main(): plt.show() +# %% if __name__ == "__main__": main() diff --git a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py b/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py index 9f8533e9f..de819ca9c 100644 --- a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py +++ b/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Sensor Directivity Modelling in 2D +# Model the directional response of a finite-sized sensor element in 2D. + +# %% import matplotlib.pyplot as plt import numpy as np @@ -14,6 +19,7 @@ from kwave.utils.mapgen import make_cart_circle from kwave.utils.matlab import ind2sub, matlab_find, unflatten_matlab_mask +# %% grid_size_points = Vector([128, 128]) # [grid points] grid_size_meters = Vector([50e-3, 50e-3]) # [m] grid_spacing_meters = grid_size_meters / grid_size_points # [m] @@ -54,6 +60,7 @@ # filter the source to remove high frequencies not supported by the grid source.p = filter_time_series(kgrid, medium, source.p) +# %% # pre-allocate array for storing the output time series single_element_data = np.zeros((Nt, points)) # noqa: F841 @@ -75,6 +82,7 @@ ) single_element_data[:, source_loop] = sensor_data["p"].sum(axis=1) +# %% plt.figure() plt.imshow( circle + sensor.mask, diff --git a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py b/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py index 034a84b41..790a8e2e8 100644 --- a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py +++ b/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py @@ -1,6 +1,8 @@ +# %% [markdown] # # Focussed Detector In 2D Example -# This example shows how k-Wave-python can be used to model the output of a focused semicircular detector, where the directionality arises from spatially averaging across the detector surface. Unlike the original example in k-Wave, this example does not visualize the simulation, as this functionality is not intrinsically supported by the accelerated binaries. +# Model the output of a focused semicircular detector with spatial averaging across its surface. +# %% import matplotlib.pyplot as plt import numpy as np @@ -13,9 +15,7 @@ from kwave.utils.data import scale_SI from kwave.utils.mapgen import make_circle, make_disc -# In[3]: - - +# %% # create the computational grid grid_size = Vector([180, 180]) # [grid points] grid_spacing = Vector([0.1e-3, 0.1e-3]) # [m] @@ -34,7 +34,7 @@ t_end = 11e-6 # [s] _ = kgrid.makeTime(medium.sound_speed, t_end=t_end) - +# %% ## Run simulation with first source # place a disc-shaped source near the focus of the detector source = kSource() @@ -53,10 +53,9 @@ sensor_data1["p"].shape - +# %% ## Run simulation with second source - # place a disc-shaped source horizontally shifted from the focus of the detector source.p0 = 2 * make_disc(grid_size, grid_size / 2 + [0, 20], 4) @@ -69,10 +68,9 @@ device="cpu", ) - +# %% ## Visualize recorded data - sensor_output1 = np.sum(sensor_data1["p"], axis=1) / np.sum(sensor.mask) sensor_output2 = np.sum(sensor_data2["p"], axis=1) / np.sum(sensor.mask) diff --git a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py b/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py index fc1a13147..3ed614d74 100644 --- a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py +++ b/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py @@ -1,6 +1,8 @@ -# Focussed Detector In 3D Example -# This example shows how k-Wave can be used to model the output of a focussed bowl detector where the directionality arises from spatially averaging across the detector surface. +# %% [markdown] +# # Focussed Detector In 3D Example +# Model the output of a focussed bowl detector with spatial averaging across its surface. +# %% import matplotlib.pyplot as plt import numpy as np @@ -14,6 +16,7 @@ from kwave.utils.filters import filter_time_series from kwave.utils.mapgen import make_bowl +# %% # create the computational grid grid_size = Vector([64, 64, 64]) # [grid points] grid_spacing_single = 100e-3 / grid_size.x @@ -44,6 +47,7 @@ source.p = source_mag * np.sin(2 * np.pi * source_freq * kgrid.t_array) source.p = filter_time_series(kgrid, medium, source.p) +# %% # place the first point source near the focus of the detector source1 = np.zeros(grid_size) source1[int(sphere_offset + radius), grid_size.y // 2 - 1, grid_size.z // 2 - 1] = 1 @@ -64,6 +68,7 @@ # average the data recorded at each grid point to simulate the measured signal from a single element focused detector sensor_data1 = np.sum(sensor_data1["p"], axis=1) +# %% # place the second point source off axis source2 = np.zeros(grid_size) source2[int(1 + sphere_offset + radius), grid_size.y // 2 + 5, grid_size.z // 2 + 5] = 1 @@ -84,6 +89,7 @@ # average the data recorded at each grid point to simulate the measured signal from a single element focused detector sensor_data2 = np.sum(sensor_data2["p"], axis=1) +# %% # Combine arrays as in MATLAB: sensor.mask + source1 + source2 combined_array = sensor_mask + source1 + source2 diff --git a/examples/unified_entry_point_demo.py b/examples/unified_entry_point_demo.py index fbf35e6bf..b9d83ddfb 100644 --- a/examples/unified_entry_point_demo.py +++ b/examples/unified_entry_point_demo.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Unified Entry Point Demo +# Demonstrates the new single-function kspaceFirstOrder() API across backends and PML options. + +# %% """ Unified Entry Point Demo — kspaceFirstOrder() @@ -13,9 +18,7 @@ from kwave.ksource import kSource from kwave.kspaceFirstOrder import kspaceFirstOrder -# ============================================================ -# Setup (same for all backends) -# ============================================================ +# %% Common setup grid_size = Vector([128, 128]) kgrid = kWaveGrid(grid_size, Vector([0.1e-3, 0.1e-3])) kgrid.makeTime(1500) @@ -29,30 +32,22 @@ sensor = kSensor(mask=np.ones((128, 128), dtype=bool)) -# ============================================================ -# Example 1: Native CPU (default) -# ============================================================ +# %% Example 1: Native CPU (default) print("Running native CPU simulation...") result = kspaceFirstOrder(kgrid, medium, source, sensor) print(f" Sensor data: {result['p'].shape}") -# ============================================================ -# Example 2: Custom PML -# ============================================================ +# %% Example 2: Custom PML print("\nRunning with custom PML (10, 15)...") result = kspaceFirstOrder(kgrid, medium, source, sensor, pml_size=(10, 15)) print(f" Sensor data: {result['p'].shape}") -# ============================================================ -# Example 3: Auto PML -# ============================================================ +# %% Example 3: Auto PML print("\nRunning with auto PML...") result = kspaceFirstOrder(kgrid, medium, source, sensor, pml_size="auto") print(f" Sensor data: {result['p'].shape}") -# ============================================================ -# Example 4: C++ save_only (writes HDF5 for cluster submission) -# ============================================================ +# %% Example 4: C++ save_only (writes HDF5 for cluster submission) import tempfile print("\nSaving HDF5 for C++ binary...") @@ -67,9 +62,7 @@ ) print(f" Input file: {result['input_file']}") -# ============================================================ -# Example 5: Migrating from legacy options -# ============================================================ +# %% Example 5: Migrating from legacy options print("\nMigrating from legacy options...") from kwave.compat import options_to_kwargs from kwave.options.simulation_options import SimulationOptions diff --git a/examples/us_beam_patterns/us_beam_patterns.py b/examples/us_beam_patterns/us_beam_patterns.py index 631036214..1efc9c1db 100644 --- a/examples/us_beam_patterns/us_beam_patterns.py +++ b/examples/us_beam_patterns/us_beam_patterns.py @@ -1,9 +1,8 @@ -#!/usr/bin/env python -# coding: utf-8 - -# In[ ]: - +# %% [markdown] +# # Ultrasound Beam Patterns Example +# Compute and visualize beam patterns from a linear transducer array in 3D. +# %% import matplotlib.pyplot as plt import numpy as np @@ -17,9 +16,7 @@ from kwave.utils.math import find_closest from kwave.utils.signals import tone_burst -# In[ ]: - - +# %% # simulation settings # set to 'xy' or 'xz' to generate the beam pattern in different planes MASK_PLANE = "xy" @@ -40,10 +37,7 @@ kgrid = kWaveGrid([Nx, Ny, Nz], [dx, dy, dz]) - -# In[ ]: - - +# %% # define the medium medium = kWaveMedium(sound_speed=1540, density=1000, alpha_coeff=0.75, alpha_power=1.5, BonA=6) @@ -52,10 +46,6 @@ kgrid.makeTime(medium.sound_speed, t_end=t_end) - -# In[ ]: - - # define the input signal source_strength = 1e6 tone_burst_freq = 0.5e6 @@ -63,10 +53,7 @@ input_signal = tone_burst(1 / kgrid.dt, tone_burst_freq, tone_burst_cycles) input_signal = (source_strength / (medium.sound_speed * medium.density)) * input_signal - -# In[ ]: - - +# %% # define the transducer transducer = dotdict() transducer.number_elements = 32 @@ -82,10 +69,6 @@ transducer.position = np.round([1, Ny / 2 - transducer_width / 2, Nz / 2 - transducer.element_length / 2]) transducer = kWaveTransducerSimple(kgrid, **transducer) - -# In[ ]: - - not_transducer = dotdict() not_transducer.sound_speed = medium.sound_speed # sound speed [m/s] not_transducer.focus_distance = 20e-3 # focus distance [m] @@ -98,10 +81,7 @@ not_transducer = NotATransducer(transducer, kgrid, **not_transducer) - -# In[ ]: - - +# %% sensor_mask = np.zeros((Nx, Ny, Nz)) if MASK_PLANE == "xy": @@ -117,10 +97,6 @@ j_vec = kgrid.z_vec j_label = "z" - -# In[ ]: - - sensor = kSensor(sensor_mask) if USE_STATISTICS: sensor.record = ["p_rms", "p_max"] @@ -137,10 +113,7 @@ pml_size=(PML_X_SIZE, PML_Y_SIZE, PML_Z_SIZE), ) - -# In[ ]: - - +# %% if USE_STATISTICS: fig, axes = plt.subplots(1, 2) fig.set_figwidth(8) @@ -170,10 +143,7 @@ plt.tight_layout() plt.show() - -# In[ ]: - - +# %% if not USE_STATISTICS: sensor_data_array = np.reshape(sensor_data["p"], [kgrid.Nt, kgrid.Ny, kgrid.Nx]).transpose(2, 1, 0) # compute the amplitude spectrum @@ -192,10 +162,7 @@ # extract the integral of the total amplitude spectrum beam_pattern_total = np.squeeze(np.sum(amp_spect, axis=2)) - -# In[ ]: - - +# %% if not USE_STATISTICS: fig, axes = plt.subplots(1, 3) fig.set_figwidth(8) @@ -226,10 +193,7 @@ plt.tight_layout() plt.show() - -# In[ ]: - - +# %% if not USE_STATISTICS: # Compute the directivity at each of the harmonics directivity_f1 = beam_pattern_f1[round(not_transducer.focus_distance / dx), :] diff --git a/examples/us_bmode_linear_transducer/example_utils.py b/examples/us_bmode_linear_transducer/example_utils.py index da15cc965..f695769bf 100644 --- a/examples/us_bmode_linear_transducer/example_utils.py +++ b/examples/us_bmode_linear_transducer/example_utils.py @@ -1,3 +1,7 @@ +# %% [markdown] +# # B-Mode Linear Transducer Utilities +# Helper functions for downloading example data from Google Drive. + import os diff --git a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py b/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py index a5314dbbd..0ec9bce85 100644 --- a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py +++ b/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Ultrasound B-Mode Linear Transducer Example +# Simulate and process B-mode ultrasound images using a linear transducer array. + +# %% import logging import matplotlib.pyplot as plt @@ -17,6 +22,7 @@ from kwave.utils.filters import gaussian_filter from kwave.utils.signals import get_win, tone_burst +# %% SENSOR_DATA_GDRIVE_ID = "1lGFTifpOrzBYT4Bl_ccLu_Kx0IDxM0Lv" PHANTOM_DATA_GDRIVE_ID = "1ZfSdJPe8nufZHz0U9IuwHR4chaOGAWO4" PHANTOM_DATA_PATH = "phantom_data.mat" @@ -50,6 +56,7 @@ BonA=6, ) +# %% transducer = dotdict() transducer.number_elements = 32 # total number of transducer elements transducer.element_width = 2 # width of each element [grid points/voxels] @@ -77,7 +84,7 @@ not_transducer = NotATransducer(transducer, kgrid, **not_transducer) - +# %% logging.log(logging.INFO, "Fetching phantom data...") download_if_does_not_exist(PHANTOM_DATA_GDRIVE_ID, PHANTOM_DATA_PATH) @@ -129,7 +136,7 @@ scan_lines = simulation_data - +# %% # ## PROCESS THE RESULTS # ### Remove Input Signal # @@ -205,9 +212,8 @@ scan_lines_fund_log_ex = scan_lines_fund[len(scan_lines_fund) // 2, :] # scan_lines_harm_log_ex = scan_lines_harm[len(scan_lines_harm) // 2, :] - +# %% # ### Visualization -# # Set the desired size of the image @@ -245,10 +251,7 @@ ax.set_ylim(40, 5) plt.tight_layout() - -# In[ ]: - - +# %% # Creating a dictionary with the step labels as keys processing_steps = { "1. Beamformed Signal": scan_lines_no_input, diff --git a/examples/us_bmode_phased_array/us_bmode_phased_array.py b/examples/us_bmode_phased_array/us_bmode_phased_array.py index 5492506af..0c877c605 100644 --- a/examples/us_bmode_phased_array/us_bmode_phased_array.py +++ b/examples/us_bmode_phased_array/us_bmode_phased_array.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Ultrasound B-Mode Phased Array Example +# Simulate and process B-mode ultrasound images using a phased array transducer with steering. + +# %% import numpy as np import scipy.io from matplotlib import pyplot as plt @@ -15,6 +20,7 @@ from kwave.utils.mapgen import make_ball from kwave.utils.signals import get_win, tone_burst +# %% # simulation settings RUN_SIMULATION = True @@ -39,10 +45,7 @@ t_end = (grid_size_points.x * grid_spacing_meters.x) * 2.2 / c0 # [s] kgrid.makeTime(c0, t_end=t_end) - -# In[ ]: - - +# %% source_strength = 1e6 # [Pa] tone_burst_freq = 1e6 # [Hz] tone_burst_cycles = 4 @@ -78,7 +81,7 @@ not_transducer = NotATransducer(transducer, kgrid, **not_transducer) - +# %% # Define a random distribution of scatterers for the medium background_map_mean = 1 background_map_std = 0.008 @@ -108,7 +111,7 @@ medium.sound_speed = sound_speed_map medium.density = density_map - +# %% # Range of steering angles to test steering_angles = np.arange(-32, 33, 2) @@ -140,7 +143,7 @@ else: scan_lines = scipy.io.loadmat("example_us_phased_array_scan_lines")["scan_lines"] - +# %% # PROCESS THE RESULTS # Remove Input Signal # Trim the delay offset from the scan line data @@ -188,14 +191,11 @@ # Log Compression -# In[ ]: - - compression_ratio = 3 scan_lines_fund = log_compression(scan_lines_fund, compression_ratio, True) scan_lines_harm = log_compression(scan_lines_harm, compression_ratio, True) - +# %% # Visualization image_size = [kgrid.Nx * kgrid.dx, kgrid.Ny * kgrid.dy] image_res = [256, 256] diff --git a/examples/us_defining_transducer/us_defining_transducer.py b/examples/us_defining_transducer/us_defining_transducer.py index 6beb19c7f..043ef2b99 100644 --- a/examples/us_defining_transducer/us_defining_transducer.py +++ b/examples/us_defining_transducer/us_defining_transducer.py @@ -1,3 +1,8 @@ +# %% [markdown] +# # Defining An Ultrasound Transducer Example +# Define a transducer array, run a simulation, and analyze the recorded signals and spectra. + +# %% import matplotlib.pyplot as plt import numpy as np @@ -11,6 +16,7 @@ from kwave.utils.plot import voxel_plot from kwave.utils.signals import tone_burst +# %% # define the grid PML_X_SIZE = 20 PML_Y_SIZE = 10 @@ -39,6 +45,7 @@ input_signal = tone_burst(1 / kgrid.dt, tone_burst_freq, tone_burst_cycles) input_signal = (source_strength / (medium.sound_speed * medium.density)) * input_signal +# %% # define the transducer transducer = dotdict() transducer.number_elements = 72 @@ -66,6 +73,7 @@ voxel_plot(np.single(not_transducer.all_elements_mask)) +# %% # define sensor mask sensor_mask = np.zeros((Nx, Ny, Nz)) sensor_mask[Nx // 4, Ny // 2, Nz // 2] = 1 @@ -86,6 +94,7 @@ pml_size=(PML_X_SIZE, PML_Y_SIZE, PML_Z_SIZE), ) +# %% padded_input_signal = np.concatenate((input_signal, np.zeros((1, 2 * np.shape(input_signal)[1]))), axis=1) f_input, as_input, _ = spect(padded_input_signal, 1 / kgrid.dt) _, as_1, _ = spect(sensor_data["p"][:, 0], 1 / kgrid.dt) From 0835d5b1d776633672fba5bfcf54276ca09c1f10 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:30:22 -0700 Subject: [PATCH 05/21] Delete .ipynb notebooks and per-example READMEs, add top-level examples README .py files with # %% markers are the source of truth. Notebooks will be auto-generated via jupytext at release time. Per-example READMEs replaced by inline markdown headers in each .py file. Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/README.md | 108 ++-- examples/at_array_as_sensor/README.md | 7 - .../at_array_as_sensor.ipynb | 299 ----------- examples/at_array_as_source/README.md | 9 - .../at_array_as_source.ipynb | 262 ---------- examples/at_circular_piston_3D/README.md | 5 - .../at_circular_piston_3D.ipynb | 455 ----------------- examples/at_circular_piston_AS/README.md | 5 - .../at_circular_piston_AS.ipynb | 383 -------------- .../at_focused_annular_array_3D/README.md | 5 - .../at_focused_annular_array_3D.ipynb | 452 ----------------- examples/at_focused_bowl_3D/README.md | 5 - .../at_focused_bowl_3D.ipynb | 451 ----------------- examples/at_focused_bowl_AS/README.md | 5 - .../at_focused_bowl_AS.ipynb | 384 -------------- examples/at_linear_array_transducer/README.md | 7 - .../at_linear_array_transducer.ipynb | 201 -------- examples/checkpointing/README.md | 5 - .../ivp_photoacoustic_waveforms/README.md | 16 - .../ivp_photoacoustic_waveforms.ipynb | 284 ----------- examples/na_controlling_the_pml/README.md | 21 - .../na_controlling_the_pml.ipynb | 232 --------- examples/pr_2D_FFT_line_sensor/README.md | 7 - .../pr_2D_FFT_line_sensor.ipynb | 298 ----------- examples/pr_2D_TR_line_sensor/README.md | 7 - .../pr_2D_TR_line_sensor.ipynb | 335 ------------ examples/pr_3D_FFT_planar_sensor/README.md | 7 - .../pr_3D_FFT_planar_sensor.ipynb | 313 ------------ examples/pr_3D_TR_planar_sensor/README.md | 7 - .../pr_3D_TR_planar_sensor.ipynb | 307 ----------- .../sd_directivity_modelling_2D/README.md | 7 - .../sd_directivity_modelling_2D.ipynb | 244 --------- examples/sd_focussed_detector_2D/README.md | 7 - .../sd_focussed_detector_2D.ipynb | 207 -------- examples/sd_focussed_detector_3D/README.md | 7 - .../sd_focussed_detector_3D.ipynb | 275 ---------- examples/us_beam_patterns/README.md | 7 - .../us_beam_patterns/us_beam_patterns.ipynb | 345 ------------- examples/us_bmode_linear_transducer/README.md | 8 - .../us_bmode_linear_transducer.ipynb | 478 ------------------ examples/us_bmode_phased_array/README.md | 9 - .../us_bmode_phased_array.ipynb | 468 ----------------- examples/us_defining_transducer/README.md | 8 - .../us_defining_transducer.ipynb | 271 ---------- 44 files changed, 51 insertions(+), 7172 deletions(-) delete mode 100644 examples/at_array_as_sensor/README.md delete mode 100644 examples/at_array_as_sensor/at_array_as_sensor.ipynb delete mode 100644 examples/at_array_as_source/README.md delete mode 100644 examples/at_array_as_source/at_array_as_source.ipynb delete mode 100644 examples/at_circular_piston_3D/README.md delete mode 100644 examples/at_circular_piston_3D/at_circular_piston_3D.ipynb delete mode 100644 examples/at_circular_piston_AS/README.md delete mode 100644 examples/at_circular_piston_AS/at_circular_piston_AS.ipynb delete mode 100644 examples/at_focused_annular_array_3D/README.md delete mode 100644 examples/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb delete mode 100644 examples/at_focused_bowl_3D/README.md delete mode 100644 examples/at_focused_bowl_3D/at_focused_bowl_3D.ipynb delete mode 100644 examples/at_focused_bowl_AS/README.md delete mode 100644 examples/at_focused_bowl_AS/at_focused_bowl_AS.ipynb delete mode 100644 examples/at_linear_array_transducer/README.md delete mode 100644 examples/at_linear_array_transducer/at_linear_array_transducer.ipynb delete mode 100644 examples/checkpointing/README.md delete mode 100644 examples/ivp_photoacoustic_waveforms/README.md delete mode 100644 examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb delete mode 100644 examples/na_controlling_the_pml/README.md delete mode 100644 examples/na_controlling_the_pml/na_controlling_the_pml.ipynb delete mode 100644 examples/pr_2D_FFT_line_sensor/README.md delete mode 100644 examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb delete mode 100644 examples/pr_2D_TR_line_sensor/README.md delete mode 100644 examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb delete mode 100644 examples/pr_3D_FFT_planar_sensor/README.md delete mode 100644 examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb delete mode 100644 examples/pr_3D_TR_planar_sensor/README.md delete mode 100644 examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb delete mode 100644 examples/sd_directivity_modelling_2D/README.md delete mode 100644 examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb delete mode 100644 examples/sd_focussed_detector_2D/README.md delete mode 100644 examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb delete mode 100644 examples/sd_focussed_detector_3D/README.md delete mode 100644 examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb delete mode 100644 examples/us_beam_patterns/README.md delete mode 100644 examples/us_beam_patterns/us_beam_patterns.ipynb delete mode 100644 examples/us_bmode_linear_transducer/README.md delete mode 100644 examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb delete mode 100644 examples/us_bmode_phased_array/README.md delete mode 100644 examples/us_bmode_phased_array/us_bmode_phased_array.ipynb delete mode 100644 examples/us_defining_transducer/README.md delete mode 100644 examples/us_defining_transducer/us_defining_transducer.ipynb diff --git a/examples/README.md b/examples/README.md index de463edba..029ef232d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,57 +1,51 @@ -# Examples - -Many examples from k-wave-python are Python mirrors of the [large collection of great examples](http://www.k-wave.org/documentation/k-wave_examples.php) from the original k-wave project. -All examples are included as python files or notebooks in a subdirectory of the example directory. When running the examples in Google Colab, remember to select a GPU runtime for GPU simulations. -Every example has a short readme.md file which briefly describes the purpose of the example. - -## List of Examples - -- [Array as a sensor](at_array_as_sensor/) ([original example](http://www.k-wave.org/documentation/example_at_array_as_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_array_as_sensor/at_array_as_sensor.ipynb)) -- [Array as a source](at_array_as_source/) ([original example](http://www.k-wave.org/documentation/example_at_array_as_source.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_array_as_source/at_array_as_source.ipynb)) -- [Linear array transducer](at_linear_array_transducer/) - ([original example](http://www.k-wave.org/documentation/example_at_linear_array_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_linear_array_transducer/at_linear_array_transducer.ipynb)) -- [Photoacoustic Waveforms](ivp_photoacoustic_waveforms/) ([original example](http://www.k-wave.org/documentation/example_ivp_photoacoustic_waveforms.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb)) -- [Controlling the PML](na_controlling_the_pml/) - ([original example](http://www.k-wave.org/documentation/example_na_controlling_the_pml.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/na_controlling_the_pml/na_controlling_the_pml.ipynb)) -- [Defining An Ultrasound Transducer Example](us_defining_transducer) ([original example](http://www.k-wave.org/documentation/example_us_defining_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_defining_transducer/us_defining_transducer.ipynb)) -- [Simulating Ultrasound Beam Patterns](us_beam_patterns/) ([original example](http://www.k-wave.org/documentation/example_us_beam_patterns), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_beam_patterns/us_beam_patterns.ipynb)) -- [Linear transducer B-mode](us_bmode_linear_transducer/) ([original example](http://www.k-wave.org/documentation/example_us_bmode_linear_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb)) -- [Phased array B-mode](us_bmode_phased_array/) - ([original example](http://www.k-wave.org/documentation/example_us_bmode_phased_array.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_bmode_phased_array/us_bmode_phased_array.ipynb)) -- [Circular piston transducer](at_circular_piston_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading3), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_circular_piston_3D/at_circular_piston_3D.ipynb)) - -- [Circular piston transducer (axisymmetric)](at_circular_piston_AS/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading4), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_circular_piston_AS/at_circular_piston_AS.ipynb)) - -- [Focused bowl transducer](at_focused_bowl_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading5), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_focused_bowl_3D/at_focused_bowl_3D.ipynb)) - -- [Focused bowl transducer (axisymmetric)](at_focused_bowl_AS/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading6), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_focused_bowl_AS/at_focused_bowl_AS.ipynb)) - -- [Focused annular array](at_focused_annular_array_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading7), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb)) -- [2D FFT Reconstruction For A Line Sensor](pr_2D_FFT_line_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_2D_fft_line_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb)) -- [3D FFT Reconstruction For A Planar Sensor](pr_3D_FFT_planar_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_3D_fft_planar_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb)) -- [2D Time Reversal Reconstruction For A Line Sensor](pr_2D_TR_line_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_2D_tr_line_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb)) -- [3D Time Reversal Reconstruction For A Planar Sensor](pr_3D_TR_planar_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_3D_tr_planar_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb)) - - -- [Focussed Detector In 2D Example](sd_focussed_detector_2D/) ([original example](http://www.k-wave.org/documentation/example_sd_focussed_detector_2D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb)) - -- [Focussed Detector In 3D Example](sd_focussed_detector_3D/) ([original example](http://www.k-wave.org/documentation/example_sd_focussed_detector_3D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb)) - -- [Modelling Sensor Directivity In 2D Example](sd_directivity_modelling_2D/) ([original example](http://www.k-wave.org/documentation/example_sd_directivity_modelling_2D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb)) - -## Contributing new examples - -When adding a new example notebook, follow these steps: - -1. Search the [list of examples](https://docs.google.com/spreadsheets/d/1-x13iIez84AEyjjHMOe2GoC8FSyzUFHoy9R7VKttTlo/edit?usp=sharing) to find an open example. -1. Claim your example by opening an "Example" issue on GitHub. -1. Fork and clone the repository and create a branch for your new example. -1. Create an example sub-directory using the name from the hyperlink of the original k-wave example if it exists (e.g. for http://www.k-wave.org/documentation/example_ivp_loading_external_image.php name the directory "ivp_loading_external_image). -1. Add your example notebook to your example directory. -1. Create a Python script that mirrors your example notebook in the same directory. Using `nbconvert` is an easy way to convert to a Python script using ` jupyter nbconvert --to python --RegexRemovePreprocessor.patterns="^%" ` -1. Add a README.md file to your example directory briefly describing the concept or principle the example is meant to display and linking to the origonal k-wave example page if it exists. -1. Include a link in the `README.md` in the examples directory to a colab notebook for your example. -1. Add a your example to the list on this `README.md` and add a colab badge [using html](https://openincolab.com/) OR copy the pure markdown version above. -1. Open a pull request that [closes the open issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) from your forked example branch and name pull request "[Example] \". - -Thanks for contributing to k-wave-python! +# k-Wave Examples + +Each `.py` file is both a runnable script and an interactive notebook. + +## Running examples + +**As a script:** +```bash +python examples/ivp_1D_simulation.py +``` + +**Interactively (VS Code / JupyterLab / PyCharm):** +Open any `.py` file — the `# %%` markers define cells you can run one at a time. + +**As a Jupyter notebook:** +```bash +jupytext --to notebook examples/ivp_1D_simulation.py +jupyter notebook examples/ivp_1D_simulation.ipynb +``` + +## Example categories + +| Prefix | Topic | +|--------|-------| +| `ivp_` | Initial value problems (photoacoustics) | +| `pr_` | Photoacoustic reconstruction (FFT, time reversal) | +| `sd_` | Sensor directivity and focused detectors | +| `at_` | Acoustic transducers (pistons, bowls, arrays) | +| `us_` | Ultrasound imaging (B-mode, beam patterns) | +| `na_` | Numerical analysis (PML, performance) | + +## Writing a new example + +1. Create `examples/your_example/your_example.py` +2. Start with a markdown header and imports: + ```python + # %% [markdown] + # # Your Example Title + # One-line description of what this demonstrates. + + # %% + import numpy as np + from kwave.kgrid import kWaveGrid + from kwave.kmedium import kWaveMedium + from kwave.ksource import kSource + from kwave.ksensor import kSensor + from kwave.kspaceFirstOrder import kspaceFirstOrder + ``` +3. Add `# %%` before each logical section (grid, source, simulation, visualization) +4. Use `kspaceFirstOrder()` — not the legacy `kspaceFirstOrder2D/3D` +5. Notebooks (`.ipynb`) are auto-generated from `.py` files at release time — don't create them by hand diff --git a/examples/at_array_as_sensor/README.md b/examples/at_array_as_sensor/README.md deleted file mode 100644 index 928ec3daa..000000000 --- a/examples/at_array_as_sensor/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Defining A Sensor Using An Array Transducer Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_array_as_sensor/at_array_as_sensor.ipynb) - -This example provides a demonstration of using the kWaveArray class to define an array transducer with 20 arc-shaped elements which is then used as a receiver array. It builds on the Defining A Source Using An Array Transducer Example. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_at_array_as_sensor.php) \ No newline at end of file diff --git a/examples/at_array_as_sensor/at_array_as_sensor.ipynb b/examples/at_array_as_sensor/at_array_as_sensor.ipynb deleted file mode 100644 index 87a4f520f..000000000 --- a/examples/at_array_as_sensor/at_array_as_sensor.ipynb +++ /dev/null @@ -1,299 +0,0 @@ -{ - "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 as mpl\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.conversion import cart2grid\n", - "from kwave.utils.kwave_array import kWaveArray\n", - "from kwave.utils.mapgen import make_cart_circle, make_disc\n", - "from kwave.utils.signals import reorder_binary_sensor_data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# define arc properties\n", - "radius = 100e-3 # [m]\n", - "diameter = 8e-3 # [m]\n", - "ring_radius = 50e-3 # [m]\n", - "num_elements = 20\n", - "\n", - "# orient all elements towards the center of the grid\n", - "focus_pos = Vector([0, 0]) # [m]\n", - "\n", - "element_pos = make_cart_circle(ring_radius, num_elements, focus_pos)\n", - "\n", - "# create empty array\n", - "karray = kWaveArray()\n", - "\n", - "for idx in range(num_elements):\n", - " karray.add_arc_element(element_pos[:, idx], radius, diameter, focus_pos)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "# medium properties\n", - "medium = kWaveMedium(sound_speed=1500)\n", - "\n", - "# grid properties\n", - "N = Vector([256, 256])\n", - "d = Vector([0.5e-3, 0.5e-3])\n", - "kgrid = kWaveGrid(N, d)\n", - "# time array\n", - "kgrid.makeTime(medium.sound_speed)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "source = kSource()\n", - "x_offset = 20 # [pixels]\n", - "# make a small disc in the top left of the domain\n", - "source.p0 = make_disc(N, Vector([N.x / 4 + x_offset, N.y / 4]), 4)\n", - "source.p0[99:119, 59:199] = 1\n", - "logical_p0 = source.p0.astype(bool)\n", - "sensor = kSensor()\n", - "sensor.mask = element_pos" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "simulation_options = SimulationOptions(\n", - " save_to_disk=True,\n", - " data_cast='single',\n", - ")\n", - "\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True)\n", - "output = kspaceFirstOrder2D(kgrid, source, sensor, medium, simulation_options, execution_options)\n", - "\n", - "# Here we reorder the sensor data returned by k-Wave to match the order of the elements in the array\n", - "_, _, reorder_index = cart2grid(kgrid, element_pos)\n", - "sensor_data_point = reorder_binary_sensor_data(output['p'].T, reorder_index=reorder_index)\n", - "\n", - "# assign binary mask from karray to the source mask\n", - "sensor.mask = karray.get_array_binary_mask(kgrid)\n", - "\n", - "output = kspaceFirstOrder2D(kgrid, source, sensor, medium, simulation_options, execution_options)\n", - "sensor_data = output['p'].T\n", - "combined_sensor_data = karray.combine_sensor_data(kgrid, sensor_data)\n", - "\n", - "\n", - "# create pml mask (reuse default size of 20 grid points from simulation_options)\n", - "pml_size = simulation_options.pml_x_size # 20 [grid_points]\n", - "pml_mask = np.zeros((N.x, N.y), dtype=bool)\n", - "pml_mask[:pml_size, :] = 1\n", - "pml_mask[:, :pml_size] = 1\n", - "pml_mask[-pml_size:, :] = 1\n", - "pml_mask[:, -pml_size:] = 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Visualization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "# create pml mask (reuse default size of 20 grid points from simulation_options)\n", - "pml_size = simulation_options.pml_x_size # 20 [grid_points]\n", - "pml_mask = np.zeros((N.x, N.y), dtype=bool)\n", - "pml_mask[:pml_size, :] = 1\n", - "pml_mask[:, :pml_size] = 1\n", - "pml_mask[-pml_size:, :] = 1\n", - "pml_mask[:, -pml_size:] = 1\n", - "\n", - "# Plot source, sensor, and pml masks\n", - "\n", - "# Assign unique values to each mask\n", - "sensor_val = sensor.mask * 1\n", - "logical_p0_val = logical_p0 * 2\n", - "pml_mask_val = pml_mask * 3\n", - "\n", - "# Combine masks\n", - "combined_mask = sensor_val + logical_p0_val + pml_mask_val\n", - "combined_mask = np.flipud(combined_mask)\n", - "\n", - "# Define custom colormap\n", - "colors = [\n", - " (1, 1, 1), # White (Background)\n", - " (233/255, 131/255, 0/255), # Orange (Sensor)\n", - " (254/255, 221/255, 92/255), # Yellow (Sources)\n", - " (0.8, 0.8, 0.8), # Light Grey (PML Mask)\n", - "]\n", - "cmap = plt.matplotlib.colors.ListedColormap(colors)\n", - "\n", - "fig, ax = plt.subplots()\n", - "c = ax.pcolormesh(combined_mask, cmap=cmap, shading='auto')\n", - "for element in karray.elements:\n", - " ax.plot(*element.position // kgrid.spacing + kgrid.N // 2, 'kx') \n", - "plt.axis('image')\n", - "\n", - "# Define labels for the colorbar\n", - "labels = {\n", - " 0: 'None',\n", - " 1: 'Sensor',\n", - " 2: 'Initial pressure p0',\n", - " 3: 'PML Mask',\n", - "}\n", - "\n", - "bounds = np.linspace(0, len(labels), len(labels)+1)\n", - "norm = mpl.colors.BoundaryNorm(bounds, cmap.N)\n", - "\n", - "ax2 = fig.add_axes([0.95, 0.1, 0.03, 0.8])\n", - "cb = mpl.colorbar.ColorbarBase(ax2, cmap=cmap, norm=norm,\n", - " spacing='proportional', ticks=bounds, boundaries=bounds, format='%1i')\n", - "\n", - "# Update the title and label as before\n", - "ax.set_title('Simulation Layout')\n", - "ax2.set_ylabel('Simulation Components [-]', size=12)\n", - "\n", - "# Calculate the middle points for each segment of the colorbar\n", - "mid_points = [(bounds[i] + bounds[i+1])/2 for i in range(len(bounds)-1)]\n", - "\n", - "# Set the new tick positions and labels\n", - "ax2.set_yticks(mid_points)\n", - "ax2.set_yticklabels(list(labels.values()))\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, [ax1, ax2] = plt.subplots(ncols=1, nrows=2)\n", - "im1 = ax1.imshow(sensor_data_point, aspect=\"auto\", cmap=get_color_map(), interpolation=\"none\")\n", - "ax1.set_xlabel(r\"Time [$\\mu$s]\")\n", - "ax1.set_ylabel(\"Detector Number\")\n", - "ax1.set_title(\"Cartesian point detectors\")\n", - "fig.colorbar(im1, ax=ax1)\n", - "\n", - "im2 = ax2.imshow(combined_sensor_data, aspect=\"auto\", cmap=get_color_map(), interpolation=\"none\")\n", - "ax2.set_xlabel(r\"Time [$\\mu$s]\")\n", - "ax2.set_ylabel(\"Detector Number\")\n", - "ax2.set_title(\"Arc detectors\")\n", - "fig.colorbar(im2, ax=ax2)\n", - "\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig.subplots_adjust(hspace=0.5)\n", - "\n", - "# Plot a trace from the recorded sensor data\n", - "fig = plt.figure()\n", - "plt.plot(kgrid.t_array.squeeze() * 1e6, sensor_data_point[0, :], label='Cartesian point detectors')\n", - "plt.plot(kgrid.t_array.squeeze() * 1e6, combined_sensor_data[0, :], label='Arc detectors')\n", - "plt.xlabel(r'Time [$\\mu$s]')\n", - "plt.ylabel('Pressure [pa]')\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig.subplots_adjust(hspace=0.5)\n", - "\n", - "# Plot a trace from the recorded sensor data\n", - "fig = plt.figure()\n", - "plt.plot(kgrid.t_array.squeeze() * 1e6, combined_sensor_data[0, :] - sensor_data_point[0, :])\n", - "plt.xlabel(r'Time [$\\mu$s]')\n", - "plt.ylabel('Pressure [pa]')\n", - "plt.title('Difference between Cartesian and Arc detectors')\n", - "plt.legend()\n", - "plt.show()" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 0 -} diff --git a/examples/at_array_as_source/README.md b/examples/at_array_as_source/README.md deleted file mode 100644 index 2c75d5a00..000000000 --- a/examples/at_array_as_source/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Defining A Source Using An Array Transducer Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_array_as_source/at_array_as_source.ipynb) - -This example provides a demonstration of using the kWaveArray class to define an array transducer with three arc-shaped elements without staircasing errors. - -For a more detailed discussion of this example and the underlying techniques, see [E. S. Wise, B. T. Cox, J. Jaros, & B. E. Treeby (2019). Representing arbitrary acoustic source and sensor distributions in Fourier collocation methods. The Journal of the Acoustical Society of America, 146(1), 278-288.](https://pubs.aip.org/asa/jasa/article/146/1/278/993785/Representing-arbitrary-acoustic-source-and-sensor). - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_at_array_as_source.php). diff --git a/examples/at_array_as_source/at_array_as_source.ipynb b/examples/at_array_as_source/at_array_as_source.ipynb deleted file mode 100644 index ba7453d8f..000000000 --- a/examples/at_array_as_source/at_array_as_source.ipynb +++ /dev/null @@ -1,262 +0,0 @@ -{ - "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", - "from matplotlib import colors\n", - "from matplotlib.animation import FuncAnimation\n", - "\n", - "from kwave.data import Vector\n", - "from kwave.kgrid import kWaveGrid\n", - "from kwave.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder2D import kspace_first_order_2d_gpu\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.kwave_array import kWaveArray\n", - "from kwave.utils.signals import tone_burst\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "# Define kWaveArray" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create empty array\n", - "karray = kWaveArray()\n", - "\n", - "# define arc properties\n", - "radius = 50e-3 # [m]\n", - "diameter = 30e-3 # [m]\n", - "focus_pos = [-20e-3, 0] # [m]\n", - "\n", - "# add arc-shaped element\n", - "elem_pos = [10e-3, -40e-3] # [m]\n", - "karray.add_arc_element(elem_pos, radius, diameter, focus_pos)\n", - "\n", - "# add arc-shaped element\n", - "elem_pos = [20e-3, 0] # [m]\n", - "karray.add_arc_element(elem_pos, radius, diameter, focus_pos)\n", - "\n", - "# add arc-shaped element\n", - "elem_pos = [10e-3, 40e-3] # [m]\n", - "karray.add_arc_element(elem_pos, radius, diameter, focus_pos)\n", - "\n", - "# move the array down 10 mm, and rotate by 10 degrees (this moves all the\n", - "# elements together)\n", - "karray.set_array_position([10e-3, 0], 10)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Define grid properties" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "# grid properties\n", - "Nx = 256\n", - "dx = 0.5e-3\n", - "Ny = 256\n", - "dy = 0.5e-3\n", - "kgrid = kWaveGrid(Vector([Nx, Ny]), Vector([dx, dy]))\n", - "\n", - "# medium properties\n", - "medium = kWaveMedium(sound_speed=1500)\n", - "\n", - "# time array\n", - "kgrid.makeTime(medium.sound_speed)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# assign binary mask from karray to the source mask\n", - "source_p_mask = karray.get_array_binary_mask(kgrid)\n", - "\n", - "# set source signals, one for each physical array element\n", - "f1 = 100e3\n", - "f2 = 200e3\n", - "f3 = 500e3\n", - "sig1 = tone_burst(1 / kgrid.dt, f1, 3).squeeze()\n", - "sig2 = tone_burst(1 / kgrid.dt, f2, 5).squeeze()\n", - "sig3 = tone_burst(1 / kgrid.dt, f3, 5).squeeze()\n", - "\n", - "# combine source signals into one array\n", - "source_signal = np.zeros((3, max(len(sig1), len(sig2))))\n", - "source_signal[0, :len(sig1)] = sig1\n", - "source_signal[1, :len(sig2)] = sig2\n", - "source_signal[2, :len(sig3)] = sig3\n", - "\n", - "# get distributed source signals (this automatically returns a weighted\n", - "# source signal for each grid point that forms part of the source)\n", - "source_p = karray.get_distributed_source_signal(kgrid, source_signal)\n", - "\n", - "simulation_options = SimulationOptions(\n", - " save_to_disk=True,\n", - " data_cast='single',\n", - ")\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True)\n", - "# run k-Wave simulation (no sensor is used for this example)\n", - "# TODO: I would say proper behaviour would be to return the entire pressure field if sensor is None\n", - "sensor = kSensor()\n", - "sensor.mask = np.ones((Nx, Ny), dtype=bool)\n", - "\n", - "source = kSource()\n", - "source.p_mask = source_p_mask\n", - "source.p = source_p\n", - "\n", - "p = kspace_first_order_2d_gpu(kgrid, source, sensor, medium, simulation_options, execution_options)\n", - "\n", - "p_field = np.reshape(p['p'], (kgrid.Nt, Nx, Ny))\n", - "p_field = np.transpose(p_field, (0, 2, 1))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Visualization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from matplotlib import rc\n", - "\n", - "rc('animation', html='jshtml', embed_limit=10**3)\n", - "%matplotlib notebook\n", - "\n", - "# Normalize frames based on the maximum value over all frames\n", - "max_value = np.max(p_field)\n", - "normalized_frames = p_field / max_value\n", - "\n", - "cmap = get_color_map()\n", - "\n", - "# Create a figure and axis\n", - "fig, ax = plt.subplots()\n", - "\n", - "# Create an empty image with the first normalized frame\n", - "image = ax.imshow(normalized_frames[0], cmap=cmap, norm=colors.Normalize(vmin=0, vmax=1))\n", - "\n", - "# Function to update the image for each frame\n", - "def update(frame):\n", - " image.set_data(normalized_frames[frame])\n", - " ax.set_title(f'Frame {frame + 1}/{kgrid.Nt}')\n", - " return [image]\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create the animation\n", - "ani = FuncAnimation(fig, update, frames=kgrid.Nt, interval=5, blit=False, repeat=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ani # This takes time" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create pml mask (default size in 2D is 20 grid points)\n", - "%matplotlib inline\n", - "pml_size = 20\n", - "pml_mask = np.zeros((Nx, Ny), dtype=bool)\n", - "pml_mask[:pml_size, :] = 1\n", - "pml_mask[:, :pml_size] = 1\n", - "pml_mask[-pml_size:, :] = 1\n", - "pml_mask[:, -pml_size:] = 1\n", - "\n", - "# plot source and pml masks\n", - "plt.figure()\n", - "plt.imshow(np.logical_not(np.squeeze(source.p_mask | pml_mask)), aspect='auto', cmap='gray')\n", - "plt.xlabel('x-position [m]')\n", - "plt.ylabel('y-position [m]')\n", - "plt.title('Source and PML Masks')" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 0 -} diff --git a/examples/at_circular_piston_3D/README.md b/examples/at_circular_piston_3D/README.md deleted file mode 100644 index 613eed287..000000000 --- a/examples/at_circular_piston_3D/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Modelling A Circular Plane Piston Transducer Example - -This example models a circular plan piston transducer in 3D. The _on-axis_ pressure is compared with an analytical solution for a piston in an infinite baffle (Eq 5-7.3 in Allan Pierce's book on Acoustics). - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading3) diff --git a/examples/at_circular_piston_3D/at_circular_piston_3D.ipynb b/examples/at_circular_piston_3D/at_circular_piston_3D.ipynb deleted file mode 100644 index 3fa1287c1..000000000 --- a/examples/at_circular_piston_3D/at_circular_piston_3D.ipynb +++ /dev/null @@ -1,455 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "5b6f8e7a-5c7d-4340-8ed4-a04099307131", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install git+https://github.com/waltsims/k-wave-python" - ] - }, - { - "cell_type": "markdown", - "id": "db1bda7e-a820-4eb4-abf9-96ab041675af", - "metadata": {}, - "source": [ - "## Modelling A Circular Piston\n", - "\n", - "This example models a circular piston transducer in 3D. The on-axis pressure is compared with the analytical expression from Pierce's _\"Acoustics: An Introduction to its Physical Principles and Applications\"_\n", - "\n", - "First, define the settings, import the libraries and functions needed" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32e28e46-df00-4a08-996f-5683470c0e2b", - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "from copy import deepcopy\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.filters import extract_amp_phase\n", - "from kwave.utils.kwave_array import kWaveArray\n", - "from kwave.utils.math import round_even\n", - "from kwave.utils.signals import create_cw_signals" - ] - }, - { - "cell_type": "markdown", - "id": "a28dbad5-6ba4-42ef-9e55-715d2383df68", - "metadata": {}, - "source": [ - "The parameters of the system are defined below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "67150133-3e02-4fd9-a36d-27a9550fe84f", - "metadata": {}, - "outputs": [], - "source": [ - "# medium parameters\n", - "c0: float = 1500.0 # sound speed [m/s]\n", - "rho0: float = 1000.0 # density [kg/m^3]\n", - "\n", - "# source parameters\n", - "source_f0 = 1.0e6 # source frequency [Hz]\n", - "source_diameter = 10e-3 # bowl aperture diameter [m]\n", - "source_amp = np.array([1.0e6]) # source pressure [Pa]\n", - "source_phase = np.array([0.0]) # source phase [radians]\n", - "\n", - "# grid parameters\n", - "axial_size: float = 32.0e-3 # total grid size in the axial dimension [m]\n", - "lateral_size: float = 23.0e-3 # total grid size in the lateral dimension [m]\n", - "\n", - "# computational parameters\n", - "ppw: int = 3 # number of points per wavelength\n", - "t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state)\n", - "record_periods: int = 1 # number of periods to record\n", - "cfl: float = 0.5 # CFL number\n", - "bli_tolerance: float = 0.03 # tolerance for truncation of the off-grid source points\n", - "upsampling_rate: int = 10 # density of integration points relative to grid\n", - "verbose_level: int = 0 # verbosity of k-wave executable" - ] - }, - { - "cell_type": "markdown", - "id": "26e9fc37-096f-4e2d-af2b-32de0e085cd6", - "metadata": {}, - "source": [ - "## Grid\n", - "\n", - "Construct the grid via the `kgrid` class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dae95498-e846-4cb1-87ac-dcc684e952bc", - "metadata": {}, - "outputs": [], - "source": [ - "# calculate the grid spacing based on the PPW and F0\n", - "dx: float = c0 / (ppw * source_f0) # [m]\n", - "\n", - "# compute the size of the grid\n", - "Nx: int = round_even(axial_size / dx)\n", - "Ny: int = round_even(lateral_size / dx)\n", - "Nz: int = Ny\n", - "\n", - "grid_size_points = Vector([Nx, Ny, Nz])\n", - "grid_spacing_meters = Vector([dx, dx, dx])\n", - "\n", - "# create the k-space grid\n", - "kgrid = kWaveGrid(grid_size_points, grid_spacing_meters)\n", - "\n", - "# compute points per period\n", - "ppp: int = round(ppw / cfl)\n", - "\n", - "# compute corresponding time spacing\n", - "dt: float = 1.0 / (ppp * source_f0)\n", - "\n", - "# create the time array using an integer number of points per period\n", - "Nt: int = int(np.round(t_end / dt))\n", - "kgrid.setTime(Nt, dt)\n", - "\n", - "# calculate the actual CFL and PPW\n", - "print('points-per-period: ' + str(c0 / (dx * source_f0)) + ' and CFL number: ' + str(c0 * dt / dx))" - ] - }, - { - "cell_type": "markdown", - "id": "b39b121c-cc4d-45ee-9d0a-3cee00c690f5", - "metadata": {}, - "source": [ - "## Source\n", - "\n", - "Define the source, using the `kWaveArray` class and the `add_bowl_element` method along with a continuous wave signal." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d8474039-e3b7-4f09-b76d-6bfaf536bc21", - "metadata": {}, - "outputs": [], - "source": [ - "source = kSource()\n", - "\n", - "# create time varying source\n", - "source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_amp, source_phase)\n", - "\n", - "# create empty kWaveArray\n", - "karray = kWaveArray(bli_tolerance=bli_tolerance,\n", - " upsampling_rate=upsampling_rate,\n", - " single_precision=True)\n", - "\n", - "# add disc shaped element at one end of the grid\n", - "karray.add_disc_element([kgrid.x_vec[0].item(), 0.0, 0.0], source_diameter, [0.0, 0.0, 0.0])\n", - "\n", - "# assign binary mask\n", - "source.p_mask = karray.get_array_binary_mask(kgrid)\n", - "\n", - "# assign source signals\n", - "source.p = karray.get_distributed_source_signal(kgrid, source_sig)" - ] - }, - { - "cell_type": "markdown", - "id": "d9e68ba8-f59f-441d-a86f-bd649571a752", - "metadata": {}, - "source": [ - "## Medium\n", - "\n", - "The medium is water. Neither nonlinearity nor attenuation are considered." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f402c35a-43db-49cc-8947-afd1842f5c63", - "metadata": {}, - "outputs": [], - "source": [ - "# assign medium properties\n", - "medium = kWaveMedium(sound_speed=c0, density=rho0)" - ] - }, - { - "cell_type": "markdown", - "id": "acb68ad1-1f98-4dd5-b000-301386aab42e", - "metadata": {}, - "source": [ - "## Sensor\n", - "\n", - "The sensor class defines what acoustic information is recorded.\n", - "\n", - "A mask records all data except for at the location of the source." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "882f547c-9b94-4da5-8157-3157b983bdf3", - "metadata": {}, - "outputs": [], - "source": [ - "sensor = kSensor()\n", - "\n", - "# set sensor mask to record central plane, not including the source point\n", - "sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool)\n", - "sensor.mask[1:, :, Nz // 2] = True\n", - "\n", - "# record the pressure\n", - "sensor.record = ['p']\n", - "\n", - "# record only the final few periods when the field is in steady state\n", - "sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1" - ] - }, - { - "cell_type": "markdown", - "id": "7df50ac4-3e8e-4cdf-a275-90b54c7e5bea", - "metadata": {}, - "source": [ - "## Simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "66826f6c-b520-463b-a51f-aa4c15ed72a5", - "metadata": {}, - "outputs": [], - "source": [ - "simulation_options = SimulationOptions(pml_auto=True,\n", - " data_recast=True,\n", - " save_to_disk=True,\n", - " save_to_disk_exit=False,\n", - " pml_inside=False)\n", - "\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True,\n", - " delete_data=False,\n", - " verbose_level=0)\n", - "\n", - "sensor_data = kspaceFirstOrder3D(medium=deepcopy(medium),\n", - " kgrid=deepcopy(kgrid),\n", - " source=deepcopy(source),\n", - " sensor=deepcopy(sensor),\n", - " simulation_options=simulation_options,\n", - " execution_options=execution_options)" - ] - }, - { - "cell_type": "markdown", - "id": "77173d6d-bacc-46ee-be2b-e085d4985401", - "metadata": {}, - "source": [ - "## Post-processing\n", - "\n", - "Extract amplitude from the sensor data, using the Fourier transform. The data can be reshaped to match the spatial extents of the domain. The on-axis pressure amplitudes found and axes for plotting defined." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fbd76b31-58b4-4310-bdc6-0b7c6e7818e9", - "metadata": {}, - "outputs": [], - "source": [ - "# extract amplitude from the sensor data\n", - "amp, _, _ = extract_amp_phase(sensor_data['p'].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window='Rectangular')\n", - "\n", - "# reshape data\n", - "amp = np.reshape(amp, (Nx-1, Ny), order='F')\n", - "\n", - "# extract pressure on axis\n", - "amp_on_axis = amp[:, Ny // 2]\n", - "\n", - "# define axis vectors for plotting\n", - "x_vec = kgrid.x_vec[1:, :] - kgrid.x_vec[0]\n", - "y_vec = kgrid.y_vec" - ] - }, - { - "cell_type": "markdown", - "id": "11ec4eed-1349-4ea3-a2e9-61ede65f3702", - "metadata": {}, - "source": [ - "## Analytical Solution\n", - "\n", - "An analytical expression cam be found in Pierce[[1]](#cite_note-1). Given a transdcuer of radius $a$, wavenumber $k= 2 \\pi f / c$, where $f$ is the frequency, speed of sound $c$, and a unit normal vector to transducer surface, $\\hat{v}_n$, the on-axis pressure is given by\n", - "\n", - "$$\n", - "p_{\\mathrm{ref}}(z) = −2 \\, i \\, \\rho \\, c \\, \\hat{v}_n \\, e^{i k \\left( z + \\sqrt{z^2 + a^2} \\right) \\big/ 2} \\sin \\left( \\dfrac{k}{2} \\left( \\sqrt{z^2 + a^2} − z \\right) \\right).\n", - "$$\n", - "\n", - "[[1]](#cite_ref-1) A. D. Pierce, _\"Acoustics: An Introduction to its Physical Principles and Applications\"_ Springer (2019)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d5691bd3-295d-49c1-b815-13f33bc9b952", - "metadata": {}, - "outputs": [], - "source": [ - "# calculate the wavenumber\n", - "k: float = 2.0 * np.pi * source_f0 / c0\n", - "\n", - "# define radius and axis\n", - "a: float = source_diameter / 2.0\n", - "x_max: float = Nx * dx\n", - "delta_x: float = x_max / 10000.0\n", - "x_ref: float = np.arange(0.0, x_max + delta_x, delta_x, dtype=float)\n", - "\n", - "# calculate the analytical solution for a piston in an infinite baffle\n", - "# for comparison (Eq 5-7.3 in Pierce)\n", - "r_ref = np.sqrt(x_ref**2 + a**2)\n", - "p_ref = source_amp * np.abs(2.0 * np.sin((k * r_ref - k * x_ref) / 2.0))\n", - "\n", - "# get analytical solution at exactly the same points as k-Wave\n", - "r_vec = np.sqrt(x_vec**2 + a**2)\n", - "p_ref_kw = source_amp * np.abs(2.0 * np.sin((k * r_vec - k * x_vec) / 2.0))\n", - "\n", - "# calculate error\n", - "L2_error: float = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=2)\n", - "Linf_error: float = 100 * np.linalg.norm(p_ref_kw - amp_on_axis, ord=np.inf)" - ] - }, - { - "cell_type": "markdown", - "id": "373af282-a91a-42c2-82dd-7678de72b1fd", - "metadata": {}, - "source": [ - "## Visualisation\n", - "\n", - "First plot the pressure along the focal axis of the piston" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e38cbfea-115d-4196-b3cc-2585a56e861f", - "metadata": {}, - "outputs": [], - "source": [ - "fig1, ax1 = plt.subplots(1, 1)\n", - "ax1.plot(1e3 * x_ref, 1e-6 * p_ref, 'k-', label='Exact')\n", - "ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, 'b.', label='k-Wave')\n", - "ax1.legend()\n", - "ax1.set(xlabel='Axial Position [mm]',\n", - " ylabel='Pressure [MPa]',\n", - " title='Axial Pressure')\n", - "ax1.set_xlim(0.0, 1e3 * axial_size)\n", - "ax1.set_ylim(0.0, 1.1 * source_amp * 2e-6)\n", - "ax1.grid()" - ] - }, - { - "cell_type": "markdown", - "id": "03150718-35bc-4796-990c-7469b6177159", - "metadata": {}, - "source": [ - "Next plot the source mask (pml is outside the grid in this example). This means getting the grid weights first" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f04f9d92-da57-4627-969d-3c8d96f4c396", - "metadata": {}, - "outputs": [], - "source": [ - "# get grid weights\n", - "grid_weights = karray.get_array_grid_weights(kgrid)\n", - "\n", - "fig2, (ax2a, ax2b) = plt.subplots(1, 2)\n", - "ax2a.pcolormesh(1e3 * np.squeeze(kgrid.y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec),\n", - " np.flip(source.p_mask[:, :, Nz // 2], axis=0),\n", - " shading='nearest')\n", - "ax2a.set(xlabel='y [mm]',\n", - " ylabel='x [mm]',\n", - " title='Source Mask')\n", - "ax2b.pcolormesh(1e3 * np.squeeze(kgrid.y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec),\n", - " np.flip(grid_weights[:, :, Nz // 2], axis=0),\n", - " shading='nearest')\n", - "ax2b.set_xlabel('y [mm]')\n", - "ax2b.set_ylabel('x [mm]')\n", - "_ = ax2b.set_title('Off-Grid Source Weights')\n", - "plt.tight_layout(pad=1.2)" - ] - }, - { - "cell_type": "markdown", - "id": "d51ef784-06c3-4c5f-a478-e6864aa12041", - "metadata": {}, - "source": [ - "Finally, plot the pressure field" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "109c084a-29fb-4180-88e7-e37ecf3b3927", - "metadata": {}, - "outputs": [], - "source": [ - "fig3, ax3 = plt.subplots(1, 1)\n", - "p3 = ax3.pcolormesh(1.0e3 * np.squeeze(y_vec),\n", - " 1.0e3 * np.squeeze(x_vec),\n", - " np.flip(amp, axis=1) / 1.0e6,\n", - " shading='gouraud')\n", - "ax3.set(xlabel='Lateral Position [mm]',\n", - " ylabel='Axial Position [mm]',\n", - " title='Pressure Field')\n", - "ax3.set_ylim(1.0e3 * x_vec[-1], 1.0e3 * x_vec[0])\n", - "cbar3 = fig3.colorbar(p3, ax=ax3)\n", - "_ = cbar3.ax.set_title('[MPa]', fontsize='small')" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 5 -} diff --git a/examples/at_circular_piston_AS/README.md b/examples/at_circular_piston_AS/README.md deleted file mode 100644 index c56788f83..000000000 --- a/examples/at_circular_piston_AS/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Modelling An Axisymmetric Circular Plane Piston Transducer Example - -This example models an axisymmetric circular plan piston transducer. The _on-axis_ pressure is compared with an analytical solution for a piston in an infinite baffle (Eq 5-7.3 in Allan Pierce's book on Acoustics). - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading4) \ No newline at end of file diff --git a/examples/at_circular_piston_AS/at_circular_piston_AS.ipynb b/examples/at_circular_piston_AS/at_circular_piston_AS.ipynb deleted file mode 100644 index 6247925fe..000000000 --- a/examples/at_circular_piston_AS/at_circular_piston_AS.ipynb +++ /dev/null @@ -1,383 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install git+https://github.com/waltsims/k-wave-python" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from copy import deepcopy\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrderAS import kspaceFirstOrderASC\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions, SimulationType\n", - "from kwave.utils.filters import extract_amp_phase\n", - "from kwave.utils.kwave_array import kWaveArray\n", - "from kwave.utils.mapgen import focused_bowl_oneil\n", - "from kwave.utils.math import round_even\n", - "from kwave.utils.signals import create_cw_signals" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Set parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# medium parameters\n", - "c0: float = 1500.0 # sound speed [m/s]\n", - "rho0: float = 1000.0 # density [kg/m^3]\n", - "\n", - "# source parameters\n", - "source_f0 = 1.0e6 # source frequency [Hz]\n", - "source_roc = 30e-3 # bowl radius of curvature [m]\n", - "source_diameter = 30e-3 # bowl aperture diameter [m]\n", - "source_amp = np.array([1.0e6]) # source pressure [Pa]\n", - "source_phase = np.array([0.0]) # source phase [radians]\n", - "\n", - "# grid parameters\n", - "axial_size: float = 50.0e-3 # total grid size in the axial dimension [m]\n", - "lateral_size: float = 45.0e-3 # total grid size in the lateral dimension [m]\n", - "\n", - "# computational parameters\n", - "ppw: int = 3 # number of points per wavelength\n", - "t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state)\n", - "record_periods: int = 1 # number of periods to record\n", - "cfl: float = 0.05 # CFL number\n", - "source_x_offset: int = 20 # grid points to offset the source\n", - "bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points\n", - "upsampling_rate: int = 10 # density of integration points relative to grid" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Set grid" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# calculate the grid spacing based on the PPW and F0\n", - "dx: float = c0 / (ppw * source_f0) # [m]\n", - "\n", - "# compute the size of the grid\n", - "Nx: int= round_even(axial_size / dx) + source_x_offset\n", - "Ny: int = round_even(lateral_size / dx)\n", - "\n", - "grid_size_points = Vector([Nx, Ny])\n", - "grid_spacing_meters = Vector([dx, dx])\n", - "\n", - "# create the k-space grid\n", - "kgrid = kWaveGrid(grid_size_points, grid_spacing_meters)\n", - "\n", - "# compute points per temporal period\n", - "ppp: int = round(ppw / cfl)\n", - "\n", - "# compute corresponding time spacing\n", - "dt: float = 1.0 / (ppp * source_f0)\n", - "\n", - "# create the time array using an integer number of points per period\n", - "Nt: int = round(t_end / dt)\n", - "kgrid.setTime(Nt, dt)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define source" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "source = kSource()\n", - "\n", - "# create time varying source\n", - "source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_amp, source_phase)\n", - "\n", - "# set arc position and orientation\n", - "arc_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0]\n", - "focus_pos = [kgrid.x_vec[-1].item(), 0]\n", - "\n", - "# create empty kWaveArray\n", - "karray = kWaveArray(axisymmetric=True,\n", - " bli_tolerance=bli_tolerance,\n", - " upsampling_rate=upsampling_rate,\n", - " single_precision=True)\n", - "\n", - "# add bowl shaped element\n", - "karray.add_arc_element(arc_pos, source_roc, source_diameter, focus_pos)\n", - "\n", - "# assign binary mask\n", - "source.p_mask = karray.get_array_binary_mask(kgrid)\n", - "\n", - "# assign source signals\n", - "source.p = karray.get_distributed_source_signal(kgrid, source_sig)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Set up medium" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "medium = kWaveMedium(sound_speed=c0, density=rho0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Set up sensor" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sensor = kSensor()\n", - "\n", - "# set sensor mask to record central plane, not including the source point\n", - "sensor.mask = np.zeros((Nx, Ny), dtype=bool)\n", - "sensor.mask[(source_x_offset + 1):, :] = True\n", - "\n", - "# record the pressure\n", - "sensor.record = ['p']\n", - "\n", - "# record only the final few periods when the field is in steady state\n", - "sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define simulation options" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "simulation_options = SimulationOptions(\n", - " simulation_type=SimulationType.AXISYMMETRIC,\n", - " data_cast='single',\n", - " data_recast=False,\n", - " save_to_disk=True,\n", - " save_to_disk_exit=False,\n", - " pml_inside=False)\n", - "\n", - "execution_options = SimulationExecutionOptions(\n", - " is_gpu_simulation=False,\n", - " delete_data=False,\n", - " verbose_level=2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sensor_data = kspaceFirstOrderASC(medium=deepcopy(medium),\n", - " kgrid=deepcopy(kgrid),\n", - " source=deepcopy(source),\n", - " sensor=deepcopy(sensor),\n", - " simulation_options=simulation_options,\n", - " execution_options=execution_options)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Post-processing" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# extract amplitude from the sensor data\n", - "amp, _, _ = extract_amp_phase(sensor_data['p'].T, 1.0 / kgrid.dt, source_f0,\n", - " dim=1, fft_padding=1, window='Rectangular')\n", - "\n", - "# reshape data\n", - "amp = np.reshape(amp, (Nx - (source_x_offset+1), Ny), order='F')\n", - "\n", - "# extract pressure on axis\n", - "amp_on_axis = amp[:, 0]\n", - "\n", - "# define axis vectors for plotting\n", - "x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1):, :] - kgrid.x_vec[source_x_offset])\n", - "y_vec = kgrid.y_vec\n", - "\n", - "# =========================================================================\n", - "# ANALYTICAL SOLUTION\n", - "# =========================================================================\n", - "\n", - "# calculate the wavenumber\n", - "knumber = 2.0 * np.pi * source_f0 / c0\n", - "\n", - "# define radius and axis\n", - "x_max = (Nx-1) * dx\n", - "delta_x = x_max / 10000.0\n", - "x_ref = np.arange(0.0, x_max + delta_x, delta_x)\n", - "\n", - "# calculate analytical solution\n", - "p_ref_axial, _, _ = focused_bowl_oneil(source_roc,\n", - " source_diameter,\n", - " source_amp[0] / (c0 * rho0),\n", - " source_f0,\n", - " c0,\n", - " rho0,\n", - " axial_positions=x_ref)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# plot the pressure along the focal axis of the piston\n", - "fig1, ax1 = plt.subplots(1, 1)\n", - "ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, 'k-', label='Exact')\n", - "ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, 'b.', label='k-Wave')\n", - "ax1.legend()\n", - "ax1.set(xlabel='Axial Position [mm]',\n", - " ylabel='Pressure [MPa]',\n", - " title='Axial Pressure')\n", - "ax1.set_xlim(0.0, 1e3 * axial_size)\n", - "ax1.set_ylim(0.0, 20)\n", - "ax1.grid()\n", - "\n", - "# get grid weights\n", - "grid_weights = karray.get_array_grid_weights(kgrid)\n", - "\n", - "# define axis vectors for plotting\n", - "yvec = np.squeeze(kgrid.y_vec) - kgrid.y_vec[0].item()\n", - "y_vec = np.hstack((-np.flip(yvec)[:-1], yvec))\n", - "x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1):, :] - kgrid.x_vec[source_x_offset])\n", - "\n", - "data = np.hstack((np.fliplr(amp[:, :-1]), amp)) / 1e6\n", - "sp = np.hstack((np.fliplr(source.p_mask[:, :-1]), source.p_mask))\n", - "gw = np.hstack((np.fliplr(grid_weights[:, :-1]), grid_weights))\n", - "\n", - "fig3, (ax3a, ax3b) = plt.subplots(1, 2)\n", - "ax3a.pcolormesh(1e3 * np.squeeze(y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec[:, :] - kgrid.x_vec[source_x_offset]),\n", - " sp,\n", - " shading='gouraud')\n", - "ax3a.set(xlabel='y [mm]',\n", - " ylabel='x [mm]',\n", - " title='Source Mask')\n", - "ax3b.pcolormesh(1e3 * np.squeeze(y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec[:, :] - kgrid.x_vec[source_x_offset]),\n", - " gw,\n", - " shading='gouraud')\n", - "ax3b.set(xlabel='y [mm]',\n", - " ylabel='x [mm]',\n", - " title='Off-Grid Source Weights')\n", - "fig3.tight_layout(pad=1.2)\n", - "\n", - "# plot the pressure field\n", - "fig4, ax4 = plt.subplots(1, 1)\n", - "ax4.pcolormesh(1e3 * np.squeeze(y_vec),\n", - " 1e3 * np.squeeze(x_vec),\n", - " data,\n", - " shading='gouraud')\n", - "ax4.set(xlabel='Lateral Position [mm]',\n", - " ylabel='Axial Position [mm]',\n", - " title='Pressure Field')\n", - "ax4.invert_yaxis()\n", - "\n", - "# show figures\n", - "plt.show()" - ] - } - ], - "metadata": { - "colab": { - "authorship_tag": "ABX9TyOTl1BLKuzg6aBmvj8vKKSi", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 0 -} diff --git a/examples/at_focused_annular_array_3D/README.md b/examples/at_focused_annular_array_3D/README.md deleted file mode 100644 index e89c6d247..000000000 --- a/examples/at_focused_annular_array_3D/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Modelling A Focused Annular Transducer Example - -This example models a focused annular transducer in 3D. The _on-axis_ pressure is compared with an analytical solution calculated using `focused_annulus_oneil`. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading7) \ No newline at end of file diff --git a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb b/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb deleted file mode 100644 index 7a5baff02..000000000 --- a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb +++ /dev/null @@ -1,452 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "8b20eee6", - "metadata": {}, - "source": [ - "\"Open" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b6f8e7a-5c7d-4340-8ed4-a04099307131", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install git+https://github.com/waltsims/k-wave-python" - ] - }, - { - "cell_type": "markdown", - "id": "db1bda7e-a820-4eb4-abf9-96ab041675af", - "metadata": {}, - "source": [ - "## Modelling A Focused Annular Array Transducer In 3D Example\n", - "\n", - "This example models a focused annular array transducer in 3D. The on-axis pressure is compared with the exact solution calculated using `focused_annulus_oneil`.\n", - "\n", - "First, define the settings, import the libraries and functions needed" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32e28e46-df00-4a08-996f-5683470c0e2b", - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "from copy import deepcopy\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.filters import extract_amp_phase\n", - "from kwave.utils.kwave_array import kWaveArray\n", - "from kwave.utils.mapgen import focused_annulus_oneil\n", - "from kwave.utils.math import round_even\n", - "from kwave.utils.signals import create_cw_signals" - ] - }, - { - "cell_type": "markdown", - "id": "a28dbad5-6ba4-42ef-9e55-715d2383df68", - "metadata": {}, - "source": [ - "The parameters of the system are defined below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "67150133-3e02-4fd9-a36d-27a9550fe84f", - "metadata": {}, - "outputs": [], - "source": [ - "# medium parameters\n", - "c0: float = 1500.0 # sound speed [m/s]\n", - "rho0: float = 1000.0 # density [kg/m^3]\n", - "\n", - "# source parameters\n", - "source_f0 = 1.0e6 # source frequency [Hz]\n", - "source_roc = 30e-3 # bowl radius of curvature [m]\n", - "source_amp = np.array([0.5e6, 1e6, 0.75e6]) # source pressure [Pa]\n", - "source_phase = np.deg2rad(np.array([0.0, 10.0, 20.0])) # source phase [radians]\n", - "\n", - "# aperture diameters of the elements given an inner, outer pairs [m]\n", - "diameters = np.array([[0.0, 5.0], [10.0, 15.0], [20.0, 25.0]]) * 1e-3\n", - "diameters = diameters.tolist()\n", - "\n", - "# grid parameters\n", - "axial_size: float = 40.0e-3 # total grid size in the axial dimension [m]\n", - "lateral_size: float = 45.0e-3 # total grid size in the lateral dimension [m]\n", - "\n", - "# computational parameters\n", - "ppw: int = 3 # number of points per wavelength\n", - "t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state)\n", - "record_periods: int = 1 # number of periods to record\n", - "cfl: float = 0.5 # CFL number\n", - "source_x_offset: int = 20 # grid points to offset the source\n", - "bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points\n", - "upsampling_rate: int = 10 # density of integration points relative to grid\n", - "verbose_level: int = 0 # verbosity of k-wave executable" - ] - }, - { - "cell_type": "markdown", - "id": "26e9fc37-096f-4e2d-af2b-32de0e085cd6", - "metadata": {}, - "source": [ - "## Grid\n", - "\n", - "Construct the grid via the `kgrid` class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dae95498-e846-4cb1-87ac-dcc684e952bc", - "metadata": {}, - "outputs": [], - "source": [ - "# calculate the grid spacing based on the PPW and F0\n", - "dx: float = c0 / (ppw * source_f0) # [m]\n", - "\n", - "# compute the size of the grid\n", - "Nx: int = round_even(axial_size / dx) + source_x_offset\n", - "Ny: int = round_even(lateral_size / dx)\n", - "Nz: int = Ny\n", - "\n", - "grid_size_points = Vector([Nx, Ny, Nz])\n", - "grid_spacing_meters = Vector([dx, dx, dx])\n", - "\n", - "# create the k-space grid\n", - "kgrid = kWaveGrid(grid_size_points, grid_spacing_meters)\n", - "\n", - "# compute points per period\n", - "ppp: int = round(ppw / cfl)\n", - "\n", - "# compute corresponding time spacing\n", - "dt: float = 1.0 / (ppp * source_f0)\n", - "\n", - "# create the time array using an integer number of points per period\n", - "Nt: int = int(np.round(t_end / dt))\n", - "kgrid.setTime(Nt, dt)\n", - "\n", - "# calculate the actual CFL and PPW\n", - "print('points-per-period: ' + str(c0 / (dx * source_f0)) + ' and CFL number : ' + str(c0 * dt / dx))" - ] - }, - { - "cell_type": "markdown", - "id": "b39b121c-cc4d-45ee-9d0a-3cee00c690f5", - "metadata": {}, - "source": [ - "## Source\n", - "\n", - "Define the source, using the `kWaveArray` class and the `add_bowl_element` method along with a continuous wave signal." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d8474039-e3b7-4f09-b76d-6bfaf536bc21", - "metadata": {}, - "outputs": [], - "source": [ - "source = kSource()\n", - "\n", - "# create time varying source\n", - "source_signal = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_amp, source_phase)\n", - "\n", - "# create empty kWaveArray\n", - "karray = kWaveArray(bli_tolerance=bli_tolerance,\n", - " upsampling_rate=upsampling_rate,\n", - " single_precision=True)\n", - "\n", - "# set bowl position and orientation\n", - "bowl_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0, 0]\n", - "focus_pos = [kgrid.x_vec[-1].item(), 0, 0]\n", - "\n", - "# add bowl shaped element to array\n", - "karray.add_annular_array(bowl_pos, source_roc, diameters, focus_pos)\n", - "\n", - "# assign binary mask\n", - "source.p_mask = karray.get_array_binary_mask(kgrid)\n", - "\n", - "# assign source signals\n", - "source.p = karray.get_distributed_source_signal(kgrid, source_signal)" - ] - }, - { - "cell_type": "markdown", - "id": "d9e68ba8-f59f-441d-a86f-bd649571a752", - "metadata": {}, - "source": [ - "## Medium\n", - "\n", - "The medium is water. Neither nonlinearity nor attenuation are considered." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f402c35a-43db-49cc-8947-afd1842f5c63", - "metadata": {}, - "outputs": [], - "source": [ - "# assign medium properties\n", - "medium = kWaveMedium(sound_speed=c0, density=rho0)" - ] - }, - { - "cell_type": "markdown", - "id": "acb68ad1-1f98-4dd5-b000-301386aab42e", - "metadata": {}, - "source": [ - "## Sensor\n", - "\n", - "The sensor class defines what acoustic information is recorded." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "882f547c-9b94-4da5-8157-3157b983bdf3", - "metadata": {}, - "outputs": [], - "source": [ - "sensor = kSensor()\n", - "\n", - "# set sensor mask to record central plane, not including the source point\n", - "sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool)\n", - "sensor.mask[(source_x_offset + 1):, :, Nz // 2] = True\n", - "\n", - "# record the pressure\n", - "sensor.record = ['p']\n", - "\n", - "# record only the final few periods when the field is in steady state\n", - "sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1" - ] - }, - { - "cell_type": "markdown", - "id": "7df50ac4-3e8e-4cdf-a275-90b54c7e5bea", - "metadata": {}, - "source": [ - "## Simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "66826f6c-b520-463b-a51f-aa4c15ed72a5", - "metadata": {}, - "outputs": [], - "source": [ - "simulation_options = SimulationOptions(pml_auto=True,\n", - " data_recast=True,\n", - " save_to_disk=True,\n", - " save_to_disk_exit=False,\n", - " pml_inside=False)\n", - "\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True,\n", - " delete_data=False,\n", - " verbose_level=0)\n", - "\n", - "sensor_data = kspaceFirstOrder3D(medium=deepcopy(medium),\n", - " kgrid=deepcopy(kgrid),\n", - " source=deepcopy(source),\n", - " sensor=deepcopy(sensor),\n", - " simulation_options=simulation_options,\n", - " execution_options=execution_options)" - ] - }, - { - "cell_type": "markdown", - "id": "77173d6d-bacc-46ee-be2b-e085d4985401", - "metadata": {}, - "source": [ - "## Post-processing\n", - "\n", - "Extract amplitude from the sensor data, using the Fourier transform. The data can be reshaped to match the spatial extents of the domain. The on-axis pressure amplitudes found and axes for plotting defined." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fbd76b31-58b4-4310-bdc6-0b7c6e7818e9", - "metadata": {}, - "outputs": [], - "source": [ - "# extract amplitude from the sensor data\n", - "amp, _, _ = extract_amp_phase(sensor_data['p'].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window='Rectangular')\n", - "\n", - "# reshape data\n", - "amp = np.reshape(amp, (Nx - (source_x_offset + 1), Ny), order='F')\n", - "\n", - "# extract pressure on axis\n", - "amp_on_axis = amp[:, Ny // 2]\n", - "\n", - "# define axis vectors for plotting\n", - "x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1):, :] - kgrid.x_vec[source_x_offset])\n", - "y_vec = kgrid.y_vec" - ] - }, - { - "cell_type": "markdown", - "id": "11ec4eed-1349-4ea3-a2e9-61ede65f3702", - "metadata": {}, - "source": [ - "## Analytical Solution\n", - "\n", - "An analytical expression cam be found in Pierce[[1]](#cite_note-1). Given a transdcuer of radius $a$, wavenumber $k= 2 \\pi f / c$, where $f$ is the frequency, speed of sound $c$, and a unit normal vector to transducer surface, $\\hat{v}_n$, the on-axis pressure is given by\n", - "\n", - "$$\n", - "p_{\\mathrm{ref}}(z) = −2 \\, i \\, \\rho \\, c \\, \\hat{v}_n \\, e^{i k \\left( z + \\sqrt{z^2 + a^2} \\right) \\big/ 2} \\sin \\left( \\dfrac{k}{2} \\left( \\sqrt{z^2 + a^2} − z \\right) \\right).\n", - "$$\n", - "\n", - "[[1]](#cite_ref-1) A. D. Pierce, _\"Acoustics: An Introduction to its Physical Principles and Applications\"_ Springer (2019)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d5691bd3-295d-49c1-b815-13f33bc9b952", - "metadata": {}, - "outputs": [], - "source": [ - "p_axial = focused_annulus_oneil(source_roc, np.asarray(diameters).T, source_amp / (c0 * rho0), source_phase, source_f0, c0, rho0, np.squeeze(x_vec))" - ] - }, - { - "cell_type": "markdown", - "id": "373af282-a91a-42c2-82dd-7678de72b1fd", - "metadata": {}, - "source": [ - "## Visualisation\n", - "\n", - "First plot the pressure along the focal axis of the piston" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e38cbfea-115d-4196-b3cc-2585a56e861f", - "metadata": {}, - "outputs": [], - "source": [ - "fig1, ax1 = plt.subplots(1, 1)\n", - "ax1.plot(1e3 * x_vec, 1e-6 * p_axial, 'k-', label='Analytical')\n", - "ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, 'b.', label='k-Wave')\n", - "ax1.legend()\n", - "ax1.set(xlabel='Axial Position [mm]',\n", - " ylabel='Pressure [MPa]',\n", - " title='Axial Pressure')\n", - "ax1.set_xlim(0.0, 1e3 * axial_size)\n", - "ax1.set_ylim(0.0, 6)\n", - "ax1.grid()" - ] - }, - { - "cell_type": "markdown", - "id": "03150718-35bc-4796-990c-7469b6177159", - "metadata": {}, - "source": [ - "Next plot the source mask (pml is outside the grid in this example). This means getting the grid weights first" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f04f9d92-da57-4627-969d-3c8d96f4c396", - "metadata": {}, - "outputs": [], - "source": [ - "# get grid weights\n", - "grid_weights = karray.get_array_grid_weights(kgrid)\n", - "\n", - "fig2, (ax2a, ax2b) = plt.subplots(1, 2)\n", - "ax2a.pcolormesh(1e3 * np.squeeze(kgrid.y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec),\n", - " np.flip(source.p_mask[:, :, int(np.ceil(Nz / 2))], axis=0),\n", - " shading='nearest')\n", - "ax2a.set(xlabel='y [mm]',\n", - " ylabel='x [mm]',\n", - " title='Source Mask')\n", - "ax2b.pcolormesh(1e3 * np.squeeze(kgrid.y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec),\n", - " np.flip(grid_weights[:, :, int(np.ceil(Nz / 2))], axis=0),\n", - " shading='nearest')\n", - "ax2b.set_xlabel('y [mm]')\n", - "ax2b.set_ylabel('x [mm]')\n", - "_ = ax2b.set_title('Off-Grid Source Weights')\n", - "plt.tight_layout(pad=1.2)" - ] - }, - { - "cell_type": "markdown", - "id": "d51ef784-06c3-4c5f-a478-e6864aa12041", - "metadata": {}, - "source": [ - "Finally, plot the pressure field" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "109c084a-29fb-4180-88e7-e37ecf3b3927", - "metadata": {}, - "outputs": [], - "source": [ - "fig3, ax3 = plt.subplots(1, 1)\n", - "p3 = ax3.pcolormesh(1e3 * np.squeeze(y_vec),\n", - " 1e3 * np.squeeze(x_vec),\n", - " np.flip(amp, axis=1) / 1e6,\n", - " shading='gouraud')\n", - "ax3.set(xlabel='Lateral Position [mm]',\n", - " ylabel='Axial Position [mm]',\n", - " title='Pressure Field')\n", - "ax3.set_ylim(1e3 * x_vec[-1], 1e3 * x_vec[0])\n", - "cbar3 = fig3.colorbar(p3, ax=ax3)\n", - "_ = cbar3.ax.set_title('[MPa]', fontsize='small')" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "include_colab_link": true, - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 5 -} diff --git a/examples/at_focused_bowl_3D/README.md b/examples/at_focused_bowl_3D/README.md deleted file mode 100644 index f40f95a29..000000000 --- a/examples/at_focused_bowl_3D/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Modelling A Focused Bowl Transducer Example - -This example models a focused bowl transducer in 3D. The _on-axis_ pressure is compared with an analytical solution calculated using `focused_bowl_oneil`. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading5) \ No newline at end of file diff --git a/examples/at_focused_bowl_3D/at_focused_bowl_3D.ipynb b/examples/at_focused_bowl_3D/at_focused_bowl_3D.ipynb deleted file mode 100644 index 74ddf92f5..000000000 --- a/examples/at_focused_bowl_3D/at_focused_bowl_3D.ipynb +++ /dev/null @@ -1,451 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "5b6f8e7a-5c7d-4340-8ed4-a04099307131", - "metadata": {}, - "outputs": [], - "source": [ - "%pip install git+https://github.com/waltsims/k-wave-python " - ] - }, - { - "cell_type": "markdown", - "id": "db1bda7e-a820-4eb4-abf9-96ab041675af", - "metadata": {}, - "source": [ - "## Modelling A Focused Bowl Transducer In 3D Example\n", - "\n", - "This example models a focused bowl transducer in 3D. The on-axis pressure is compared with the exact solution calculated using `focused_bowl_oneil` function.\n", - "\n", - "First, define the settings, import the libraries and functions needed:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32e28e46-df00-4a08-996f-5683470c0e2b", - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "from copy import deepcopy\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.filters import extract_amp_phase\n", - "from kwave.utils.kwave_array import kWaveArray\n", - "from kwave.utils.mapgen import focused_bowl_oneil\n", - "from kwave.utils.math import round_even\n", - "from kwave.utils.signals import create_cw_signals\n" - ] - }, - { - "cell_type": "markdown", - "id": "a28dbad5-6ba4-42ef-9e55-715d2383df68", - "metadata": {}, - "source": [ - "The parameters of the system are defined below" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "67150133-3e02-4fd9-a36d-27a9550fe84f", - "metadata": {}, - "outputs": [], - "source": [ - "# medium parameters\n", - "c0: float = 1500.0 # sound speed [m/s]\n", - "rho0: float = 1000.0 # density [kg/m^3]\n", - "\n", - "# source parameters\n", - "source_f0 = 1.0e6 # source frequency [Hz]\n", - "source_roc = 30e-3 # bowl radius of curvature [m]\n", - "source_diameter = 30e-3 # bowl aperture diameter [m]\n", - "source_amp = 1.0e6 # source pressure [Pa]\n", - "source_phase = np.array([0.0]) # source phase [radians]\n", - "\n", - "# grid parameters\n", - "axial_size: float = 50.0e-3 # total grid size in the axial dimension [m]\n", - "lateral_size: float = 40.0e-3 # total grid size in the lateral dimension [m]\n", - "\n", - "# computational parameters\n", - "ppw: int = 3 # number of points per wavelength\n", - "t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state)\n", - "record_periods: int = 1 # number of periods to record\n", - "cfl: float = 0.5 # CFL number\n", - "source_x_offset: int = 20 # grid points to offset the source\n", - "bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points\n", - "upsampling_rate: int = 10 # density of integration points relative to grid\n", - "verbose_level: int = 0 # verbosity of k-wave executable" - ] - }, - { - "cell_type": "markdown", - "id": "26e9fc37-096f-4e2d-af2b-32de0e085cd6", - "metadata": {}, - "source": [ - "## Grid\n", - "\n", - "Construct the grid via the `kgrid` class" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dae95498-e846-4cb1-87ac-dcc684e952bc", - "metadata": {}, - "outputs": [], - "source": [ - "# calculate the grid spacing based on the points-per-wavelength and fundamental frequency\n", - "dx: float = c0 / (ppw * source_f0) # [m]\n", - "\n", - "# compute the size of the grid\n", - "Nx: int = round_even(axial_size / dx) + source_x_offset\n", - "Ny: int = round_even(lateral_size / dx)\n", - "Nz: int = Ny\n", - "\n", - "grid_size_points = Vector([Nx, Ny, Nz])\n", - "grid_spacing_meters = Vector([dx, dx, dx])\n", - "\n", - "# create the k-space grid\n", - "kgrid = kWaveGrid(grid_size_points, grid_spacing_meters)\n", - "\n", - "# compute points per period\n", - "ppp: int = round(ppw / cfl)\n", - "\n", - "# compute corresponding time spacing\n", - "dt: float = 1.0 / (ppp * source_f0)\n", - "\n", - "# create the time array using an integer number of points per period\n", - "Nt: int = int(np.round(t_end / dt))\n", - "kgrid.setTime(Nt, dt)\n", - "\n", - "# calculate the actual CFL and PPW\n", - "print('points-per-period: ' + str(c0 / (dx * source_f0)) + ' and CFL number : ' + str(c0 * dt / dx))" - ] - }, - { - "cell_type": "markdown", - "id": "b39b121c-cc4d-45ee-9d0a-3cee00c690f5", - "metadata": {}, - "source": [ - "## Source\n", - "\n", - "Define the source, using the `kWaveArray` class and the `add_bowl_element` method along with a continuous wave signal." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d8474039-e3b7-4f09-b76d-6bfaf536bc21", - "metadata": {}, - "outputs": [], - "source": [ - "source = kSource()\n", - "\n", - "# create time varying source\n", - "source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, np.array([source_amp]), source_phase)\n", - "\n", - "# set bowl position and orientation\n", - "bowl_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0.0, 0.0]\n", - "focus_pos = [kgrid.x_vec[-1].item(), 0.0, 0.0]\n", - "\n", - "# create empty kWaveArray\n", - "karray = kWaveArray(bli_tolerance=bli_tolerance,\n", - " upsampling_rate=upsampling_rate,\n", - " single_precision=True)\n", - "\n", - "# add bowl shaped element\n", - "karray.add_bowl_element(bowl_pos, source_roc, source_diameter, focus_pos)\n", - "\n", - "# assign binary mask\n", - "source.p_mask = karray.get_array_binary_mask(kgrid)\n", - "\n", - "# assign source signals\n", - "source.p = karray.get_distributed_source_signal(kgrid, source_sig)" - ] - }, - { - "cell_type": "markdown", - "id": "d9e68ba8-f59f-441d-a86f-bd649571a752", - "metadata": {}, - "source": [ - "## Medium\n", - "\n", - "The medium is water. Neither nonlinearity nor attenuation are considered." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f402c35a-43db-49cc-8947-afd1842f5c63", - "metadata": {}, - "outputs": [], - "source": [ - "# assign medium properties\n", - "medium = kWaveMedium(sound_speed=c0, density=rho0)" - ] - }, - { - "cell_type": "markdown", - "id": "acb68ad1-1f98-4dd5-b000-301386aab42e", - "metadata": {}, - "source": [ - "## Sensor\n", - "\n", - "The sensor class defines what acoustic information is recorded.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "882f547c-9b94-4da5-8157-3157b983bdf3", - "metadata": {}, - "outputs": [], - "source": [ - "sensor = kSensor()\n", - "\n", - "# set sensor mask to record central plane, not including the source point\n", - "sensor.mask = np.zeros((Nx, Ny, Nz), dtype=bool)\n", - "sensor.mask[(source_x_offset + 1):-1, :, Nz // 2] = True\n", - "\n", - "# record the pressure\n", - "sensor.record = ['p']\n", - "\n", - "# record only the final few periods when the field is in steady state\n", - "sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1" - ] - }, - { - "cell_type": "markdown", - "id": "7df50ac4-3e8e-4cdf-a275-90b54c7e5bea", - "metadata": {}, - "source": [ - "## Simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "66826f6c-b520-463b-a51f-aa4c15ed72a5", - "metadata": {}, - "outputs": [], - "source": [ - "simulation_options = SimulationOptions(pml_auto=True,\n", - " data_recast=True,\n", - " save_to_disk=True,\n", - " save_to_disk_exit=False,\n", - " pml_inside=False)\n", - "\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True,\n", - " delete_data=False,\n", - " verbose_level=0)\n", - "\n", - "sensor_data = kspaceFirstOrder3D(medium=deepcopy(medium),\n", - " kgrid=deepcopy(kgrid),\n", - " source=deepcopy(source),\n", - " sensor=deepcopy(sensor),\n", - " simulation_options=simulation_options,\n", - " execution_options=execution_options)" - ] - }, - { - "cell_type": "markdown", - "id": "77173d6d-bacc-46ee-be2b-e085d4985401", - "metadata": {}, - "source": [ - "## Post-processing\n", - "\n", - "Extract amplitude from the sensor data, using the Fourier transform. The data can be reshaped to match the spatial extents of the domain. The on-axis pressure amplitudes found and axes for plotting defined." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fbd76b31-58b4-4310-bdc6-0b7c6e7818e9", - "metadata": {}, - "outputs": [], - "source": [ - "# extract amplitude from the sensor data\n", - "amp, _, _ = extract_amp_phase(sensor_data['p'].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window='Rectangular')\n", - "\n", - "# reshape data\n", - "amp = np.reshape(amp, (Nx - (source_x_offset+2), Ny), order='F')\n", - "\n", - "# extract pressure on axis\n", - "amp_on_axis = amp[:, Ny // 2]\n", - "\n", - "# define axis vectors for plotting\n", - "x_vec = kgrid.x_vec[(source_x_offset + 1):-1, :] - kgrid.x_vec[source_x_offset]\n", - "y_vec = kgrid.y_vec" - ] - }, - { - "cell_type": "markdown", - "id": "11ec4eed-1349-4ea3-a2e9-61ede65f3702", - "metadata": {}, - "source": [ - "## Analytical Solution\n", - "\n", - "An analytical expression cam be found in Pierce[[1]](#cite_note-1). Given a transdcuer of radius $a$, wavenumber $k= 2 \\pi f / c$, where $f$ is the frequency, speed of sound $c$, density $\\rho$, and a unit normal vector to transducer surface, $\\hat{v}_n$, the on-axis pressure, along $z$, is given by\n", - "\n", - "$$\n", - "p_{\\mathrm{ref}}(z) = −2 \\, i \\, \\rho \\, c \\, \\hat{v}_n \\, e^{i k \\left( z + \\sqrt{z^2 + a^2} \\right) \\big/ 2} \\sin \\left( \\dfrac{k}{2} \\left( \\sqrt{z^2 + a^2} − z \\right) \\right).\n", - "$$\n", - "\n", - "[[1]](#cite_ref-1) A. D. Pierce, _\"Acoustics: An Introduction to its Physical Principles and Applications\"_ Springer (2019)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d5691bd3-295d-49c1-b815-13f33bc9b952", - "metadata": {}, - "outputs": [], - "source": [ - "# define axis\n", - "x_max = Nx * dx\n", - "delta_x = x_max / 10000.0\n", - "x_ref = np.arange(0.0, x_max + delta_x, delta_x)\n", - "\n", - "Z = source_amp / (c0 * rho0)\n", - "\n", - "# calculate analytical solution\n", - "p_ref_axial, _, _ = focused_bowl_oneil(source_roc, source_diameter, Z, source_f0, c0, rho0, axial_positions=x_ref)\n", - "\n", - "# calculate analytical solution at exactly the same points as the simulation\n", - "p_ref_axial_kw, _, _ = focused_bowl_oneil(source_roc, source_diameter, Z, source_f0, c0, rho0, axial_positions=np.squeeze(x_vec))" - ] - }, - { - "cell_type": "markdown", - "id": "373af282-a91a-42c2-82dd-7678de72b1fd", - "metadata": {}, - "source": [ - "## Visualisation\n", - "\n", - "First plot the pressure along the focal axis of the piston" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e38cbfea-115d-4196-b3cc-2585a56e861f", - "metadata": {}, - "outputs": [], - "source": [ - "fig1, ax1 = plt.subplots(1, 1)\n", - "ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, 'k-', label='Analytical')\n", - "ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, 'b.', label='k-Wave')\n", - "ax1.legend()\n", - "ax1.set(xlabel='Axial Position [mm]',\n", - " ylabel='Pressure [MPa]',\n", - " title='Axial Pressure')\n", - "ax1.set_xlim(0.0, 1e3 * axial_size)\n", - "ax1.set_ylim(0.0, 20)\n", - "ax1.grid()" - ] - }, - { - "cell_type": "markdown", - "id": "03150718-35bc-4796-990c-7469b6177159", - "metadata": {}, - "source": [ - "Next plot the source mask (pml is outside the grid in this example). This means getting the grid weights first" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f04f9d92-da57-4627-969d-3c8d96f4c396", - "metadata": {}, - "outputs": [], - "source": [ - "# get grid weights\n", - "grid_weights = karray.get_array_grid_weights(kgrid)\n", - "\n", - "fig2, (ax2a, ax2b) = plt.subplots(1, 2)\n", - "ax2a.pcolormesh(1e3 * np.squeeze(kgrid.y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec),\n", - " np.flip(source.p_mask[:, :, Nz // 2], axis=0),\n", - " shading='nearest')\n", - "ax2a.set(xlabel='y [mm]',\n", - " ylabel='x [mm]',\n", - " title='Source Mask')\n", - "ax2b.pcolormesh(1e3 * np.squeeze(kgrid.y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec),\n", - " np.flip(grid_weights[:, :, Nz // 2], axis=0),\n", - " shading='nearest')\n", - "ax2b.set_xlabel('y [mm]')\n", - "ax2b.set_ylabel('x [mm]')\n", - "_ = ax2b.set_title('Off-Grid Source Weights')\n", - "plt.tight_layout(pad=1.2)" - ] - }, - { - "cell_type": "markdown", - "id": "d51ef784-06c3-4c5f-a478-e6864aa12041", - "metadata": {}, - "source": [ - "Finally, plot the pressure field" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "109c084a-29fb-4180-88e7-e37ecf3b3927", - "metadata": {}, - "outputs": [], - "source": [ - "fig3, ax3 = plt.subplots(1, 1)\n", - "p3 = ax3.pcolormesh(1e3 * np.squeeze(y_vec),\n", - " 1e3 * np.squeeze(x_vec),\n", - " np.flip(amp, axis=1) / 1e6,\n", - " shading='gouraud')\n", - "ax3.set(xlabel='Lateral Position [mm]',\n", - " ylabel='Axial Position [mm]',\n", - " title='Pressure Field')\n", - "ax3.set_ylim(1e3 * x_vec[-1], 1e3 * x_vec[0])\n", - "cbar3 = fig3.colorbar(p3, ax=ax3)\n", - "_ = cbar3.ax.set_title('[MPa]', fontsize='small')" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 5 -} diff --git a/examples/at_focused_bowl_AS/README.md b/examples/at_focused_bowl_AS/README.md deleted file mode 100644 index 505a07eee..000000000 --- a/examples/at_focused_bowl_AS/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Modelling An Axisymmetric Focused Bowl Transducer Example - -This example models an axisymmetric focused bowl transducer. The _on-axis_ pressure is compared with an analytical solution calculated using `focused_bowl_oneil`. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading6) \ No newline at end of file diff --git a/examples/at_focused_bowl_AS/at_focused_bowl_AS.ipynb b/examples/at_focused_bowl_AS/at_focused_bowl_AS.ipynb deleted file mode 100644 index 152ad9337..000000000 --- a/examples/at_focused_bowl_AS/at_focused_bowl_AS.ipynb +++ /dev/null @@ -1,384 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install git+https://github.com/waltsims/k-wave-python" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from copy import deepcopy\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrderAS import kspaceFirstOrderASC\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions, SimulationType\n", - "from kwave.utils.filters import extract_amp_phase\n", - "from kwave.utils.kwave_array import kWaveArray\n", - "from kwave.utils.mapgen import focused_bowl_oneil\n", - "from kwave.utils.math import round_even\n", - "from kwave.utils.signals import create_cw_signals" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define simulation parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "verbose: bool = False\n", - "\n", - "# medium parameters\n", - "c0: float = 1500.0 # sound speed [m/s]\n", - "rho0: float = 1000.0 # density [kg/m^3]\n", - "\n", - "# source parameters\n", - "source_f0 = 1.0e6 # source frequency [Hz]\n", - "source_roc = 30e-3 # bowl radius of curvature [m]\n", - "source_diameter = 30e-3 # bowl aperture diameter [m]\n", - "source_amp = np.array([1.0e6]) # source pressure [Pa]\n", - "source_phase = np.array([0.0]) # source phase [radians]\n", - "\n", - "# grid parameters\n", - "axial_size: float = 50.0e-3 # total grid size in the axial dimension [m]\n", - "lateral_size: float = 45.0e-3 # total grid size in the lateral dimension [m]\n", - "\n", - "# computational parameters\n", - "ppw: int = 3 # number of points per wavelength\n", - "t_end: float = 40e-6 # total compute time [s] (this must be long enough to reach steady state)\n", - "record_periods: int = 1 # number of periods to record\n", - "cfl: float = 0.05 # CFL number\n", - "source_x_offset: int = 20 # grid points to offset the source\n", - "bli_tolerance: float = 0.01 # tolerance for truncation of the off-grid source points\n", - "upsampling_rate: int = 10 # density of integration points relative to grid" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# calculate the grid spacing based on the PPW and F0\n", - "dx: float = c0 / (ppw * source_f0) # [m]\n", - "\n", - "# compute the size of the grid\n", - "Nx: int= round_even(axial_size / dx) + source_x_offset\n", - "Ny: int = round_even(lateral_size / dx)\n", - "\n", - "grid_size_points = Vector([Nx, Ny])\n", - "grid_spacing_meters = Vector([dx, dx])\n", - "\n", - "# create the k-space grid\n", - "kgrid = kWaveGrid(grid_size_points, grid_spacing_meters)\n", - "\n", - "# compute points per temporal period\n", - "ppp: int = round(ppw / cfl)\n", - "\n", - "# compute corresponding time spacing\n", - "dt: float = 1.0 / (ppp * source_f0)\n", - "\n", - "# create the time array using an integer number of points per period\n", - "Nt: int = round(t_end / dt)\n", - "kgrid.setTime(Nt, dt)\n", - "\n", - "# calculate the actual CFL and PPW\n", - "if verbose:\n", - " print('PPW = ' + str(c0 / (dx * source_f0)))\n", - " print('CFL = ' + str(c0 * dt / dx))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Assign medium properties" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "medium = kWaveMedium(sound_speed=c0, density=rho0)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Set up the source" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "source = kSource()\n", - "\n", - "# create time varying source\n", - "source_sig = create_cw_signals(np.squeeze(kgrid.t_array), source_f0, source_amp,\n", - " source_phase)\n", - "\n", - "# set arc position and orientation\n", - "arc_pos = [kgrid.x_vec[0].item() + source_x_offset * kgrid.dx, 0]\n", - "focus_pos = [kgrid.x_vec[-1].item(), 0]\n", - "\n", - "# create empty kWaveArray\n", - "karray = kWaveArray(axisymmetric=True,\n", - " bli_tolerance=bli_tolerance,\n", - " upsampling_rate=upsampling_rate,\n", - " single_precision=True)\n", - "\n", - "# add bowl shaped element\n", - "karray.add_arc_element(arc_pos, source_roc, source_diameter, focus_pos)\n", - "\n", - "# assign binary mask\n", - "source.p_mask = karray.get_array_binary_mask(kgrid)\n", - "\n", - "# assign source signals\n", - "source.p = karray.get_distributed_source_signal(kgrid, source_sig)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Define the sensor" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sensor = kSensor()\n", - "\n", - "# set sensor mask to record central plane, not including the source point\n", - "sensor.mask = np.zeros((Nx, Ny), dtype=bool)\n", - "sensor.mask[(source_x_offset + 1):, :] = True\n", - "\n", - "# record the pressure\n", - "sensor.record = ['p']\n", - "\n", - "# record only the final few periods when the field is in steady state\n", - "sensor.record_start_index = kgrid.Nt - (record_periods * ppp) + 1" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Simulation options. Note that for axisymmetric computations the simulation type must be set and the declared that it will run on the CPU." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "simulation_options = SimulationOptions(\n", - " simulation_type=SimulationType.AXISYMMETRIC,\n", - " data_cast='single',\n", - " data_recast=False,\n", - " save_to_disk=True,\n", - " save_to_disk_exit=False,\n", - " pml_inside=False)\n", - "\n", - "execution_options = SimulationExecutionOptions(\n", - " is_gpu_simulation=False,\n", - " delete_data=False,\n", - " verbose_level=2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Run the simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sensor_data = kspaceFirstOrderASC(medium=deepcopy(medium),\n", - " kgrid=deepcopy(kgrid),\n", - " source=deepcopy(source),\n", - " sensor=deepcopy(sensor),\n", - " simulation_options=simulation_options,\n", - " execution_options=execution_options)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Post-process the data, and calculate the on-axis analytical solution" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# extract amplitude from the sensor data\n", - "amp, _, _ = extract_amp_phase(sensor_data['p'].T, 1.0 / kgrid.dt, source_f0,\n", - " dim=1, fft_padding=1, window='Rectangular')\n", - "\n", - "# reshape data\n", - "amp = np.reshape(amp, (Nx - (source_x_offset+1), Ny), order='F')\n", - "\n", - "# extract pressure on axis\n", - "amp_on_axis = amp[:, 0]\n", - "\n", - "# define axis vectors for plotting\n", - "x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1):, :] - kgrid.x_vec[source_x_offset])\n", - "y_vec = kgrid.y_vec\n", - "\n", - "# calculate the wavenumber\n", - "knumber = 2.0 * np.pi * source_f0 / c0\n", - "\n", - "# define beam axis for plotting\n", - "x_max = (Nx-1) * dx\n", - "delta_x = x_max / 10000.0\n", - "x_ref = np.arange(0.0, x_max + delta_x, delta_x)\n", - "\n", - "# calculate analytical solution\n", - "p_ref_axial, _, _ = focused_bowl_oneil(source_roc,\n", - " source_diameter,\n", - " source_amp[0] / (c0 * rho0),\n", - " source_f0,\n", - " c0,\n", - " rho0,\n", - " axial_positions=x_ref)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Visualize solutions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# =========================================================================\n", - "# VISUALISATION\n", - "# =========================================================================\n", - "\n", - "# plot the pressure along the focal axis of the piston\n", - "fig1, ax1 = plt.subplots(1, 1)\n", - "ax1.plot(1e3 * x_ref, 1e-6 * p_ref_axial, 'k-', label='Exact')\n", - "ax1.plot(1e3 * x_vec, 1e-6 * amp_on_axis, 'b.', label='k-Wave')\n", - "ax1.legend()\n", - "ax1.set(xlabel='Axial Position [mm]',\n", - " ylabel='Pressure [MPa]',\n", - " title='Axial Pressure')\n", - "ax1.set_xlim(0.0, 1e3 * axial_size)\n", - "ax1.set_ylim(0.0, 20)\n", - "ax1.grid()\n", - "\n", - "# get grid weights\n", - "grid_weights = karray.get_array_grid_weights(kgrid)\n", - "\n", - "# define axis vectors for plotting\n", - "yvec = np.squeeze(kgrid.y_vec) - kgrid.y_vec[0].item()\n", - "y_vec = np.hstack((-np.flip(yvec)[:-1], yvec))\n", - "x_vec = np.squeeze(kgrid.x_vec[(source_x_offset + 1):, :] - kgrid.x_vec[source_x_offset])\n", - "\n", - "data = np.hstack((np.fliplr(amp[:, :-1]), amp)) / 1e6\n", - "sp = np.hstack((np.fliplr(source.p_mask[:, :-1]), source.p_mask))\n", - "gw = np.hstack((np.fliplr(grid_weights[:, :-1]), grid_weights))\n", - "\n", - "fig3, (ax3a, ax3b) = plt.subplots(1, 2)\n", - "ax3a.pcolormesh(1e3 * np.squeeze(y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec[:, :] - kgrid.x_vec[source_x_offset]),\n", - " sp,\n", - " shading='gouraud')\n", - "ax3a.set(xlabel='y [mm]',\n", - " ylabel='x [mm]',\n", - " title='Source Mask')\n", - "ax3b.pcolormesh(1e3 * np.squeeze(y_vec),\n", - " 1e3 * np.squeeze(kgrid.x_vec[:, :] - kgrid.x_vec[source_x_offset]),\n", - " gw,\n", - " shading='gouraud')\n", - "ax3b.set(xlabel='y [mm]',\n", - " ylabel='x [mm]',\n", - " title='Off-Grid Source Weights')\n", - "fig3.tight_layout(pad=1.2)\n", - "\n", - "# plot the pressure field\n", - "fig4, ax4 = plt.subplots(1, 1)\n", - "ax4.pcolormesh(1e3 * np.squeeze(y_vec),\n", - " 1e3 * np.squeeze(x_vec),\n", - " data,\n", - " shading='gouraud')\n", - "ax4.set(xlabel='Lateral Position [mm]',\n", - " ylabel='Axial Position [mm]',\n", - " title='Pressure Field')\n", - "ax4.invert_yaxis()\n", - "\n", - "# show figures\n", - "plt.show()" - ] - } - ], - "metadata": { - "colab": { - "authorship_tag": "ABX9TyPvgfi818S8IbWGlN6C6Tmi", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 0 -} diff --git a/examples/at_linear_array_transducer/README.md b/examples/at_linear_array_transducer/README.md deleted file mode 100644 index 0b4793587..000000000 --- a/examples/at_linear_array_transducer/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Modelling A Linear Array Transducer Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_linear_array_transducer/at_linear_array_transducer.ipynb) - -This example provides a demonstration of using the kWaveArray class to define a linear array transducer with 15 rectangular elements. Electronic focusing is then used to transmit an ultrasound pulse. It builds on the [Defining A Source Using An Array Transducer Example](../at_array_as_source/). - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_at_linear_array_transducer.php). \ No newline at end of file diff --git a/examples/at_linear_array_transducer/at_linear_array_transducer.ipynb b/examples/at_linear_array_transducer/at_linear_array_transducer.ipynb deleted file mode 100644 index a5a6207bc..000000000 --- a/examples/at_linear_array_transducer/at_linear_array_transducer.ipynb +++ /dev/null @@ -1,201 +0,0 @@ -{ - "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", - "import kwave.data\n", - "from kwave.kgrid import kWaveGrid\n", - "from kwave.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3DC\n", - "from kwave.kWaveSimulation import SimulationOptions\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.kwave_array import kWaveArray\n", - "from kwave.utils.plot import voxel_plot\n", - "from kwave.utils.signals import tone_burst" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define constants\n", - "c0 = 1500\n", - "rho0 = 1000\n", - "source_f0 = 1e6\n", - "source_amp = 1e6\n", - "source_cycles = 5\n", - "source_focus = 20e-3\n", - "element_num = 15\n", - "element_width = 1e-3\n", - "element_length = 10e-3\n", - "element_pitch = 2e-3\n", - "translation = kwave.data.Vector([5e-3, 0, 8e-3])\n", - "rotation = kwave.data.Vector([0, 20, 0])\n", - "grid_size_x = 40e-3\n", - "grid_size_y = 20e-3\n", - "grid_size_z = 40e-3\n", - "ppw = 3\n", - "t_end = 35e-6\n", - "cfl = 0.5\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "# GRID\n", - "dx = c0 / (ppw * source_f0)\n", - "Nx = round(grid_size_x / dx)\n", - "Ny = round(grid_size_y / dx)\n", - "Nz = round(grid_size_z / dx)\n", - "kgrid = kWaveGrid([Nx, Ny, Nz], [dx, dx, dx])\n", - "kgrid.makeTime(c0, cfl, t_end)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# SOURCE\n", - "if element_num % 2 != 0:\n", - " ids = np.arange(1, element_num + 1) - np.ceil(element_num / 2)\n", - "else:\n", - " ids = np.arange(1, element_num + 1) - (element_num + 1) / 2\n", - "\n", - "time_delays = -(np.sqrt((ids * element_pitch) ** 2 + source_focus ** 2) - source_focus) / c0\n", - "time_delays = time_delays - min(time_delays)\n", - "\n", - "source_sig = source_amp * tone_burst(1 / kgrid.dt, source_f0, source_cycles,\n", - " signal_offset=np.round(time_delays / kgrid.dt).astype(int))\n", - "karray = kWaveArray(bli_tolerance=0.05, upsampling_rate=10)\n", - "\n", - "for ind in range(element_num):\n", - " x_pos = 0 - (element_num * element_pitch / 2 - element_pitch / 2) + ind * element_pitch\n", - " karray.add_rect_element([x_pos, 0, kgrid.z_vec[0][0]], element_width, element_length, rotation)\n", - "\n", - "karray.set_array_position(translation, rotation)\n", - "source = kSource()\n", - "source.p_mask = karray.get_array_binary_mask(kgrid)\n", - "%matplotlib inline\n", - "voxel_plot(np.single(source.p_mask))\n", - "source.p = karray.get_distributed_source_signal(kgrid, source_sig)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# MEDIUM\n", - "medium = kWaveMedium(sound_speed=c0, density=rho0)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# SENSOR\n", - "sensor_mask = np.zeros((Nx, Ny, Nz))\n", - "sensor_mask[:, Ny // 2, :] = 1\n", - "sensor = kSensor(sensor_mask, record=['p_max'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# SIMULATION\n", - "simulation_options = SimulationOptions(\n", - " pml_auto=True,\n", - " pml_inside=False,\n", - " save_to_disk=True,\n", - " data_cast='single',\n", - ")\n", - "\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True)\n", - "\n", - "sensor_data = kspaceFirstOrder3DC(kgrid=kgrid, medium=medium, source=source, sensor=sensor,\n", - " simulation_options=simulation_options, execution_options=execution_options)\n", - "\n", - "p_max = np.reshape(sensor_data['p_max'], (Nx, Nz), order='F')\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# VISUALISATION\n", - "plt.figure()\n", - "plt.imshow(1e-6 * p_max, extent=[1e3 * kgrid.x_vec[0][0], 1e3 * kgrid.x_vec[-1][0], 1e3 * kgrid.z_vec[0][0],\n", - " 1e3 * kgrid.z_vec[-1][0]], aspect='auto', cmap=get_color_map())\n", - "plt.xlabel('z-position [mm]')\n", - "plt.ylabel('x-position [mm]')\n", - "plt.title('Pressure Field')\n", - "plt.colorbar(label='[MPa]')\n", - "plt.show()\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 0 -} diff --git a/examples/checkpointing/README.md b/examples/checkpointing/README.md deleted file mode 100644 index c0f068010..000000000 --- a/examples/checkpointing/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Checkpointing - -This example demonstrates how to checkpoint simulations. - -It uses the same simulation parameters as found in "3D FFT Reconstruction For A Planar Sensor". \ No newline at end of file diff --git a/examples/ivp_photoacoustic_waveforms/README.md b/examples/ivp_photoacoustic_waveforms/README.md deleted file mode 100644 index 625650ac1..000000000 --- a/examples/ivp_photoacoustic_waveforms/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Initial Value Problem: Photoacoustic Waveforms in 2D and 3D - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb) - -See the k-wave example [here](http://www.k-wave.org/documentation/example_ivp_photoacoustic_waveforms.php), which includes one-dimensional propagation as well. - -> [!WARNING] -> As there is no one-dimensional simulation, the example does not fully recreate the k-wave example. It underable to show how a 1D wave is different from 2D and 3D waves. - -## Rational - -The purpose of this example is to show that the dimensions of a system influence characteristics of the propagation. A point source in two-dimensions is represented by an infinite line source in three-dimensions. - -In two-dimensions there is cylindrical spreading, This means the acoustic energy is inversely proportional to radius, $r$, and the acoustic pressure decays as $1/\sqrt r$. In three-dimensions there is spherical spreading, so the energy is spread over $r^2$, and the pressure decays as ${1/r}$. - -Photoacoustic waves in 3D have [compact support](https://en.wikipedia.org/wiki/Support_(mathematics)#Compact_support). This means they decay to zero rapidly, whereas a waveform in 2D does not have this property. As an infinite line source in 3D, there will always be some signal arriving to the detector from some (increasingly distant) part of the line source. diff --git a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb b/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb deleted file mode 100644 index 42f832b74..000000000 --- a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb +++ /dev/null @@ -1,284 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "11cd3967", - "metadata": {}, - "source": [ - "# Initial Value Problem: Photoacoustic Waveforms\n", - "\n", - "First load the modules needed." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c11efc20", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "!pip install k-wave-python" - ] - }, - { - "cell_type": "markdown", - "id": "35bbf8ce", - "metadata": {}, - "source": [ - "Now import the libraries we need" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1fe4153f", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.data import scale_SI\n", - "from kwave.utils.mapgen import make_ball, make_disc" - ] - }, - { - "cell_type": "markdown", - "id": "96d23a4d", - "metadata": {}, - "source": [ - "Next define the problem in terms of the domain dimension, size, source position, etc." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a7e5f5b5", - "metadata": {}, - "outputs": [], - "source": [ - "Nx = 64 # number of grid points in the x (row) direction\n", - "\n", - "x = 1e-3 # size of the domain in the x direction [m]\n", - "\n", - "dx = x / Nx # grid point spacing in the x direction [m]\n", - "\n", - "sound_speed = 1500\n", - "\n", - "# size of the initial pressure distribution\n", - "source_radius = 2 # [grid points]\n", - "\n", - "# distance between the centre of the source and the sensor\n", - "source_sensor_distance = 10 # [grid points]\n", - "\n", - "# time array\n", - "dt = 2e-9 # [s]\n", - "t_end = 300e-9 # [s]" - ] - }, - { - "cell_type": "markdown", - "id": "87aa5a24", - "metadata": {}, - "source": [ - "Code for 2d simulations" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "964309b3", - "metadata": {}, - "outputs": [], - "source": [ - "medium2 = kWaveMedium(sound_speed=sound_speed)\n", - "\n", - "# create the k-space grid\n", - "kgrid2 = kWaveGrid([Nx, Nx], [dx, dx])\n", - "\n", - "# create the time array using an integer number of points per period\n", - "kgrid2.setTime(int(np.round(t_end / dt)), dt)\n", - "\n", - "# create instance of a sensor\n", - "sensor2 = kSensor()\n", - "\n", - "# set sensor mask: the mask says at which points data should be recorded\n", - "sensor2.mask = np.zeros((Nx, Nx), dtype=bool)\n", - "\n", - "# define a single sensor point\n", - "sensor2.mask[Nx // 2 + source_sensor_distance, Nx // 2] = True\n", - "\n", - "# set the record type: record the pressure waveform\n", - "sensor2.record = ['p']\n", - "\n", - "# make a source object\n", - "source2 = kSource()\n", - "source2.p0 = make_disc(Vector([Nx, Nx]), Vector([Nx // 2, Nx // 2]), source_radius, plot_disc=False)" - ] - }, - { - "cell_type": "markdown", - "id": "a53f8a88", - "metadata": {}, - "source": [ - "simulation and execution options" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "37de7ce4", - "metadata": {}, - "outputs": [], - "source": [ - "simulation_options = SimulationOptions(\n", - " data_cast='single',\n", - " save_to_disk=True)\n", - "\n", - "execution_options = SimulationExecutionOptions(\n", - " is_gpu_simulation=True,\n", - " delete_data=False,\n", - " verbose_level=2)" - ] - }, - { - "cell_type": "markdown", - "id": "315e699c", - "metadata": {}, - "source": [ - "Now run the simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b4e9d359", - "metadata": {}, - "outputs": [], - "source": [ - "# run the simulation\n", - "sensor_data_2D = kspaceFirstOrder2D(\n", - " medium=medium2,\n", - " kgrid=kgrid2,\n", - " source=source2,\n", - " sensor=sensor2,\n", - " simulation_options=simulation_options,\n", - " execution_options=execution_options)" - ] - }, - { - "cell_type": "markdown", - "id": "f25433e0", - "metadata": {}, - "source": [ - "# 3D Simulations" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "76905e44", - "metadata": {}, - "outputs": [], - "source": [ - "medium3 = kWaveMedium(sound_speed=1500)\n", - "\n", - "# create the k-space grid\n", - "kgrid3 = kWaveGrid([Nx, Nx, Nx], [dx, dx, dx])\n", - "\n", - "# create the time array using an integer number of points per period\n", - "kgrid3.setTime(int(np.round(t_end / dt)), dt)\n", - "\n", - "# create instance of a sensor\n", - "sensor3 = kSensor()\n", - "\n", - "# set sensor mask: the mask says at which points data should be recorded\n", - "sensor3.mask = np.zeros((Nx, Nx, Nx), dtype=bool)\n", - "\n", - "# define a single sensor point\n", - "sensor3.mask[Nx // 2 + source_sensor_distance, Nx // 2, Nx // 2] = True\n", - "\n", - "# set the record type: record the pressure waveform\n", - "sensor3.record = ['p']\n", - "\n", - "# make a source object\n", - "source3 = kSource()\n", - "source3.p0 = make_ball(Vector([Nx, Nx, Nx]), Vector([Nx // 2, Nx // 2, Nx // 2]), source_radius)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cfce9e85", - "metadata": {}, - "outputs": [], - "source": [ - "sensor_data_3D = kspaceFirstOrder3D(\n", - " medium=medium3,\n", - " kgrid=kgrid3,\n", - " source=source3,\n", - " sensor=sensor3,\n", - " simulation_options=simulation_options,\n", - " execution_options=execution_options)" - ] - }, - { - "cell_type": "markdown", - "id": "1626ec9b", - "metadata": {}, - "source": [ - "Plot the data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "507443fa", - "metadata": {}, - "outputs": [], - "source": [ - "t_sc, t_scale, t_prefix, _ = scale_SI(t_end)\n", - "_, ax1 = plt.subplots()\n", - "ax1.plot(np.squeeze(kgrid2.t_array * t_scale), sensor_data_2D['p'] / np.max(np.abs(sensor_data_2D['p'])), 'r-')\n", - "ax1.plot(np.squeeze(kgrid3.t_array * t_scale), sensor_data_3D[\"p\"] / np.max(np.abs(sensor_data_3D[\"p\"])), \"k-\", label=\"3D\")\n", - "ax1.set(xlabel= f\"Time [{t_prefix}s]\", ylabel='Recorded Pressure [au]')\n", - "ax1.grid(True)\n", - "plt.show()" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 5 -} diff --git a/examples/na_controlling_the_pml/README.md b/examples/na_controlling_the_pml/README.md deleted file mode 100644 index 45c33b684..000000000 --- a/examples/na_controlling_the_pml/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Using An Ultrasound Transducer As A Sensor Example - - - Open In Colab - - -# Running the example - -Please have a look at the `example_na_controlling_the_pml.ipynb` notebook file to see the example in action. - -## Preparing the Matlab reference files - -We can verify the correctness of Python implementation by saving intermediate/final variable states from the Matlab script -and comparing them against Python version. Saving the variable states can be done manually by running the Matlab script, -pausing it in appropriate places and saving the variables using `save(...)` method. However, this is very involved process. -To automate the process a bit, we followed the steps below. - -1. Grab the original example file from K-wave repository (link: [example_na_controlling_the_PML.m](https://github.com/ucl-bug/k-wave/blob/main/k-Wave/examples/example_na_controlling_the_PML.m)) -2. Modify the file such that we are running for all scenarios (4 different simulation cases) and saving the output sensor data + visualized figures each time. We saved the modified script in the `modified_matlab_example.m` file. -3. Run the modified file from the step above. Now we should have 4x `.mat` and `.png` files for the captured sensor data and visualized figure, respectively. -4. We check against these files in the `example_na_controlling_the_pml.ipynb` notebook. diff --git a/examples/na_controlling_the_pml/na_controlling_the_pml.ipynb b/examples/na_controlling_the_pml/na_controlling_the_pml.ipynb deleted file mode 100644 index 8fcc910b4..000000000 --- a/examples/na_controlling_the_pml/na_controlling_the_pml.ipynb +++ /dev/null @@ -1,232 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using An Ultrasound Transducer As A Sensor Example\n", - "\n", - "This example shows how an ultrasound transducer can be used as a detector by substituting a transducer object for the normal sensor input structure. It builds on the Defining An Ultrasound Transducer and Simulating Ultrasound Beam Patterns examples.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Notes\n", - "\n", - "The example is based on the original kWave example in Matlab. A few parts were adapted to make it compatible with `k-wave-python`." - ] - }, - { - "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 os\n", - "from copy import deepcopy\n", - "from typing import Dict\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.conversion import cart2grid\n", - "from kwave.utils.mapgen import make_cart_circle, make_disc\n", - "from kwave.utils.signals import reorder_binary_sensor_data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create the computational grid and propagation medium\n", - "grid_size = Vector([128, 128]) # [grid points]\n", - "grid_spacing = Vector([0.1e-3, 0.1e-3]) # [m]\n", - "kgrid = kWaveGrid(grid_size, grid_spacing)\n", - "\n", - "medium = kWaveMedium(sound_speed=1500)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create initial pressure distribution using make_disc\n", - "disc_magnitude = 5 # [Pa]\n", - "disc_pos = Vector([50, 50]) # [grid points]\n", - "disc_radius = 8 # [grid points]\n", - "disc_1 = disc_magnitude * make_disc(grid_size, disc_pos, disc_radius)\n", - "\n", - "disc_magnitude = 3 # [Pa]\n", - "disc_pos = Vector([80, 60]) # [grid points]\n", - "disc_radius = 5 # [grid points]\n", - "disc_2 = disc_magnitude * make_disc(grid_size, disc_pos, disc_radius)\n", - "\n", - "source = kSource()\n", - "source.p0 = disc_1 + disc_2" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# define a centered circular sensor\n", - "sensor_radius = 4e-3 # [m]\n", - "num_sensor_points = 50\n", - "sensor_mask = make_cart_circle(sensor_radius, num_sensor_points)\n", - "sensor = kSensor(sensor_mask)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define a function to run simulation with given options\n", - "def run_simulation_with_options(**simulation_opts) -> Dict[str, np.array]:\n", - " simulation_options = SimulationOptions(\n", - " save_to_disk=True,\n", - " **simulation_opts\n", - " )\n", - " execution_options = SimulationExecutionOptions(is_gpu_simulation=False)\n", - " \n", - " sensor_data = kspaceFirstOrder2DC(\n", - " medium=medium,\n", - " kgrid=kgrid,\n", - " source=deepcopy(source),\n", - " sensor=deepcopy(sensor),\n", - " simulation_options=simulation_options,\n", - " execution_options=execution_options\n", - " )\n", - " return sensor_data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Update the example ID below to change the simulation conditions\n", - "example_idx = 1\n", - "\n", - "if example_idx == 1:\n", - " options = {'pml_alpha': 0}\n", - "elif example_idx == 2:\n", - " options = {'pml_alpha': 1e6}\n", - "elif example_idx == 3:\n", - " options = {'pml_size': 2}\n", - "elif example_idx == 4:\n", - " options = {'pml_inside': False}\n", - "else:\n", - " raise NotImplementedError()\n", - "\n", - "sensor_data = run_simulation_with_options(**options)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "_, _, reorder_index = cart2grid(kgrid, sensor.mask)\n", - "sensor_data_point = reorder_binary_sensor_data(sensor_data['p'].T, reorder_index=reorder_index)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "cmap = get_color_map()\n", - "plt.imshow(sensor_data_point, vmin=-1, vmax=1, aspect='auto', cmap=cmap)\n", - "plt.ylabel('Sensor Position')\n", - "plt.xlabel('Time Step')\n", - "plt.colorbar()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Comparing against Matlab implementation\n", - "\n", - "Seems like above example runs successfully but ... How do we know if our results match the Matlab implementation? A simple yet effective strategy is to run Matlab script, record outputs, load them in Python and compare the results. Huh, sounds involved and it is indeed a bit involved. However, we did it and you can do this in two steps:\n", - "1. Open Matlab and run `modified_matlab_example.m` script in this folder.\n", - "2. Execute the cell below.\n", - "\n", - "Step #1 will run the Matlab example and record values. Step #2 will load values and compare `k-wave-python` outputs against Matlab implementation.\n", - "\n", - "You may be wondering at this point, why we do not run `example_na_controlling_the_pml.m` (original Matlab script) instead of the script from this folder. There are two reasons - (1) original example does not record output values in a file and (2) it runs one simulation case at a time. Modified example records output values (recorded pressure and visualized image) and runs all four simulation cases. In case you want to run the original example by adding output recording logic and running all four simulation cases individually, please feel free to do so. If everything is correct, you will get the same results as the `modified_matlab_example.m`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from scipy.io import loadmat\n", - "\n", - "matlab_output_path = f'example_{example_idx}.mat'\n", - "\n", - "if not os.path.exists(matlab_output_path):\n", - " print(\"Data recorded from Matlab script does not exist! Did you run the `modified_matlab_example.m` in Matlab?\")\n", - "else:\n", - " recorded_data = loadmat(matlab_output_path, simplify_cells=True)\n", - " assert np.allclose(recorded_data['sensor_data'].T, sensor_data['p'])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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": 4 -} diff --git a/examples/pr_2D_FFT_line_sensor/README.md b/examples/pr_2D_FFT_line_sensor/README.md deleted file mode 100644 index e05c7e151..000000000 --- a/examples/pr_2D_FFT_line_sensor/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# 2D FFT Reconstruction For A Line Sensor Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb) - -This example demonstrates the use of k-Wave for the reconstruction of a two-dimensional photoacoustic wave-field recorded over a linear array of sensor elements. The sensor data is simulated using [kspaceFirstOrder2D](https://k-wave-python.readthedocs.io/en/latest/kwave.kspaceFirstOrder2D.html) and reconstructed using kspaceLineRecon. It builds on the Homogeneous Propagation Medium and Heterogeneous Propagation Medium examples. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_pr_2D_fft_line_sensor.php) or its [implementation](https://github.com/ucl-bug/k-wave/blob/main/k-Wave/examples/example_pr_2D_FFT_line_sensor.m). \ No newline at end of file diff --git a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb b/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb deleted file mode 100644 index a65feab0c..000000000 --- a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb +++ /dev/null @@ -1,298 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "initial_id", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "!pip install k-wave-python " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "48677f5b96e1511e", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", - "from scipy.interpolate import RegularGridInterpolator\n", - "\n", - "from kwave.data import Vector\n", - "from kwave.kgrid import kWaveGrid\n", - "from kwave.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D\n", - "from kwave.kspaceLineRecon import kspaceLineRecon\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.filters import smooth\n", - "from kwave.utils.mapgen import make_disc" - ] - }, - { - "cell_type": "markdown", - "id": "262efa49a13b9574", - "metadata": {}, - "source": [ - "## 2D FFT Reconstruction For A Line Sensor Example\n", - "\n", - "This example demonstrates the use of k-Wave for the reconstruction of a\n", - "two-dimensional photoacoustic wave-field recorded over a linear array of\n", - "sensor elements The sensor data is simulated using kspaceFirstOrder2D\n", - "and reconstructed using kspaceLineRecon. It builds on the Homogeneous\n", - "Propagation Medium and Heterogeneous Propagation Medium examples. " - ] - }, - { - "cell_type": "markdown", - "id": "310bcfe8541609c2", - "metadata": {}, - "source": [ - "### SIMULATION" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6d7c5328ae5dd3ee", - "metadata": {}, - "outputs": [], - "source": [ - "# create the computational grid\n", - "PML_size = 20 # size of the PML in grid points\n", - "N = Vector([128, 256]) - 2 * PML_size # number of grid points\n", - "d = Vector([0.1e-3, 0.1e-3]) # grid point spacing [m]\n", - "kgrid = kWaveGrid(N, d)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd5eece140453f0f", - "metadata": {}, - "outputs": [], - "source": [ - "# define the properties of the propagation medium\n", - "medium = kWaveMedium(sound_speed=1500) # [m/s]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ce3b8059a9ec319", - "metadata": {}, - "outputs": [], - "source": [ - "# create initial pressure distribution using makeDisc\n", - "disc_magnitude = 5 # [Pa]\n", - "disc_pos = Vector([60, 140])\n", - "disc_radius = 5\n", - "disc_2 = disc_magnitude * make_disc(N, disc_pos, disc_radius)\n", - "\n", - "disc_pos = Vector([30, 110])\n", - "disc_radius = 8\n", - "disc_1 = disc_magnitude * make_disc(N, disc_pos, disc_radius)\n", - "\n", - "# smooth the initial pressure distribution and restore the magnitude\n", - "p0 = disc_1 + disc_2\n", - "p0 = smooth(p0, restore_max=True)\n", - "\n", - "source = kSource()\n", - "source.p0 = p0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cf6961e373c6f8e8", - "metadata": {}, - "outputs": [], - "source": [ - "# define a binary line sensor\n", - "sensor = kSensor()\n", - "sensor.mask = np.zeros(N)\n", - "sensor.mask[0] = 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5a278b1e9b253295", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "# create the time array\n", - "kgrid.makeTime(medium.sound_speed)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a3802e3e482dc77", - "metadata": {}, - "outputs": [], - "source": [ - "# set the input arguments: force the PML to be outside the computational grid\n", - "simulation_options = SimulationOptions(\n", - " save_to_disk=True,\n", - " pml_inside=False,\n", - " pml_size=PML_size,\n", - " smooth_p0=False,\n", - ")\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "154c372469cd6063", - "metadata": {}, - "outputs": [], - "source": [ - "# run the simulation\n", - "sensor_data = kspaceFirstOrder2D(kgrid, source, sensor, medium, simulation_options, execution_options)\n", - "sensor_data = sensor_data['p'].T\n", - "\n", - "# reconstruct the initial pressure\n", - "p_xy = kspaceLineRecon(sensor_data.T, dy=d[1], dt=kgrid.dt.item(), c=medium.sound_speed.item(),\n", - " pos_cond=True, interp='linear')\n", - "\n", - "# define a second k-space grid using the dimensions of p_xy\n", - "N_recon = Vector(p_xy.shape)\n", - "d_recon = Vector([kgrid.dt.item() * medium.sound_speed.item(), kgrid.dy])\n", - "kgrid_recon = kWaveGrid(N_recon, d_recon)\n", - "\n", - "# resample p_xy to be the same size as source.p0\n", - "interp_func = RegularGridInterpolator(\n", - " (kgrid_recon.x_vec[:, 0] - kgrid_recon.x_vec.min(), kgrid_recon.y_vec[:, 0]),\n", - " p_xy, method='linear'\n", - ")\n", - "query_points = np.stack((kgrid.x - kgrid.x.min(), kgrid.y), axis=-1)\n", - "p_xy_rs = interp_func(query_points)" - ] - }, - { - "cell_type": "markdown", - "id": "b07f65d05feaf902", - "metadata": {}, - "source": [ - "### VISUALIZATION\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30edd081e39aa21b", - "metadata": {}, - "outputs": [], - "source": [ - "cmap = get_color_map()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d02e73401dee274c", - "metadata": {}, - "outputs": [], - "source": [ - "# plot the initial pressure and sensor distribution\n", - "fig, ax = plt.subplots(1, 1)\n", - "im = ax.imshow(p0 + sensor.mask[PML_size:-PML_size, PML_size:-PML_size] * disc_magnitude,\n", - " extent=[kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=-disc_magnitude, vmax=disc_magnitude, cmap=cmap)\n", - "divider = make_axes_locatable(ax)\n", - "cax = divider.append_axes(\"right\", size=\"3%\", pad=\"2%\")\n", - "ax.set_ylabel('x-position [mm]')\n", - "ax.set_xlabel('y-position [mm]')\n", - "fig.colorbar(im, cax=cax)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fc203d6e2a22dec6", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots(1, 1)\n", - "im = ax.imshow(sensor_data, vmin=-1, vmax=1, cmap=cmap, aspect='auto')\n", - "divider = make_axes_locatable(ax)\n", - "cax = divider.append_axes(\"right\", size=\"3%\", pad=\"2%\")\n", - "ax.set_ylabel('Sensor Position')\n", - "ax.set_xlabel('Time Step')\n", - "fig.colorbar(im, cax=cax)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2da3a2a311fd1683", - "metadata": {}, - "outputs": [], - "source": [ - "# plot the reconstructed initial pressure\n", - "fig, ax = plt.subplots(1, 1)\n", - "im = ax.imshow(p_xy_rs,\n", - " extent=[kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=-disc_magnitude, vmax=disc_magnitude, cmap=cmap)\n", - "divider = make_axes_locatable(ax)\n", - "cax = divider.append_axes(\"right\", size=\"3%\", pad=\"2%\")\n", - "ax.set_ylabel('x-position [mm]')\n", - "ax.set_xlabel('y-position [mm]')\n", - "fig.colorbar(im, cax=cax)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f7ae5b0a2f5e147e", - "metadata": {}, - "outputs": [], - "source": [ - "# plot a profile for comparison\n", - "plt.plot(kgrid.y_vec[:, 0] * 1e3, p0[disc_pos[0], :], 'k-', label='Initial Pressure')\n", - "plt.plot(kgrid.y_vec[:, 0] * 1e3, p_xy_rs[disc_pos[0], :], 'r--', label='Reconstructed Pressure')\n", - "plt.xlabel('y-position [mm]')\n", - "plt.ylabel('Pressure')\n", - "plt.legend()\n", - "plt.axis('tight')\n", - "plt.ylim([0, 5.1])\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/pr_2D_TR_line_sensor/README.md b/examples/pr_2D_TR_line_sensor/README.md deleted file mode 100644 index 2c5d15c98..000000000 --- a/examples/pr_2D_TR_line_sensor/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# 2D Time Reversal Reconstruction For A Line Sensor Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb) - -This example demonstrates the use of k-Wave for the time-reversal reconstruction of a two-dimensional photoacoustic wave-field recorded over a linear array of sensor elements. The sensor data is simulated and then time-reversed using [kspaceFirstOrder2D](https://k-wave-python.readthedocs.io/en/latest/kwave.kspaceFirstOrder2D.html). It builds on the [2D FFT Reconstruction For A Line Sensor Example](../pr_2D_TR_line_sensor/). - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_pr_2D_tr_line_sensor.php) or its [implementation](https://github.com/ucl-bug/k-wave/blob/main/k-Wave/examples/example_pr_2D_TR_line_sensor.m). \ No newline at end of file diff --git a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb deleted file mode 100644 index bd4e1d0cb..000000000 --- a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb +++ /dev/null @@ -1,335 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "initial_id", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "!pip install k-wave-python " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4d18dbe95c4a69b0", - "metadata": {}, - "outputs": [], - "source": [ - "from copy import deepcopy\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from mpl_toolkits.axes_grid1 import make_axes_locatable\n", - "from scipy.interpolate import RegularGridInterpolator\n", - "\n", - "from kwave.data import Vector\n", - "from kwave.kgrid import kWaveGrid\n", - "from kwave.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D\n", - "from kwave.kspaceLineRecon import kspaceLineRecon\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.reconstruction.time_reversal import TimeReversal\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.filters import smooth\n", - "from kwave.utils.mapgen import make_disc" - ] - }, - { - "cell_type": "markdown", - "id": "88cb3b08164514b0", - "metadata": {}, - "source": [ - "## 2D Time Reversal Reconstruction For A Line Sensor Example\n", - "\n", - "This example demonstrates the use of k-Wave for the time-reversal\n", - "reconstruction of a two-dimensional photoacoustic wave-field recorded\n", - "over a linear array of sensor elements. The sensor data is simulated and\n", - "then time-reversed using kspaceFirstOrder2D. It builds on the 2D FFT \n", - "Reconstruction For A Line Sensor Example." - ] - }, - { - "cell_type": "markdown", - "id": "76db584c54e13248", - "metadata": {}, - "source": [ - "### SIMULATION" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6eeb7e260b43f43", - "metadata": {}, - "outputs": [], - "source": [ - "# create the computational grid\n", - "PML_size = 20 # size of the PML in grid points\n", - "N = Vector([128, 256]) - 2 * PML_size # number of grid points\n", - "d = Vector([0.1e-3, 0.1e-3]) # grid point spacing [m]\n", - "kgrid = kWaveGrid(N, d)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7f72a8c783169161", - "metadata": {}, - "outputs": [], - "source": [ - "# define the properties of the propagation medium\n", - "medium = kWaveMedium(\n", - " sound_speed=1500,\t# [m/s]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "619c6b4d5a08c7a5", - "metadata": {}, - "outputs": [], - "source": [ - "# create initial pressure distribution using makeDisc\n", - "disc_magnitude = 5 # [Pa]\n", - "disc_pos = Vector([60, 140]) # [grid points]\n", - "disc_radius = 5 # [grid points]\n", - "disc_2 = disc_magnitude * make_disc(N, disc_pos, disc_radius)\n", - "\n", - "disc_pos = Vector([30, 110]) # [grid points]\n", - "disc_radius = 8 # [grid points]\n", - "disc_1 = disc_magnitude * make_disc(N, disc_pos, disc_radius)\n", - "\n", - "# smooth the initial pressure distribution and restore the magnitude\n", - "p0 = smooth(disc_1 + disc_2, restore_max=True)\n", - "\n", - "# assign to the source structure\n", - "source = kSource()\n", - "source.p0 = p0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "59bbb876871f155e", - "metadata": {}, - "outputs": [], - "source": [ - "# define a binary line sensor\n", - "sensor = kSensor()\n", - "sensor.mask = np.zeros(N)\n", - "sensor.mask[0] = 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a23b0c47baf1bde2", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "# create the time array\n", - "kgrid.makeTime(medium.sound_speed)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c85b2aacc4cc6bcc", - "metadata": {}, - "outputs": [], - "source": [ - "# set the input arguments: force the PML to be outside the computational\n", - "# grid; switch off p0 smoothing within kspaceFirstOrder2D\n", - "simulation_options = SimulationOptions(\n", - " pml_inside=False,\n", - " pml_size=PML_size,\n", - " smooth_p0=False,\n", - " save_to_disk=True,\n", - " data_cast=\"single\",\n", - ")\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "db85c9943ed5f91a", - "metadata": {}, - "outputs": [], - "source": [ - "# run the simulation\n", - "sensor_data = kspaceFirstOrder2D(kgrid, source, deepcopy(sensor), medium, simulation_options, execution_options)\n", - "sensor_data = sensor_data['p'].T\n", - "sensor.recorded_pressure = sensor_data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fc22514bed1e2a98", - "metadata": {}, - "outputs": [], - "source": [ - "# remove padding from sensor mask that was added by kspaceFirstOrder2D\n", - "source = kSource()\n", - "\n", - "# create time reversal handler and run reconstruction\n", - "tr = TimeReversal(kgrid, medium, sensor)\n", - "p0_recon = tr(kspaceFirstOrder2D, simulation_options, execution_options)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "431b7bb0ee525b5e", - "metadata": {}, - "outputs": [], - "source": [ - "# repeat the FFT reconstruction for comparison\n", - "p_xy = kspaceLineRecon(sensor_data.T, dy=d[1], dt=kgrid.dt.item(), c=medium.sound_speed.item(), pos_cond=True, interp='linear')\n", - "\n", - "# define a second k-space grid using the dimensions of p_xy\n", - "N_recon = Vector(p_xy.shape)\n", - "d_recon = Vector([kgrid.dt * medium.sound_speed.item(), kgrid.dy])\n", - "kgrid_recon = kWaveGrid(N_recon, d_recon)\n", - "\n", - "# resample p_xy to be the same size as source.p0\n", - "interp_func = RegularGridInterpolator(\n", - " (kgrid_recon.x_vec[:, 0] - kgrid_recon.x_vec.min(), kgrid_recon.y_vec[:, 0]),\n", - " p_xy, method='linear'\n", - ")\n", - "query_points = np.stack((kgrid.x - kgrid.x.min(), kgrid.y), axis=-1)\n", - "p_xy_rs = interp_func(query_points)" - ] - }, - { - "cell_type": "markdown", - "id": "7305f8eba3374df2", - "metadata": {}, - "source": [ - "### VISUALIZATION" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7c8e399f00971c60", - "metadata": {}, - "outputs": [], - "source": [ - "cmap = get_color_map()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b159649563e8767e", - "metadata": {}, - "outputs": [], - "source": [ - "# Plot the initial pressure and sensor distribution\n", - "fig, ax = plt.subplots(1, 1)\n", - "im = ax.imshow(p0 + sensor.mask * disc_magnitude,\n", - " extent=[kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=-disc_magnitude, vmax=disc_magnitude, cmap=cmap)\n", - "divider = make_axes_locatable(ax)\n", - "cax = divider.append_axes(\"right\", size=\"3%\", pad=\"2%\")\n", - "ax.set_ylabel('x-position [mm]')\n", - "ax.set_xlabel('y-position [mm]')\n", - "fig.colorbar(im, cax=cax)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9bc5d6d4b6761d4", - "metadata": {}, - "outputs": [], - "source": [ - "# Plot the reconstructed initial pressure\n", - "fig, ax = plt.subplots(1, 1)\n", - "im = ax.imshow(p0_recon,\n", - " extent=[kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=-disc_magnitude, vmax=disc_magnitude, cmap=cmap)\n", - "divider = make_axes_locatable(ax)\n", - "cax = divider.append_axes(\"right\", size=\"3%\", pad=\"2%\")\n", - "ax.set_ylabel('x-position [mm]')\n", - "ax.set_xlabel('y-position [mm]')\n", - "fig.colorbar(im, cax=cax)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4b70ab1746f0ed88", - "metadata": {}, - "outputs": [], - "source": [ - "# Apply a positivity condition\n", - "p0_recon[p0_recon < 0] = 0\n", - "\n", - "# Plot the reconstructed initial pressure with positivity condition\n", - "fig, ax = plt.subplots(1, 1)\n", - "im = ax.imshow(p0_recon,\n", - " extent=[kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=-disc_magnitude, vmax=disc_magnitude, cmap=cmap)\n", - "divider = make_axes_locatable(ax)\n", - "cax = divider.append_axes(\"right\", size=\"3%\", pad=\"2%\")\n", - "ax.set_ylabel('x-position [mm]')\n", - "ax.set_xlabel('y-position [mm]')\n", - "fig.colorbar(im, cax=cax)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a95d4b11c4111e59", - "metadata": {}, - "outputs": [], - "source": [ - "# Plot a profile for comparison\n", - "plt.plot(kgrid.y_vec[:, 0] * 1e3, p0[disc_pos[0], :], 'k-', label='Initial Pressure')\n", - "plt.plot(kgrid.y_vec[:, 0] * 1e3, p_xy_rs[disc_pos[0], :], 'r--', label='FFT Reconstruction')\n", - "plt.plot(kgrid.y_vec[:, 0] * 1e3, p0_recon[disc_pos[0], :], 'b:', label='Time Reversal')\n", - "plt.xlabel('y-position [mm]')\n", - "plt.ylabel('Pressure')\n", - "plt.legend()\n", - "plt.axis('tight')\n", - "plt.ylim([0, 5.1])\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": 5 -} diff --git a/examples/pr_3D_FFT_planar_sensor/README.md b/examples/pr_3D_FFT_planar_sensor/README.md deleted file mode 100644 index a140626cf..000000000 --- a/examples/pr_3D_FFT_planar_sensor/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# 3D FFT Reconstruction For A Planar Sensor Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb) - -This example demonstrates the use of k-Wave for the reconstruction of a three-dimensional photoacoustic wave-field recorded over a planar array of sensor elements. The sensor data is simulated using [kspaceFirstOrder3D](https://k-wave-python.readthedocs.io/en/latest/kwave.kspaceFirstOrder3D.html) and reconstructed using kspacePlaneRecon. It builds on the Simulations In Three Dimensions and [2D FFT Reconstruction For A Line Sensor](../pr_3D_FFT_planar_sensor/) examples. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_pr_3D_fft_planar_sensor.php) or its [implementation](https://github.com/ucl-bug/k-wave/blob/main/k-Wave/examples/example_pr_3D_FFT_planar_sensor.m). \ No newline at end of file diff --git a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb b/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb deleted file mode 100644 index fc4efd30e..000000000 --- a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb +++ /dev/null @@ -1,313 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "initial_id", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "!pip install k-wave-python " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "611488037b162a21", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from scipy.interpolate import RegularGridInterpolator\n", - "\n", - "from kwave.data import Vector\n", - "from kwave.kgrid import kWaveGrid\n", - "from kwave.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.kspacePlaneRecon import kspacePlaneRecon\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.filters import smooth\n", - "from kwave.utils.mapgen import make_ball" - ] - }, - { - "cell_type": "markdown", - "id": "d7d832f9f7f7caf3", - "metadata": {}, - "source": [ - "## 3D FFT Reconstruction For A Planar Sensor Example\n", - "\n", - "This example demonstrates the use of k-Wave for the reconstruction of a\n", - "three-dimensional photoacoustic wave-field recorded over a planar array\n", - "of sensor elements. The sensor data is simulated using kspaceFirstOrder3D\n", - "and reconstructed using kspacePlaneRecon. It builds on the Simulations In\n", - "Three Dimensions and 2D FFT Reconstruction For A Line Sensor examples." - ] - }, - { - "cell_type": "markdown", - "id": "6e5153f4bb9a8e8b", - "metadata": {}, - "source": [ - "### SIMULATION" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ef3d745e587ea7d", - "metadata": {}, - "outputs": [], - "source": [ - "scale = 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "62a1b78d509423b1", - "metadata": {}, - "outputs": [], - "source": [ - "# create the computational grid\n", - "PML_size = 10 # size of the PML in grid points\n", - "N = Vector([32, 64, 64]) * scale - 2 * PML_size # number of grid points\n", - "d = Vector([0.2e-3, 0.2e-3, 0.2e-3]) / scale # grid point spacing [m]\n", - "kgrid = kWaveGrid(N, d)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9606aea53baa8a1", - "metadata": {}, - "outputs": [], - "source": [ - "# define the properties of the propagation medium\n", - "medium = kWaveMedium(sound_speed=1500) # [m/s]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a5ab847f521ec9b9", - "metadata": {}, - "outputs": [], - "source": [ - "# create initial pressure distribution using makeBall\n", - "ball_magnitude = 10 # [Pa]\n", - "ball_radius = 3 * scale # [grid points]\n", - "p0 = ball_magnitude * make_ball(N, N / 2, ball_radius)\n", - "p0 = smooth(p0, restore_max=True)\n", - "\n", - "source = kSource()\n", - "source.p0 = p0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "80a91cfee6c4b79f", - "metadata": {}, - "outputs": [], - "source": [ - "# define a binary planar sensor\n", - "sensor = kSensor()\n", - "sensor_mask = np.zeros(N)\n", - "sensor_mask[0] = 1\n", - "sensor.mask = sensor_mask" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "39e4252f2d55caae", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "# create the time array\n", - "kgrid.makeTime(medium.sound_speed)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d036c62f78598f2d", - "metadata": {}, - "outputs": [], - "source": [ - "# set the input arguments\n", - "simulation_options = SimulationOptions(\n", - " save_to_disk=True,\n", - " pml_size=PML_size,\n", - " pml_inside=False,\n", - " smooth_p0=False,\n", - " data_cast='single'\n", - ")\n", - "\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e74cd39b11526d81", - "metadata": {}, - "outputs": [], - "source": [ - "# run the simulation\n", - "sensor_data = kspaceFirstOrder3D(kgrid, source, sensor, medium, simulation_options, execution_options)\n", - "sensor_data = sensor_data['p'].T\n", - "\n", - "# reshape sensor data to y, z, t\n", - "sensor_data_rs = sensor_data.reshape(N[1], N[2], kgrid.Nt)\n", - "\n", - "# reconstruct the initial pressure\n", - "p_xyz = kspacePlaneRecon(sensor_data_rs, kgrid.dy, kgrid.dz, kgrid.dt.item(),\n", - " medium.sound_speed.item(), data_order='yzt', pos_cond=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c0d05511c962ede8", - "metadata": {}, - "outputs": [], - "source": [ - "# define a k-space grid using the dimensions of p_xyz\n", - "N_recon = Vector(p_xyz.shape)\n", - "d_recon = Vector([kgrid.dt.item() * medium.sound_speed.item(), kgrid.dy, kgrid.dz])\n", - "kgrid_recon = kWaveGrid(N_recon, d_recon)\n", - "\n", - "# define a k-space grid with the same z-spacing as p0\n", - "kgrid_interp = kWaveGrid(N, d)\n", - "\n", - "# resample the p_xyz to be the same size as p0\n", - "interp_func = RegularGridInterpolator(\n", - " (kgrid_recon.x_vec[:, 0] - kgrid_recon.x_vec[:, 0].min(),\n", - " kgrid_recon.y_vec[:, 0] - kgrid_recon.y_vec[:, 0].min(),\n", - " kgrid_recon.z_vec[:, 0] - kgrid_recon.z_vec[:, 0].min()),\n", - " p_xyz, method='linear'\n", - ")\n", - "query_points = np.stack((kgrid_interp.x - kgrid_interp.x.min(),\n", - " kgrid_interp.y - kgrid_interp.y.min(),\n", - " kgrid_interp.z - kgrid_interp.z.min()),\n", - " axis=-1)\n", - "p_xyz_rs = interp_func(query_points)" - ] - }, - { - "cell_type": "markdown", - "id": "c63150dfb5bc2f2f", - "metadata": {}, - "source": [ - "### VISUALIZATION" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d612fbaa36e986a9", - "metadata": {}, - "outputs": [], - "source": [ - "# plot the initial pressure and sensor surface in voxel form\n", - "# from kwave.utils.plot import voxel_plot\n", - "# voxel_plot(np.single((p0 + sensor_mask) > 0)) # todo: needs unsmoothed po + plot not working" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2db2c76fa8f0b234", - "metadata": {}, - "outputs": [], - "source": [ - "# plot the initial pressure\n", - "plot_scale = [-10, 10]\n", - "fig, axs = plt.subplots(2, 2)\n", - "axs[0, 0].imshow(p0[:, :, N[2] // 2],\n", - " extent=[kgrid_interp.y_vec.min() * 1e3, kgrid_interp.y_vec.max() * 1e3,\n", - " kgrid_interp.x_vec.max() * 1e3, kgrid_interp.x_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=get_color_map())\n", - "axs[0, 0].set_title('x-y plane')\n", - "\n", - "axs[0, 1].imshow(p0[:, N[1] // 2, :],\n", - " extent=[kgrid_interp.z_vec.min() * 1e3, kgrid_interp.z_vec.max() * 1e3,\n", - " kgrid_interp.x_vec.max() * 1e3, kgrid_interp.x_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=get_color_map())\n", - "axs[0, 1].set_title('x-z plane')\n", - "\n", - "axs[1, 0].imshow(p0[N[0] // 2, :, :],\n", - " extent=[kgrid_interp.z_vec.min() * 1e3, kgrid_interp.z_vec.max() * 1e3,\n", - " kgrid_interp.y_vec.max() * 1e3, kgrid_interp.y_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=get_color_map())\n", - "axs[1, 0].set_title('y-z plane')\n", - "\n", - "axs[1, 1].axis('off')\n", - "axs[1, 1].set_title('(All axes in mm)')\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d5f54008217d6532", - "metadata": {}, - "outputs": [], - "source": [ - "# plot the reconstructed initial pressure\n", - "fig, axs = plt.subplots(2, 2)\n", - "axs[0, 0].imshow(p_xyz_rs[:, :, N[2] // 2],\n", - " extent=[kgrid_interp.y_vec.min() * 1e3, kgrid_interp.y_vec.max() * 1e3,\n", - " kgrid_interp.x_vec.max() * 1e3, kgrid_interp.x_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=get_color_map())\n", - "axs[0, 0].set_title('x-y plane')\n", - "\n", - "axs[0, 1].imshow(p_xyz_rs[:, N[1] // 2, :],\n", - " extent=[kgrid_interp.z_vec.min() * 1e3, kgrid_interp.z_vec.max() * 1e3,\n", - " kgrid_interp.x_vec.max() * 1e3, kgrid_interp.x_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=get_color_map())\n", - "axs[0, 1].set_title('x-z plane')\n", - "\n", - "axs[1, 0].imshow(p_xyz_rs[N[0] // 2, :, :],\n", - " extent=[kgrid_interp.z_vec.min() * 1e3, kgrid_interp.z_vec.max() * 1e3,\n", - " kgrid_interp.y_vec.max() * 1e3, kgrid_interp.y_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=get_color_map())\n", - "axs[1, 0].set_title('y-z plane')\n", - "\n", - "axs[1, 1].axis('off')\n", - "axs[1, 1].set_title('(All axes in mm)')\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/pr_3D_TR_planar_sensor/README.md b/examples/pr_3D_TR_planar_sensor/README.md deleted file mode 100644 index 6935494fd..000000000 --- a/examples/pr_3D_TR_planar_sensor/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# 3D Time Reversal Reconstruction For A Planar Sensor Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb) - -This example demonstrates the use of k-Wave for the reconstruction of a three-dimensional photoacoustic wave-field recorded over a planar array of sensor elements. The sensor data is simulated and then time-reversed using [kspaceFirstOrder3D](https://k-wave-python.readthedocs.io/en/latest/kwave.kspaceFirstOrder3D.html). It builds on the [3D FFT Reconstruction For A Planar Sensor](../pr_3D_FFT_planar_sensor/) and [2D Time Reversal Reconstruction For A Line Sensor](../pr_2D_TR_line_sensor) examples. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_pr_3D_tr_planar_sensor.php) or its [implementation](https://github.com/ucl-bug/k-wave/blob/main/k-Wave/examples/example_pr_3D_TR_planar_sensor.m). \ No newline at end of file diff --git a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb deleted file mode 100644 index 38a8684ec..000000000 --- a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb +++ /dev/null @@ -1,307 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "initial_id", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "!pip install k-wave-python " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6a6cdd4e64365d3e", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.filters import smooth\n", - "from kwave.utils.mapgen import make_ball" - ] - }, - { - "cell_type": "markdown", - "id": "e7d1f7d7d6481a2c", - "metadata": {}, - "source": [ - "## 3D Time Reversal Reconstruction For A Planar Sensor Example\n", - "\n", - "This example demonstrates the use of k-Wave for the reconstruction of a three-dimensional photoacoustic wave-field recorded over a planar array of sensor elements. The sensor data is simulated and then time-reversed using kspaceFirstOrder3D. It builds on the 3D FFT Reconstruction For A Planar Sensor and 2D Time Reversal Reconstruction For A Line Sensor examples." - ] - }, - { - "cell_type": "markdown", - "id": "4a805d6488afc4b7", - "metadata": {}, - "source": [ - "### SIMULATION" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8f9b1f3d5bef716", - "metadata": {}, - "outputs": [], - "source": [ - "# change scale to 2 to reproduce the higher resolution figures used in the\n", - "# help file\n", - "scale = 1" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "249a1bd033f3abe7", - "metadata": {}, - "outputs": [], - "source": [ - "# create the computational grid\n", - "PML_size = 10 # size of the PML in grid points\n", - "N = Vector([32, 64, 64]) * scale - 2 * PML_size # number of grid points\n", - "d = Vector([0.2e-3, 0.2e-3, 0.2e-3]) / scale # grid point spacing [m]\n", - "kgrid = kWaveGrid(N, d)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "25d647c865e569b", - "metadata": {}, - "outputs": [], - "source": [ - "# define the properties of the propagation medium\n", - "medium = kWaveMedium(sound_speed=1500) # [m/s]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb75ff29974130cf", - "metadata": {}, - "outputs": [], - "source": [ - "# create initial pressure distribution using makeBall\n", - "ball_magnitude = 10 # [Pa]\n", - "ball_radius = 3 * scale # [grid points]\n", - "p0 = ball_magnitude * make_ball(N, N / 2, ball_radius)\n", - "\n", - "# smooth the initial pressure distribution and restore the magnitude\n", - "p0 = smooth(p0, True)\n", - "\n", - "# assign to the source structure\n", - "source = kSource()\n", - "source.p0 = p0" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9eedddfe4204cea5", - "metadata": {}, - "outputs": [], - "source": [ - "# define a binary planar sensor\n", - "sensor = kSensor()\n", - "sensor.mask = np.zeros(N, dtype=bool)\n", - "sensor.mask[0, :, :] = 1\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ea0924151a96a6f", - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "# create the time array\n", - "kgrid.makeTime(medium.sound_speed)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7ad7415e71716b05", - "metadata": {}, - "outputs": [], - "source": [ - "# set the input arguments\n", - "simulation_options = SimulationOptions(\n", - " pml_inside=False,\n", - " pml_size=PML_size,\n", - " smooth_p0=False,\n", - " save_to_disk=True,\n", - " data_cast=\"single\",\n", - ")\n", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True)\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "192d9eeefd209077", - "metadata": {}, - "outputs": [], - "source": [ - "# run the simulation\n", - "from copy import deepcopy\n", - "\n", - "sensor_data = kspaceFirstOrder3D(kgrid, source, deepcopy(sensor), medium, simulation_options, execution_options)\n", - "sensor_data= sensor_data[\"p\"].T" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "790676732e5f078c", - "metadata": {}, - "outputs": [], - "source": [ - "# reset the initial pressure\n", - "from kwave.reconstruction.time_reversal import TimeReversal\n", - "\n", - "source = kSource()\n", - "sensor = kSensor()\n", - "sensor.mask = np.zeros(N)\n", - "sensor.mask[0] = 1\n", - "\n", - "# assign the time reversal data\n", - "sensor.recorded_pressure = sensor_data\n", - "\n", - "# run the time-reversal reconstruction\n", - "tr = TimeReversal(kgrid, medium, sensor)\n", - "p0_recon = tr(kspaceFirstOrder3D, simulation_options, execution_options)\n" - ] - }, - { - "cell_type": "markdown", - "id": "7bc3a5e3b2f4571", - "metadata": {}, - "source": [ - "### VISUALIZATION" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7f16ca4a29e4b671", - "metadata": {}, - "outputs": [], - "source": [ - "cmap = get_color_map()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ac432a6b8e284bda", - "metadata": {}, - "outputs": [], - "source": [ - "plot_scale = [-10, 10]\n", - "\n", - "# plot the initial pressure\n", - "fig, axs = plt.subplots(2, 2)\n", - "axs[0, 0].imshow(p0[:, :, N[2] // 2],\n", - " extent=[kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=cmap)\n", - "axs[0, 0].set_title('x-y plane')\n", - "axs[0, 0].axis('image')\n", - "\n", - "axs[0, 1].imshow(p0[:, N[1] // 2, :],\n", - " extent=[kgrid.z_vec.min() * 1e3, kgrid.z_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=cmap)\n", - "axs[0, 1].set_title('x-z plane')\n", - "axs[0, 1].axis('image')\n", - "\n", - "axs[1, 0].imshow(p0[N[0] // 2, :, :],\n", - " extent=[kgrid.z_vec.min() * 1e3, kgrid.z_vec.max() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.y_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=cmap)\n", - "axs[1, 0].set_title('y-z plane')\n", - "axs[1, 0].axis('image')\n", - "\n", - "axs[1, 1].axis('off')\n", - "axs[1, 1].set_title('(All axes in mm)')\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "8616da04", - "metadata": {}, - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e946817f00b55e9e", - "metadata": {}, - "outputs": [], - "source": [ - "# plot the reconstructed initial pressure\n", - "fig, axs = plt.subplots(2, 2)\n", - "axs[0, 0].imshow(p0_recon[:, :, N[2] // 2],\n", - " extent=[kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=cmap)\n", - "axs[0, 0].set_title('x-y plane')\n", - "axs[0, 0].axis('image')\n", - "\n", - "axs[0, 1].imshow(p0_recon[:, N[1] // 2, :],\n", - " extent=[kgrid.z_vec.min() * 1e3, kgrid.z_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=cmap)\n", - "axs[0, 1].set_title('x-z plane')\n", - "axs[0, 1].axis('image')\n", - "\n", - "axs[1, 0].imshow(p0_recon[N[0] // 2, :, :],\n", - " extent=[kgrid.z_vec.min() * 1e3, kgrid.z_vec.max() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.y_vec.min() * 1e3],\n", - " vmin=plot_scale[0], vmax=plot_scale[1], cmap=cmap)\n", - "axs[1, 0].set_title('y-z plane')\n", - "axs[1, 0].axis('image')\n", - "\n", - "axs[1, 1].axis('off')\n", - "axs[1, 1].set_title('(All axes in mm)')\n", - "\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": 5 -} diff --git a/examples/sd_directivity_modelling_2D/README.md b/examples/sd_directivity_modelling_2D/README.md deleted file mode 100644 index 67cebfd21..000000000 --- a/examples/sd_directivity_modelling_2D/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Modelling Sensor Directivity In 2D Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb) - -This example demonstrates how the sensitivity of a large single element detector varies with the angular position of a point-like source. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_sd_directivity_modelling_2D.php). diff --git a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb b/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb deleted file mode 100644 index 63ccc5167..000000000 --- a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb +++ /dev/null @@ -1,244 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%pip install git+https://github.com/waltsims/k-wave-python " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Modelling Sensor Directivity In 2D Example\n", - "\n", - "This example demonstrates how the sensitivity of a large single element detector varies with the angular position of a point-like source." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "import os\n", - "from copy import deepcopy\n", - "from tempfile import gettempdir\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.colormap import get_color_map\n", - "from kwave.utils.conversion import cart2grid\n", - "from kwave.utils.data import scale_SI\n", - "from kwave.utils.filters import filter_time_series\n", - "from kwave.utils.mapgen import make_cart_circle\n", - "from kwave.utils.matlab import ind2sub, matlab_find, unflatten_matlab_mask" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Create the computational grid and medium" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grid_size_points = Vector([128, 128]) # [grid points]\n", - "grid_size_meters = Vector([50e-3, 50e-3]) # [m]\n", - "grid_spacing_meters = grid_size_meters / grid_size_points # [m]\n", - "kgrid = kWaveGrid(grid_size_points, grid_spacing_meters)\n", - "\n", - "# define the properties of the propagation medium\n", - "medium = kWaveMedium(sound_speed=1500)\n", - "\n", - "# define the array of time points [s]\n", - "Nt = 350\n", - "dt = 7e-8 # [s]\n", - "kgrid.setTime(Nt, dt)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define a large area detector" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sz = 20 # [grid points]\n", - "sensor_mask = np.zeros(grid_size_points)\n", - "sensor_mask[grid_size_points.x // 2, (grid_size_points.y // 2 - sz // 2) : (grid_size_points.y // 2 + sz // 2) + 1] = 1\n", - "sensor = kSensor(sensor_mask)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define a time varying sinusoidal source" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# define equally spaced point sources lying on a circle centred at the\n", - "# centre of the detector face\n", - "radius = 30 # [grid points]\n", - "points = 11\n", - "circle = make_cart_circle(radius * grid_spacing_meters.x, points, Vector([0, 0]), np.pi)\n", - "\n", - "# find the binary sensor mask most closely corresponding to the Cartesian\n", - "# coordinates from makeCartCircle\n", - "circle, _, _ = cart2grid(kgrid, circle)\n", - "\n", - "# find the indices of the sources in the binary source mask\n", - "source_positions = matlab_find(circle, val=1, mode=\"eq\")\n", - "\n", - "source = kSource()\n", - "source_freq = 0.25e6 # [Hz]\n", - "source_mag = 1 # [Pa]\n", - "source.p = source_mag * np.sin(2 * np.pi * source_freq * kgrid.t_array)\n", - "\n", - "# filter the source to remove high frequencies not supported by the grid\n", - "source.p = filter_time_series(kgrid, medium, source.p)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define simulation parameters and run simulations" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# pre-allocate array for storing the output time series\n", - "single_element_data = np.zeros((Nt, points)) # noqa: F841\n", - "\n", - "# run a simulation for each of these sources to see the effect that the\n", - "# angle from the detector has on the measured signal\n", - "for source_loop in range(points):\n", - " # select a point source\n", - " source.p_mask = np.zeros(grid_size_points)\n", - " source.p_mask[unflatten_matlab_mask(source.p_mask, source_positions[source_loop] - 1)] = 1\n", - "\n", - " # run the simulation\n", - "\n", - " input_filename = f\"example_input_{source_loop + 1}_input.h5\"\n", - " pathname = gettempdir()\n", - " input_file_full_path = os.path.join(pathname, input_filename)\n", - " simulation_options = SimulationOptions(save_to_disk=True, input_filename=input_filename, data_path=pathname)\n", - " # run the simulation\n", - " sensor_data = kspaceFirstOrder2DC(\n", - " medium=medium,\n", - " kgrid=kgrid,\n", - " source=deepcopy(source),\n", - " sensor=deepcopy(sensor),\n", - " simulation_options=simulation_options,\n", - " execution_options=SimulationExecutionOptions(),\n", - " )\n", - " single_element_data[:, source_loop] = sensor_data['p'].sum(axis=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualize recorded data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure()\n", - "plt.imshow(circle + sensor.mask, \n", - " extent=[\n", - " kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, \n", - " kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3], vmin=-1, vmax=1, cmap=get_color_map())\n", - "plt.ylabel('x-position [mm]')\n", - "plt.xlabel('y-position [mm]')\n", - "plt.axis('image')\n", - "\n", - "_, t_scale, t_prefix, _ = scale_SI(kgrid.t_array[-1])\n", - "\n", - "# Plot the time series recorded for each of the sources\n", - "plt.figure()\n", - "# Loop through each source and plot individually\n", - "for i in range(single_element_data.shape[1]):\n", - " plt.plot((kgrid.t_array * t_scale).squeeze(), single_element_data[:, i], label=f'Source {i+1}')\n", - "\n", - "plt.xlabel(f'Time [{t_prefix}s]')\n", - "plt.ylabel('Pressure [au]')\n", - "plt.title('Time Series For Each Source Direction')\n", - "\n", - "\n", - "# Calculate angle between source and center of detector face\n", - "angles = []\n", - "for source_position in source_positions:\n", - " x, y = ind2sub(kgrid.y.shape, source_position)\n", - " angles.append(np.arctan(kgrid.y[x, y] / kgrid.x[x, y]))\n", - "\n", - "# Plot the maximum amplitudes for each of the sources\n", - "plt.figure()\n", - "plt.plot(angles, np.max(single_element_data[200:350, :], axis=0), 'o', mfc='none')\n", - "plt.xlabel('Angle Between Source and Centre of Detector Face [rad]')\n", - "plt.ylabel('Maximum Detected Pressure [au]')\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "env_kwave", - "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 -} diff --git a/examples/sd_focussed_detector_2D/README.md b/examples/sd_focussed_detector_2D/README.md deleted file mode 100644 index 3d78d3b76..000000000 --- a/examples/sd_focussed_detector_2D/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Focussed Detector In 2D Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb) - -This example shows how k-Wave-python can be used to model the output of a focussed semicircular detector where the directionality arises from spatially averaging across the detector surface. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_sd_focussed_detector_2D.php). diff --git a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb b/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb deleted file mode 100644 index 6c54e5cfe..000000000 --- a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb +++ /dev/null @@ -1,207 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%pip install git+https://github.com/waltsims/k-wave-python " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Focussed Detector In 2D Example\n", - "This example shows how k-Wave-python can be used to model the output of a focused semicircular detector, where the directionality arises from spatially averaging across the detector surface. Unlike the original example in k-Wave, this example does not visualize the simulation, as this functionality is not intrinsically supported by the accelerated binaries." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "import os\n", - "from copy import deepcopy\n", - "from tempfile import gettempdir\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder2D import kspaceFirstOrder2DC\n", - "from kwave.ktransducer import kSensor\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.data import scale_SI\n", - "from kwave.utils.mapgen import make_circle, make_disc\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create the computational grid\n", - "grid_size = Vector([180, 180]) # [grid points]\n", - "grid_spacing = Vector([0.1e-3, 0.1e-3]) # [m]\n", - "kgrid = kWaveGrid(grid_size, grid_spacing)\n", - "\n", - "# define the properties of the propagation medium\n", - "medium = kWaveMedium(sound_speed=1500)\n", - "\n", - "# define a sensor as part of a circle centred on the grid\n", - "sensor_radius = 65 # [grid points]\n", - "arc_angle = np.pi # [rad]\n", - "sensor_mask = make_circle(grid_size, grid_size // 2 + 1, sensor_radius, arc_angle)\n", - "sensor = kSensor(sensor_mask)\n", - "\n", - "# define the array of temporal points\n", - "t_end = 11e-6 # [s]\n", - "_ = kgrid.makeTime(medium.sound_speed, t_end=t_end)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define simulation parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "input_filename = \"example_sd_focused_2d_input.h5\"\n", - "pathname = gettempdir()\n", - "input_file_full_path = os.path.join(pathname, input_filename)\n", - "simulation_options = SimulationOptions(save_to_disk=True, input_filename=input_filename, data_path=pathname)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run simulation with first source" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# place a disc-shaped source near the focus of the detector\n", - "source = kSource()\n", - "source.p0 = 2 * make_disc(grid_size, grid_size / 2, 4)\n", - "\n", - "# run the simulation\n", - "sensor_data1 = kspaceFirstOrder2DC(\n", - " medium=medium,\n", - " kgrid=kgrid,\n", - " source=deepcopy(source),\n", - " sensor=sensor,\n", - " simulation_options=simulation_options,\n", - " execution_options=SimulationExecutionOptions(),\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sensor_data1['p'].shape\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run simulation with second source" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# place a disc-shaped source horizontally shifted from the focus of the detector\n", - "source.p0 = 2 * make_disc(grid_size, grid_size / 2 + [0, 20], 4)\n", - "\n", - "sensor_data2 = kspaceFirstOrder2DC(\n", - " medium=medium,\n", - " kgrid=kgrid,\n", - " source=deepcopy(source),\n", - " sensor=sensor,\n", - " simulation_options=simulation_options,\n", - " execution_options=SimulationExecutionOptions(),\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualize recorded data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "sensor_output1 = np.sum(sensor_data1['p'], axis=1) / np.sum(sensor.mask)\n", - "sensor_output2 = np.sum(sensor_data2['p'], axis=1) / np.sum(sensor.mask)\n", - "\n", - "t_sc, t_scale, t_prefix, _ = scale_SI(t_end)\n", - "t_array = kgrid.t_array.squeeze() * t_scale\n", - "\n", - "plt.plot(t_array, sensor_output1, 'k')\n", - "plt.plot(t_array, sensor_output2, 'r')\n", - "\n", - "plt.xlabel('Time [' + t_prefix + 's]')\n", - "plt.ylabel('Average Pressure Measured Over Detector [au]')\n", - "plt.legend([\n", - " f\"Source on focus, sum(output^2) = {round(np.sum(sensor_output1**2) * 100) / 100}\",\n", - " f\"Source off focus, sum(output^2) = {round(np.sum(sensor_output2**2) * 100) / 100}\"\n", - "])\n", - "\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "env_kwave", - "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 -} diff --git a/examples/sd_focussed_detector_3D/README.md b/examples/sd_focussed_detector_3D/README.md deleted file mode 100644 index de30d13e3..000000000 --- a/examples/sd_focussed_detector_3D/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Focussed Detector In 3D Example - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb) - -This example shows how k-Wave can be used to model the output of a focussed bowl detector where the directionality arises from spatially averaging across the detector surface. - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_sd_focussed_detector_3D.php). diff --git a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb b/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb deleted file mode 100644 index bf427846e..000000000 --- a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb +++ /dev/null @@ -1,275 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%pip install git+https://github.com/waltsims/k-wave-python " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Focussed Detector In 3D Example\n", - "This example shows how k-Wave can be used to model the output of a focussed bowl detector where the directionality arises from spatially averaging across the detector surface." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "import os\n", - "from copy import deepcopy\n", - "from tempfile import gettempdir\n", - "\n", - "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.kmedium import kWaveMedium\n", - "from kwave.ksource import kSource\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.ktransducer import kSensor\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.utils.data import scale_SI\n", - "from kwave.utils.filters import filter_time_series\n", - "from kwave.utils.mapgen import make_bowl" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create the computational grid\n", - "grid_size = Vector([64, 64, 64]) # [grid points]\n", - "grid_spacing_single = 100e-3 / grid_size.x\n", - "grid_spacing = Vector([grid_spacing_single, grid_spacing_single, grid_spacing_single]) # [m]\n", - "kgrid = kWaveGrid(grid_size, grid_spacing)\n", - "\n", - "# define the properties of the propagation medium\n", - "medium = kWaveMedium(sound_speed=1500)\n", - "\n", - "# define the array of temporal points\n", - "_ = kgrid.makeTime(medium.sound_speed)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define simulation parameters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "input_filename = \"example_sd_focused_3d_input.h5\"\n", - "pathname = gettempdir()\n", - "input_file_full_path = os.path.join(pathname, input_filename)\n", - "simulation_options = SimulationOptions(\n", - " save_to_disk=True, \n", - " input_filename=input_filename, \n", - " data_path=pathname, \n", - " pml_size=10, \n", - " data_cast='single'\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Define a concave sensor" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# create a concave sensor\n", - "sphere_offset = 10\n", - "diameter = grid_size.x / 2 + 1\n", - "radius = grid_size.x / 2\n", - "bowl_pos = Vector([1 + sphere_offset, grid_size.y / 2, grid_size.z / 2])\n", - "focus_pos = grid_size / 2\n", - "\n", - "sensor_mask = make_bowl(grid_size, bowl_pos, radius, diameter, focus_pos)\n", - "sensor = kSensor(sensor_mask)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run simulation with first source" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# define a time varying sinusoidal source\n", - "source = kSource()\n", - "\n", - "source_freq = 0.25e6 # [Hz]\n", - "source_mag = 1 # [Pa]\n", - "source.p = source_mag * np.sin(2 * np.pi * source_freq * kgrid.t_array)\n", - "source.p = filter_time_series(kgrid, medium, source.p)\n", - "\n", - "# place the first point source near the focus of the detector\n", - "source1 = np.zeros(grid_size)\n", - "source1[int(sphere_offset + radius), grid_size.y // 2 - 1, grid_size.z // 2 - 1] = 1\n", - "\n", - "# run the first simulation\n", - "source.p_mask = source1\n", - "\n", - "sensor_data1 = kspaceFirstOrder3D(\n", - " medium=medium,\n", - " kgrid=kgrid,\n", - " source=deepcopy(source),\n", - " sensor=deepcopy(sensor),\n", - " simulation_options=simulation_options,\n", - " execution_options=SimulationExecutionOptions(is_gpu_simulation=False),\n", - ")\n", - "\n", - "# average the data recorded at each grid point to simulate the measured signal from a single element focused detector\n", - "sensor_data1 = np.sum(sensor_data1['p'], axis=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Run simulation with second source" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# place the second point source off axis\n", - "source2 = np.zeros(grid_size)\n", - "source2[int(1 + sphere_offset + radius), grid_size.y // 2 + 5, grid_size.z // 2 + 5] = 1\n", - "\n", - "\n", - "# run the second simulation\n", - "source.p_mask = source2\n", - "sensor_data2 = kspaceFirstOrder3D(\n", - " medium=medium,\n", - " kgrid=kgrid,\n", - " source=source,\n", - " sensor=sensor,\n", - " simulation_options=simulation_options,\n", - " execution_options=SimulationExecutionOptions(is_gpu_simulation=False),\n", - ")\n", - "\n", - "# average the data recorded at each grid point to simulate the measured signal from a single element focused detector\n", - "sensor_data2 = np.sum(sensor_data2['p'], axis=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualize detector and sources" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Combine arrays as in MATLAB: sensor.mask + source1 + source2\n", - "combined_array = sensor_mask + source1 + source2\n", - "\n", - "# Find the indices of non-zero elements\n", - "x, y, z = np.nonzero(combined_array)\n", - "\n", - "# Enable interactive mode\n", - "plt.ion()\n", - "\n", - "# Create an interactive 3D plot with matplotlib\n", - "fig = plt.figure(figsize=(8, 6))\n", - "ax = fig.add_subplot(111, projection='3d')\n", - "sc = ax.scatter(x, y, z, c='blue', marker='s', s=100, depthshade=True) # 's' for square-like markers\n", - "\n", - "# Set the view angle to mimic MATLAB's `view([130, 40])`\n", - "ax.view_init(elev=40, azim=130)\n", - "\n", - "# Customize the axes\n", - "ax.set_xlabel('X')\n", - "ax.set_ylabel('Y')\n", - "ax.set_zlabel('Z')\n", - "ax.set_title('Interactive 3D Voxel Plot')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualize recorded data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "t_sc, t_scale, t_prefix, _ = scale_SI(kgrid.t_array[-1])\n", - "t_array = kgrid.t_array.squeeze() * t_scale\n", - "\n", - "plt.figure()\n", - "plt.plot(t_array, sensor_data1)\n", - "plt.plot(t_array, sensor_data2, 'r')\n", - "\n", - "plt.xlabel('Time [' + t_prefix + 's]')\n", - "plt.ylabel('Average Pressure Measured By Focussed Detector [Pa]')\n", - "plt.legend(['Source on axis', 'Source off axis'])\n", - "\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "env_kwave", - "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 -} diff --git a/examples/us_beam_patterns/README.md b/examples/us_beam_patterns/README.md deleted file mode 100644 index b66238c3f..000000000 --- a/examples/us_beam_patterns/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Simulating Ultrasound Beam Patterns Example - - [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/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). \ No newline at end of file diff --git a/examples/us_beam_patterns/us_beam_patterns.ipynb b/examples/us_beam_patterns/us_beam_patterns.ipynb deleted file mode 100644 index c267971cb..000000000 --- a/examples/us_beam_patterns/us_beam_patterns.ipynb +++ /dev/null @@ -1,345 +0,0 @@ -{ - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.ktransducer import NotATransducer, kWaveTransducerSimple\n", - "from kwave.kWaveSimulation import SimulationOptions\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.utils.dotdictionary import dotdict\n", - "from kwave.utils.filters import spect\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 -} diff --git a/examples/us_bmode_linear_transducer/README.md b/examples/us_bmode_linear_transducer/README.md deleted file mode 100644 index e19a651bd..000000000 --- a/examples/us_bmode_linear_transducer/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Simulating B-mode Ultrasound Images Example - - [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb) - -This example illustrates how k-wave-python can be used for the simulation of B-mode ultrasound images (including tissue harmonic imaging) analogous to those produced by a modern diagnostic ultrasound scanner. - - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_us_bmode_linear_transducer.php). \ No newline at end of file diff --git a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb b/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb deleted file mode 100644 index 9cfbbfe47..000000000 --- a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb +++ /dev/null @@ -1,478 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import importlib.util\n", - "import os\n", - "\n", - "\n", - "def is_running_in_colab()->bool:\n", - " return 'COLAB_RELEASE_TAG' in os.environ\n", - "\n", - "def is_library_installed(library_name):\n", - " spec = importlib.util.find_spec(library_name)\n", - " return spec is not None\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "if not is_library_installed('kwave'):\n", - " %pip install k-wave-python\n", - "\n", - "if is_running_in_colab():\n", - " !wget https://raw.githubusercontent.com/waltsims/k-wave-python/master/examples/us_bmode_linear_transducer/example_utils.py" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import scipy.io\n", - "from example_utils import download_if_does_not_exist\n", - "\n", - "from kwave.data import Vector\n", - "from kwave.kgrid import kWaveGrid\n", - "from kwave.kmedium import kWaveMedium\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.ktransducer import NotATransducer, kWaveTransducerSimple\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.reconstruction.beamform import envelope_detection\n", - "from kwave.reconstruction.tools import log_compression\n", - "from kwave.utils.conversion import db2neper\n", - "from kwave.utils.dotdictionary import dotdict\n", - "from kwave.utils.filters import gaussian_filter\n", - "from kwave.utils.signals import get_win, tone_burst\n", - "\n", - "SENSOR_DATA_GDRIVE_ID = '1lGFTifpOrzBYT4Bl_ccLu_Kx0IDxM0Lv'\n", - "PHANTOM_DATA_GDRIVE_ID = '1ZfSdJPe8nufZHz0U9IuwHR4chaOGAWO4'\n", - "PHANTOM_DATA_PATH = 'phantom_data.mat'\n", - "\n", - "# simulation settings\n", - "DATA_CAST = 'single'\n", - "RUN_SIMULATION = True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pml_size_points = Vector([20, 10, 10]) # [grid points]\n", - "grid_size_points = Vector([256, 128, 128]) - 2 * pml_size_points # [grid points]\n", - "grid_size_meters = 40e-3 # [m]\n", - "grid_spacing_meters = grid_size_meters / Vector([grid_size_points.x, grid_size_points.x, grid_size_points.x])\n", - "\n", - "c0 = 1540\n", - "rho0 = 1000\n", - "source_strength = 1e6 # [Pa]\n", - "tone_burst_freq = 1.5e6 # [Hz]\n", - "tone_burst_cycles = 4\n", - "number_scan_lines = 96" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "kgrid = kWaveGrid(grid_size_points, grid_spacing_meters)\n", - "t_end = (grid_size_points.x * grid_spacing_meters.x) * 2.2 / c0 # [s]\n", - "kgrid.makeTime(c0, t_end=t_end)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "input_signal = tone_burst(1 / kgrid.dt, tone_burst_freq, tone_burst_cycles)\n", - "input_signal = (source_strength / (c0 * rho0)) * input_signal" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "medium = kWaveMedium(\n", - " sound_speed=None, # will be set later\n", - " alpha_coeff=0.75,\n", - " alpha_power=1.5,\n", - " BonA=6\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "transducer = dotdict()\n", - "transducer.number_elements = 32 # total number of transducer elements\n", - "transducer.element_width = 2 # width of each element [grid points/voxels]\n", - "transducer.element_length = 24 # length of each element [grid points/voxels]\n", - "transducer.element_spacing = 0 # spacing (kerf width) between the elements [grid points/voxels]\n", - "transducer.radius = float('inf') # radius of curvature of the transducer [m]\n", - "\n", - "# calculate the width of the transducer in grid points\n", - "transducer_width = transducer.number_elements * transducer.element_width + (\n", - " 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([\n", - " 1,\n", - " grid_size_points.y / 2 - transducer_width / 2,\n", - " grid_size_points.z / 2 - transducer.element_length / 2\n", - "])\n", - "transducer = kWaveTransducerSimple(kgrid, **transducer)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "not_transducer = dotdict()\n", - "not_transducer.sound_speed = c0 # 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 = 'Hanning'\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)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "logging.log(logging.INFO, \"Fetching phantom data...\")\n", - "download_if_does_not_exist(PHANTOM_DATA_GDRIVE_ID, PHANTOM_DATA_PATH)\n", - "\n", - "phantom = scipy.io.loadmat(PHANTOM_DATA_PATH)\n", - "sound_speed_map = phantom['sound_speed_map']\n", - "density_map = phantom['density_map']\n", - "\n", - "logging.log(logging.INFO, f\"RUN_SIMULATION set to {RUN_SIMULATION}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# preallocate the storage set medium position\n", - "scan_lines = np.zeros((number_scan_lines, kgrid.Nt))\n", - "medium_position = 0\n", - "\n", - "for scan_line_index in range(0, number_scan_lines):\n", - "\n", - " # load the current section of the medium\n", - " medium.sound_speed = \\\n", - " sound_speed_map[:, medium_position:medium_position + grid_size_points.y, :]\n", - " medium.density = density_map[:, medium_position:medium_position + grid_size_points.y, :]\n", - "\n", - " # set the input settings\n", - " input_filename = f'example_input_{scan_line_index}.h5'\n", - " # set the input settings\n", - " simulation_options = SimulationOptions(\n", - " pml_inside=False,\n", - " pml_size=pml_size_points,\n", - " data_cast=DATA_CAST,\n", - " data_recast=True,\n", - " save_to_disk=True,\n", - " input_filename=input_filename,\n", - " save_to_disk_exit=False\n", - " )\n", - " # run the simulation\n", - " if RUN_SIMULATION:\n", - " sensor_data = kspaceFirstOrder3D(\n", - " medium=medium,\n", - " kgrid=kgrid,\n", - " source=not_transducer,\n", - " sensor=not_transducer,\n", - " simulation_options=simulation_options,\n", - " execution_options=SimulationExecutionOptions(is_gpu_simulation=True)\n", - " )\n", - "\n", - " scan_lines[scan_line_index, :] = not_transducer.scan_line(not_transducer.combine_sensor_data(sensor_data['p'].T))\n", - "\n", - " # update medium position\n", - " medium_position = medium_position + transducer.element_width\n", - "\n", - "if RUN_SIMULATION:\n", - " simulation_data = scan_lines\n", - " scipy.io.savemat('sensor_data.mat', {'sensor_data_all_lines': simulation_data})\n", - "\n", - "else:\n", - " logging.log(logging.INFO, \"Downloading data from remote server...\")\n", - " sensor_data_path = 'sensor_data.mat'\n", - " download_if_does_not_exist(SENSOR_DATA_GDRIVE_ID, sensor_data_path)\n", - "\n", - " simulation_data = scipy.io.loadmat(sensor_data_path)['sensor_data_all_lines']\n", - "\n", - "\n", - "scan_lines = simulation_data" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## PROCESS THE RESULTS\n", - "### Remove Input Signal\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Trim the delay offset from the scan line data\n", - "tukey_win, _ = get_win(kgrid.Nt * 2, 'Tukey', False, 0.05)\n", - "transmit_len = len(input_signal.squeeze())\n", - "scan_line_win = np.concatenate((np.zeros([1, transmit_len * 2]), tukey_win.T[:, :kgrid.Nt - transmit_len * 2]), axis=1)\n", - "\n", - "scan_lines = scan_lines * scan_line_win\n", - "\n", - "# store intermediate results\n", - "scan_lines_no_input = scan_lines[len(scan_lines) // 2, :]\n", - "\n", - "Nt = kgrid.Nt" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Time Gain Compensation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create radius variable\n", - "t0 = len(input_signal) * kgrid.dt / 2\n", - "r = c0 * (np.arange(1, Nt + 1) * kgrid.dt - t0) / 2\n", - "\n", - "# Define absorption value and convert to correct units\n", - "tgc_alpha_db_cm = medium.alpha_coeff * (tone_burst_freq * 1e-6)**medium.alpha_power\n", - "tgc_alpha_np_m = db2neper(tgc_alpha_db_cm) * 100\n", - "\n", - "# Create time gain compensation function\n", - "tgc = np.exp(tgc_alpha_np_m * 2 * r)\n", - "\n", - "# Apply the time gain compensation to each of the scan lines\n", - "scan_lines *= tgc\n", - "\n", - "# store intermediate results\n", - "scan_lines_tgc = scan_lines[len(scan_lines) // 2, :]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "### Frequency Filtering\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "scan_lines_fund = gaussian_filter(scan_lines, 1/kgrid.dt, tone_burst_freq, 100)\n", - "scan_lines_harm = gaussian_filter(scan_lines, 1/kgrid.dt, 2 * tone_burst_freq, 30) # plotting was not impl.\n", - "\n", - "# store intermediate results\n", - "scan_lines_fund_ex = scan_lines_fund[len(scan_lines_fund) // 2, :]\n", - "# scan_lines_harm_ex = scan_lines_harm[len(scan_lines_harm) // 2, :]\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "### Envelope Detection\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "scan_lines_fund = envelope_detection(scan_lines_fund)\n", - "scan_lines_harm = envelope_detection(scan_lines_harm)\n", - "\n", - "# store intermediate results\n", - "scan_lines_fund_env_ex = scan_lines_fund[len(scan_lines_fund) // 2, :]\n", - "# scan_lines_harm_env_ex = scan_lines_harm[len(scan_lines_harm) // 2, :]\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Log Compression\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "compression_ratio = 3\n", - "scan_lines_fund = log_compression(scan_lines_fund, compression_ratio, True)\n", - "scan_lines_harm = log_compression(scan_lines_harm, compression_ratio, True)\n", - "\n", - "# store intermediate results\n", - "scan_lines_fund_log_ex = scan_lines_fund[len(scan_lines_fund) // 2, :]\n", - "# scan_lines_harm_log_ex = scan_lines_harm[len(scan_lines_harm) // 2, :]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Visualization\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Set the desired size of the image\n", - "image_size = kgrid.size\n", - "\n", - "# Create the axis variables\n", - "x_axis = [0, image_size[0] * 1e3 * 1.1] # [mm]\n", - "y_axis = [-0.5 * image_size[1] * 1e3, 0.5 * image_size[1] * 1e3] # [mm]\n", - "\n", - "# make plotting non-blocking\n", - "plt.ion()\n", - "# Plot the data before and after scan conversion\n", - "plt.figure(figsize=(14, 4))\n", - "# plot the sound speed map\n", - "plt.subplot(1, 3, 1)\n", - "plt.imshow(sound_speed_map[:, 64:-64, int(grid_size_points.z / 2)], aspect='auto',\n", - " extent=[y_axis[0], y_axis[1], x_axis[1], x_axis[0]])\n", - "plt.title('Sound Speed')\n", - "plt.xlabel('Width [mm]')\n", - "plt.ylabel('Depth [mm]')\n", - "ax = plt.gca()\n", - "ax.set_ylim(40, 5)\n", - "plt.subplot(1, 3, 2)\n", - "plt.imshow(scan_lines_fund.T, cmap='gray', aspect='auto', extent=[y_axis[0], y_axis[1], x_axis[1], x_axis[0]])\n", - "plt.xlabel('Image width [mm]')\n", - "plt.title('Fundamental')\n", - "ax = plt.gca()\n", - "ax.set_ylim(40, 5)\n", - "plt.yticks([])\n", - "plt.subplot(1, 3, 3)\n", - "plt.imshow(scan_lines_harm.T, cmap='gray', aspect='auto', extent=[y_axis[0], y_axis[1], x_axis[1], x_axis[0]])\n", - "plt.yticks([])\n", - "plt.xlabel('Image width [mm]')\n", - "plt.title('Harmonic')\n", - "ax = plt.gca()\n", - "ax.set_ylim(40, 5)\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Creating a dictionary with the step labels as keys\n", - "processing_steps = {\n", - " '1. Beamformed Signal': scan_lines_no_input,\n", - " '2. Time Gain Compensation': scan_lines_tgc,\n", - " '3. Frequency Filtering': scan_lines_fund_ex,\n", - " '4. Envelope Detection': scan_lines_fund_env_ex,\n", - " '5. Log Compression': scan_lines_fund_log_ex\n", - "}\n", - "\n", - "plt.figure(figsize=(14, 4), tight_layout=True)\n", - "\n", - "offset = -6e5\n", - "# Plotting each step using the dictionary\n", - "for i, (label, data) in enumerate(processing_steps.items()):\n", - " plt.plot(kgrid.t_array.squeeze(), data.squeeze() + offset * i, label=label)\n", - "\n", - "# Set y-ticks and y-labels\n", - "plt.yticks([offset * i for i in range(5)], list(processing_steps.keys()))\n", - "plt.xlabel('Time [\\u03BCs]')\n", - "plt.xlim(5e-3 * 2 / c0, t_end)\n", - "plt.title('Processing Steps Visualization')\n", - "plt.show()\n" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "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": 0 -} diff --git a/examples/us_bmode_phased_array/README.md b/examples/us_bmode_phased_array/README.md deleted file mode 100644 index 528b75f38..000000000 --- a/examples/us_bmode_phased_array/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Simulating B-mode Ultrasound Images From a Phased Array Transducer Example - - [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_bmode_phased_array/us_bmode_phased_array.ipynb) - -This example illustrates how k-wave-python can be used for the simulation of B-mode ultrasound images (including tissue harmonic imaging) analogous to those produced by a modern diagnostic ultrasound scanner. -Specifically, it demonstrates the usage of a phased array transducer. In this case, the entire medium should fit into the kgrid. - - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_us_bmode_phased_array.php). \ No newline at end of file diff --git a/examples/us_bmode_phased_array/us_bmode_phased_array.ipynb b/examples/us_bmode_phased_array/us_bmode_phased_array.ipynb deleted file mode 100644 index 23279f1e2..000000000 --- a/examples/us_bmode_phased_array/us_bmode_phased_array.ipynb +++ /dev/null @@ -1,468 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "!pip install git+https://github.com/waltsims/k-wave-python" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from copy import deepcopy\n", - "\n", - "import numpy as np\n", - "import scipy.io\n", - "from matplotlib import pyplot as plt\n", - "\n", - "from kwave.data import Vector\n", - "from kwave.kgrid import kWaveGrid\n", - "from kwave.kmedium import kWaveMedium\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.ktransducer import NotATransducer, kWaveTransducerSimple\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.options.simulation_options import SimulationOptions\n", - "from kwave.reconstruction.beamform import envelope_detection, scan_conversion\n", - "from kwave.reconstruction.tools import log_compression\n", - "from kwave.utils.conversion import db2neper\n", - "from kwave.utils.dotdictionary import dotdict\n", - "from kwave.utils.filters import gaussian_filter\n", - "from kwave.utils.mapgen import make_ball\n", - "from kwave.utils.signals import get_win, tone_burst" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# simulation settings\n", - "DATA_CAST = 'single'\n", - "RUN_SIMULATION = True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pml_size_points = Vector([15, 10, 10]) # [grid points]\n", - "grid_size_points = Vector([256, 256, 128]) - 2 * pml_size_points # [grid points]\n", - "grid_size_meters = 50e-3 # [m]\n", - "grid_spacing_meters = grid_size_meters / Vector([grid_size_points.x, grid_size_points.x, grid_size_points.x])\n", - "\n", - "c0 = 1540\n", - "rho0 = 1000\n", - "\n", - "medium = kWaveMedium(\n", - " sound_speed=None, # will be set later\n", - " alpha_coeff=0.75,\n", - " alpha_power=1.5,\n", - " BonA=6\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "kgrid = kWaveGrid(grid_size_points, grid_spacing_meters)\n", - "t_end = (grid_size_points.x * grid_spacing_meters.x) * 2.2 / c0 # [s]\n", - "kgrid.makeTime(c0, t_end=t_end)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "source_strength = 1e6 # [Pa]\n", - "tone_burst_freq = 1e6 # [Hz]\n", - "tone_burst_cycles = 4\n", - "\n", - "input_signal = tone_burst(1 / kgrid.dt, tone_burst_freq, tone_burst_cycles)\n", - "input_signal = (source_strength / (c0 * rho0)) * input_signal" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "transducer = dotdict()\n", - "transducer.number_elements = 64 # total number of transducer elements\n", - "transducer.element_width = 1 # width of each element [grid points/voxels]\n", - "transducer.element_length = 40 # length of each element [grid points/voxels]\n", - "transducer.element_spacing = 0 # spacing (kerf width) between the elements [grid points/voxels]\n", - "transducer.radius = float('inf') # radius of curvature of the transducer [m]\n", - "\n", - "# calculate the width of the transducer in grid points\n", - "transducer_width = transducer.number_elements * transducer.element_width + (\n", - " 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([\n", - " 1,\n", - " grid_size_points.y / 2 - transducer_width / 2,\n", - " grid_size_points.z / 2 - transducer.element_length / 2\n", - "])\n", - "transducer = kWaveTransducerSimple(kgrid, **transducer)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "not_transducer = dotdict()\n", - "not_transducer.sound_speed = c0 # sound speed [m/s]\n", - "not_transducer.focus_distance = 30e-3 # focus distance [m]\n", - "not_transducer.elevation_focus_distance = 30e-3 # focus distance in the elevation plane [m]\n", - "not_transducer.steering_angle = 0 # steering angle [degrees]\n", - "not_transducer.steering_angle_max = 32 # steering angle [degrees]\n", - "not_transducer.transmit_apodization = 'Hanning'\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)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define a random distribution of scatterers for the medium\n", - "background_map_mean = 1\n", - "background_map_std = 0.008\n", - "background_map = background_map_mean + background_map_std * np.random.randn(kgrid.Nx, kgrid.Ny, kgrid.Nz)\n", - "\n", - "sound_speed_map = c0 * background_map\n", - "density_map = rho0 * background_map" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define a random distribution of scatterers for the highly scattering region\n", - "scattering_map = np.random.randn(kgrid.Nx, kgrid.Ny, kgrid.Nz)\n", - "scattering_c0 = np.clip(c0 + 25 + 75 * scattering_map, 1400, 1600)\n", - "scattering_rho0 = scattering_c0 / 1.5" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Define a sphere for a highly scattering region\n", - "radius = 8e-3\n", - "x_pos = 32e-3\n", - "y_pos = kgrid.dy * kgrid.Ny / 2\n", - "z_pos = kgrid.dz * kgrid.Nz / 2\n", - "ball_center = np.round(Vector([x_pos, y_pos, z_pos]) / kgrid.dx)\n", - "scattering_region1 = make_ball(grid_size_points, ball_center, round(radius / kgrid.dx)).nonzero()\n", - "\n", - "sound_speed_map[scattering_region1] = scattering_c0[scattering_region1]\n", - "density_map[scattering_region1] = scattering_rho0[scattering_region1]\n", - "\n", - "medium.sound_speed = sound_speed_map\n", - "medium.density = density_map" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Range of steering angles to test\n", - "steering_angles = np.arange(-32, 33, 2)\n", - "\n", - "# Preallocate the storage\n", - "number_scan_lines = len(steering_angles)\n", - "scan_lines = np.zeros((number_scan_lines, kgrid.Nt))\n", - "\n", - "if RUN_SIMULATION:\n", - " for angle_index in range(number_scan_lines):\n", - " print(f'Computing scan line {angle_index} of {number_scan_lines}')\n", - "\n", - " # set the input settings\n", - " input_filename = f'example_input_{angle_index}.h5'\n", - " # set the input settings\n", - " simulation_options = SimulationOptions(\n", - " pml_inside=False,\n", - " pml_size=pml_size_points,\n", - " data_cast=DATA_CAST,\n", - " data_recast=True,\n", - " save_to_disk=True,\n", - " input_filename=input_filename,\n", - " save_to_disk_exit=False\n", - " )\n", - "\n", - " # Update the current steering angle\n", - " not_transducer.steering_angle = steering_angles[angle_index]\n", - "\n", - " sensor_data = kspaceFirstOrder3D(\n", - " medium=deepcopy(medium), # Medium is altered in-place in this function\n", - " kgrid=kgrid,\n", - " source=not_transducer,\n", - " sensor=not_transducer,\n", - " simulation_options=simulation_options,\n", - " execution_options=SimulationExecutionOptions(is_gpu_simulation=True)\n", - " )\n", - "\n", - " scan_lines[angle_index, :] = not_transducer.scan_line(not_transducer.combine_sensor_data(sensor_data['p'].T))\n", - "\n", - " scipy.io.savemat('example_us_phased_array_scan_lines.mat', {'scan_lines': scan_lines})\n", - "else:\n", - " scan_lines = scipy.io.loadmat('example_us_phased_array_scan_lines')['scan_lines']\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## PROCESS THE RESULTS\n", - "### Remove Input Signal" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Trim the delay offset from the scan line data\n", - "t0_offset = int(round(len(input_signal.squeeze())/ 2) + (not_transducer.appended_zeros - not_transducer.beamforming_delays_offset)) \n", - "\n", - "scan_lines = scan_lines[:, t0_offset:]\n", - "\n", - "Nt = np.shape(scan_lines)[1]\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "tukey_win, _ = get_win(Nt * 2, 'Tukey', False, 0.05)\n", - "scan_line_win = np.concatenate((np.zeros([1, t0_offset * 2]), tukey_win.T[:, :int(len(tukey_win)/2) - t0_offset * 2]), axis=1)\n", - "\n", - "scan_lines = scan_lines * scan_line_win\n", - "# store intermediate results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Time Gain Compensation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create radius variable\n", - "r = c0 * np.arange(1, Nt + 1) * kgrid.dt / 2\n", - "\n", - "# Define absorption value and convert to correct units\n", - "tgc_alpha_db_cm = medium.alpha_coeff * (tone_burst_freq * 1e-6)**medium.alpha_power\n", - "tgc_alpha_np_m = db2neper(tgc_alpha_db_cm) * 100\n", - "\n", - "# Create time gain compensation function\n", - "tgc = np.exp(tgc_alpha_np_m * 2 * r)\n", - "\n", - "# Apply the time gain compensation to each of the scan lines\n", - "scan_lines *= tgc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Frequency Filtering" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "scan_lines_fund = gaussian_filter(scan_lines, 1/kgrid.dt, tone_burst_freq, 100)\n", - "scan_lines_harm = gaussian_filter(scan_lines, 1/kgrid.dt, 2 * tone_burst_freq, 30)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Envelope Detection" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "scan_lines_fund = envelope_detection(scan_lines_fund)\n", - "scan_lines_harm = envelope_detection(scan_lines_harm)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Log Compression" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "compression_ratio = 3\n", - "scan_lines_fund = log_compression(scan_lines_fund, compression_ratio, True)\n", - "scan_lines_harm = log_compression(scan_lines_harm, compression_ratio, True)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Visualization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_size = [kgrid.Nx * kgrid.dx, kgrid.Ny * kgrid.dy]\n", - "image_res = [256, 256]\n", - "\n", - "b_mode_fund = scan_conversion(scan_lines_fund, steering_angles, image_size, c0, kgrid.dt, image_res)\n", - "b_mode_harm = scan_conversion(scan_lines_harm, steering_angles, image_size, c0, kgrid.dt, image_res)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Create the axis variables\n", - "x_axis = [0, image_size[0] * 1e3] # [mm]\n", - "y_axis = [0, image_size[1] * 1e3] # [mm]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.ion()\n", - "plt.figure(figsize=(15, 4))\n", - "plt.subplot(131)\n", - "plt.imshow(scan_lines.T, aspect='auto',\n", - " extent=[steering_angles[-1], steering_angles[0],y_axis[1], y_axis[0] ], interpolation='none', cmap='gray')\n", - "plt.xlabel('Steering angle [deg]')\n", - "plt.ylabel('Depth [mm]')\n", - "plt.title('Raw Scan-Line Data')\n", - "\n", - "\n", - "plt.subplot(132)\n", - "plt.imshow(scan_lines_fund.T, aspect='auto',\n", - " extent=[steering_angles[-1], steering_angles[0],y_axis[1], y_axis[0] ], interpolation='none', cmap='bone')\n", - "plt.xlabel('Steering angle [deg]')\n", - "plt.ylabel('Depth [mm]')\n", - "plt.title('Processed Scan-Line Data')\n", - "\n", - "plt.subplot(133)\n", - "plt.imshow(b_mode_fund, cmap='bone', aspect='auto', extent=[y_axis[0], y_axis[1], x_axis[1], x_axis[0]], interpolation='none')\n", - "plt.xlabel('Horizontal Position [mm]')\n", - "plt.ylabel('Depth [mm]')\n", - "plt.title('B-Mode Image')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plt.figure(figsize=(15, 4))\n", - "plt.subplot(131)\n", - "plt.imshow(medium.sound_speed[..., kgrid.Nz//2], aspect='auto',\n", - " extent=[y_axis[0], y_axis[1], x_axis[1], x_axis[0]])\n", - "plt.xlabel('Horizontal Position [mm]')\n", - "plt.ylabel('Depth [mm]')\n", - "plt.title('Scattering Phantom')\n", - "\n", - "plt.subplot(132)\n", - "plt.imshow(b_mode_fund, cmap='bone', aspect='auto', extent=[y_axis[0], y_axis[1], x_axis[1], x_axis[0]], interpolation='none')\n", - "plt.xlabel('Horizontal Position [mm]')\n", - "plt.ylabel('Depth [mm]')\n", - "plt.title('B-Mode Image')\n", - "\n", - "plt.subplot(133)\n", - "plt.imshow(b_mode_harm, cmap='bone', aspect='auto', extent=[y_axis[0], y_axis[1], x_axis[1], x_axis[0]], interpolation='none')\n", - "plt.xlabel('Horizontal Position [mm]')\n", - "plt.ylabel('Depth [mm]')\n", - "plt.title('Harmonic Image')\n", - "\n", - "plt.show()\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "deep-us", - "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 -} diff --git a/examples/us_defining_transducer/README.md b/examples/us_defining_transducer/README.md deleted file mode 100644 index a56ec5664..000000000 --- a/examples/us_defining_transducer/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Defining An Ultrasound Transducer Example - - [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_defining_transducer/us_defining_transducer.ipynb) - -This example illustrates how kWaveTransducerSimple can be used to simulate the field produced by an ultrasound transducer. - - -To read more, visit the [original example page](http://www.k-wave.org/documentation/example_us_defining_transducer.php). diff --git a/examples/us_defining_transducer/us_defining_transducer.ipynb b/examples/us_defining_transducer/us_defining_transducer.ipynb deleted file mode 100644 index b98b6f132..000000000 --- a/examples/us_defining_transducer/us_defining_transducer.ipynb +++ /dev/null @@ -1,271 +0,0 @@ -{ - "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.kmedium import kWaveMedium\n", - "from kwave.ksensor import kSensor\n", - "from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D\n", - "from kwave.ktransducer import NotATransducer, kWaveTransducerSimple\n", - "from kwave.kWaveSimulation import SimulationOptions\n", - "from kwave.options.simulation_execution_options import SimulationExecutionOptions\n", - "from kwave.utils.dotdictionary import dotdict\n", - "from kwave.utils.filters import spect\n", - "from kwave.utils.plot import voxel_plot\n", - "from kwave.utils.signals import tone_burst" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# simulation settings\n", - "DATA_CAST = 'single'\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 = 128 - 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])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "# define the medium\n", - "medium = kWaveMedium(sound_speed=1540,\n", - " density=1000,\n", - " alpha_coeff=0.75,\n", - " alpha_power=1.5,\n", - " BonA=6)\n", - "\n", - "# create the time array\n", - "t_end = 40e-6\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 = 72\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)" - ] - }, - { - "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.zeros((transducer.number_elements, 1))\n", - "not_transducer.active_elements[21:52] = 1\n", - "not_transducer.input_signal = input_signal\n", - "\n", - "not_transducer = NotATransducer(transducer, kgrid, **not_transducer)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "voxel_plot(np.single(not_transducer.all_elements_mask))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# define sensor mask\n", - "sensor_mask = np.zeros((Nx, Ny, Nz))\n", - "sensor_mask[Nx//4, Ny//2, Nz//2] = 1\n", - "sensor_mask[Nx//2, Ny//2, Nz//2] = 1\n", - "sensor_mask[3*Nx//4, Ny//2, Nz//2] = 1\n", - "sensor = kSensor(sensor_mask)\n", - "sensor.record=['p']\n", - "\n", - "# SIMULATION\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", - "execution_options = SimulationExecutionOptions(is_gpu_simulation=True)\n", - "\n", - "sensor_data = kspaceFirstOrder3D(medium=medium, kgrid=kgrid, source=not_transducer, sensor=sensor,\n", - " simulation_options=simulation_options, execution_options=execution_options)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "padded_input_signal = np.concatenate((input_signal, np.zeros((1, 2 * np.shape(input_signal)[1]))), axis=1)\n", - "f_input, as_input, _ = spect(padded_input_signal, 1/kgrid.dt)\n", - "_, as_1, _ = spect(sensor_data['p'][:, 0], 1/kgrid.dt)\n", - "_, as_2, _ = spect(sensor_data['p'][:, 1], 1/kgrid.dt)\n", - "f, as_3, _ = spect(sensor_data['p'][:, 2], 1/kgrid.dt)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fig, axes = plt.subplots(2,1)\n", - "axes[0].plot(np.arange(0,input_signal.shape[-1]) * kgrid.dt * 1e6, input_signal.T, 'k-')\n", - "axes[0].set_xlabel('Time [\\mus]')\n", - "axes[0].set_ylabel('Particle Velocity [m/s]')\n", - "\n", - "axes[1].plot(f_input/1e6, np.squeeze(as_input/np.max(as_input)), 'k-')\n", - "axes[1].plot([tone_burst_freq/1e6, tone_burst_freq/1e6], [0, 1], 'k--')\n", - "axes[1].set_xlabel('Frequency [MHz]')\n", - "axes[1].set_ylabel('Amplitude Spectrum [au]')\n", - "f_max = medium.sound_speed / (2*dx)\n", - "axes[1].set_xlim([0, f_max/1e6])\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Creating a dictionary with the step labels as keys\n", - "sensor_positions = {\n", - " 'Sensor Position 1': sensor_data['p'][:,0],\n", - " 'Sensor Position 2': sensor_data['p'][:,1],\n", - " 'Sensor Position 3': sensor_data['p'][:,2]\n", - "}\n", - "\n", - "fig, axes = plt.subplots(1,2)\n", - "fig.set_figwidth(16)\n", - "fig.tight_layout = True\n", - "\n", - "offset = -30e5\n", - "# Plotting each step using the dictionary\n", - "for i, (label, data) in enumerate(sensor_positions.items()):\n", - " axes[0].plot(kgrid.t_array.squeeze()[:len(data.squeeze())] * 1e6, data.squeeze() + offset * i, label=label)\n", - "\n", - "# Set y-ticks and y-labels\n", - "axes[0].set_yticks([offset * i for i in range(len(sensor_positions.keys()))], list(sensor_positions.keys()))\n", - "axes[0].set_xlabel('Time [\\u03BCs]')\n", - "\n", - "\n", - "axes[1].plot(f * 1e-6, as_1 / np.max(as_1.flatten()), 'k-', label = 'Sensor Position 1')\n", - "axes[1].plot(f * 1e-6, as_2 / np.max(as_1.flatten()), 'b-', label = 'Sensor Position 2')\n", - "axes[1].plot(f * 1e-6, as_3 / np.max(as_1.flatten()), 'r-', label = 'Sensor Position 3')\n", - "axes[1].legend()\n", - "axes[1].set_xlabel('Frequency [MHz]')\n", - "axes[1].set_ylabel('Normalised Amplitude Spectrum [au]')\n", - "f_max = medium.sound_speed / (2 * dx)\n", - "axes[1].set_xlim([0, f_max * 1e-6])\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv", - "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 -} From 98e092b7b3d6baa213a0585af8d259141b867bb5 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:31:09 -0700 Subject: [PATCH 06/21] Add jupytext config and dev dependency for notebook generation jupytext is a dev/CI dependency only (not runtime). Notebooks are generated from .py files with: jupytext --to notebook examples/**/*.py Co-Authored-By: Claude Opus 4.6 (1M context) --- jupytext.toml | 3 +++ pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 jupytext.toml diff --git a/jupytext.toml b/jupytext.toml new file mode 100644 index 000000000..718896859 --- /dev/null +++ b/jupytext.toml @@ -0,0 +1,3 @@ +# Jupytext config: .py files with # %% markers are the source of truth. +# Generate notebooks with: jupytext --to notebook examples/**/*.py +formats = "py:percent" diff --git a/pyproject.toml b/pyproject.toml index f0493ed07..123eec138 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ docs = [ "sphinx-mdinclude==0.6.2", "sphinx-tabs==3.4.7", "sphinx-toolbox==3.8.0", "furo==2024.8.6"] -dev = ["pre-commit==4.2.0"] +dev = ["pre-commit==4.2.0", "jupytext"] [tool.hatch.version] path = "kwave/__init__.py" From 0acc48ef8ebc1ab49a8d6d87ccee4d24ba2ad84e Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:34:08 -0700 Subject: [PATCH 07/21] Add KWAVE_BACKEND/KWAVE_DEVICE env overrides, update CI to run examples on CPU/python kspaceFirstOrder() now respects KWAVE_BACKEND and KWAVE_DEVICE env vars, allowing CI to run GPU/cpp examples with python/cpu backend. CI workflow sets these + MPLBACKEND=Agg, and excludes utility/legacy-only scripts. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/run-examples.yml | 15 +++++++++++---- kwave/kspaceFirstOrder.py | 9 +++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-examples.yml b/.github/workflows/run-examples.yml index ec470a284..08c1c327c 100644 --- a/.github/workflows/run-examples.yml +++ b/.github/workflows/run-examples.yml @@ -14,8 +14,13 @@ jobs: - uses: actions/checkout@v4 - id: find-examples run: | - # Find all Python files in examples subdirectories - EXAMPLES=$(find examples -name "*.py" -not -path "*/\.*" | jq -R -s -c 'split("\n")[:-1]') + # Find example scripts (exclude utilities and legacy-only examples) + # Exclude: utilities, checkpointing (legacy), time-reversal (needs C++ binary for TimeReversal class) + EXAMPLES=$(find examples -name "*.py" -not -path "*/\.*" \ + -not -name "example_utils.py" \ + -not -name "checkpoint.py" \ + -not -name "*TR_*" \ + | sort | jq -R -s -c 'split("\n")[:-1]') echo "examples=$EXAMPLES" >> "$GITHUB_OUTPUT" run-examples: @@ -50,7 +55,9 @@ jobs: - name: Run example env: - KWAVE_FORCE_CPU: 1 + KWAVE_BACKEND: python + KWAVE_DEVICE: cpu + MPLBACKEND: Agg run: | echo "Running example: ${{ matrix.example }}" - python "${{ matrix.example }}" + python "${{ matrix.example }}" diff --git a/kwave/kspaceFirstOrder.py b/kwave/kspaceFirstOrder.py index da8eb8158..13952548e 100644 --- a/kwave/kspaceFirstOrder.py +++ b/kwave/kspaceFirstOrder.py @@ -1,4 +1,5 @@ import copy +import os import warnings from typing import Optional, Union @@ -95,7 +96,15 @@ def kspaceFirstOrder( Returns: dict: Recorded sensor data keyed by field name (e.g. ``"p"``, ``"p_final"``, ``"ux"``, ``"uy"``). + + Environment variables: + ``KWAVE_BACKEND``: Override ``backend`` (e.g. ``"python"``). + ``KWAVE_DEVICE``: Override ``device`` (e.g. ``"cpu"``). """ + # Environment overrides — lets CI run GPU/cpp examples on CPU/python + backend = os.environ.get("KWAVE_BACKEND", backend) + device = os.environ.get("KWAVE_DEVICE", device) + if device not in ("cpu", "gpu"): raise ValueError(f"device must be 'cpu' or 'gpu', got {device!r}") if backend not in ("python", "cpp"): From 119dd32b27b1755d5411bd6fd2fd8debc0329f95 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:41:34 -0700 Subject: [PATCH 08/21] Add tests for env var overrides and Cartesian sensor mask conversion Covers KWAVE_BACKEND/KWAVE_DEVICE env override paths and cart2grid auto-conversion for cpp backend. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/test_kspaceFirstOrder.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_kspaceFirstOrder.py b/tests/test_kspaceFirstOrder.py index ce13f72aa..dbb25739f 100644 --- a/tests/test_kspaceFirstOrder.py +++ b/tests/test_kspaceFirstOrder.py @@ -63,6 +63,31 @@ def test_python_backend_runs(self, sim_2d): assert "p" in result assert result["p"].shape == (int(sensor.mask.sum()), int(kgrid.Nt)) + def test_env_override_backend(self, sim_2d, monkeypatch): + """KWAVE_BACKEND env var overrides the backend parameter.""" + monkeypatch.setenv("KWAVE_BACKEND", "python") + kgrid, medium, source, sensor = sim_2d + # Pass backend="cpp" but env says "python" — should run python backend (no C++ binary needed) + result = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp") + assert "p" in result + + def test_env_override_device(self, sim_2d, monkeypatch): + """KWAVE_DEVICE env var overrides the device parameter.""" + monkeypatch.setenv("KWAVE_DEVICE", "cpu") + kgrid, medium, source, sensor = sim_2d + result = kspaceFirstOrder(kgrid, medium, source, sensor, device="cpu") + assert "p" in result + + def test_cartesian_sensor_mask_cpp_conversion(self, sim_2d): + """Cartesian sensor mask is auto-converted to binary for cpp backend.""" + kgrid, medium, source, _ = sim_2d + # Create a Cartesian sensor mask (2, N_points) — positions in meters + cart_points = np.array([[0.0, 0.5e-3, -0.5e-3], [0.0, 0.0, 0.0]]) # 3 points, 2D + sensor = kSensor(cart_points) + # save_only=True so we don't need the C++ binary + result = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp", save_only=True, data_path=tempfile.mkdtemp()) + assert "input_file" in result + def test_cpp_save_only(self, sim_2d): kgrid, medium, source, sensor = sim_2d result = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp", save_only=True, data_path=tempfile.mkdtemp()) From 0d881335d6657263bfddb17103e80b20f6a44b6b Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:45:27 -0700 Subject: [PATCH 09/21] Allow NotATransducer as source in kspaceFirstOrder type hint NotATransducer (kSensor subclass) is used as source in transducer examples (us_bmode_phased_array). beartype rejects it without the Union[kSource, NotATransducer] annotation. Co-Authored-By: Claude Opus 4.6 (1M context) --- kwave/kspaceFirstOrder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kwave/kspaceFirstOrder.py b/kwave/kspaceFirstOrder.py index 13952548e..b65577798 100644 --- a/kwave/kspaceFirstOrder.py +++ b/kwave/kspaceFirstOrder.py @@ -31,7 +31,7 @@ def _normalize_pml(val, ndim, name="pml_size"): def kspaceFirstOrder( kgrid: kWaveGrid, medium: kWaveMedium, - source: kSource, + source: Union[kSource, NotATransducer], sensor: Union[kSensor, NotATransducer, None] = None, *, pml_size: Union[int, tuple, str] = 20, From 60dd5263466921c9d643e9916431cdea5014ea75 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:47:50 -0700 Subject: [PATCH 10/21] Fix axisymmetric examples to use legacy API, hoist TR imports to top level MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Revert at_focused_bowl_AS and at_circular_piston_AS to kspaceFirstOrderASC (new API doesn't support axisymmetric flag — would silently produce wrong results) - Hoist legacy imports (kspaceFirstOrder2D/3D, SimulationOptions, etc.) to top of TR examples instead of burying inside main() Co-Authored-By: Claude Opus 4.6 (1M context) --- .../at_circular_piston_AS.py | 26 ++++++++++--------- .../at_focused_bowl_AS/at_focused_bowl_AS.py | 25 ++++++++++-------- .../pr_2D_TR_line_sensor.py | 9 +++---- .../pr_3D_TR_planar_sensor.py | 9 +++---- 4 files changed, 36 insertions(+), 33 deletions(-) diff --git a/examples/at_circular_piston_AS/at_circular_piston_AS.py b/examples/at_circular_piston_AS/at_circular_piston_AS.py index da90877db..dad8ec769 100644 --- a/examples/at_circular_piston_AS/at_circular_piston_AS.py +++ b/examples/at_circular_piston_AS/at_circular_piston_AS.py @@ -23,7 +23,9 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.kspaceFirstOrder2D import kspaceFirstOrderASC +from kwave.options.simulation_execution_options import SimulationExecutionOptions +from kwave.options.simulation_options import SimulationOptions from kwave.utils.filters import extract_amp_phase from kwave.utils.kwave_array import kWaveArray from kwave.utils.math import round_even @@ -133,17 +135,17 @@ # record only the final few periods when the field is in steady state sensor.record_start_index = kgrid.Nt - record_periods * ppp + 1 -# NOTE: pml_inside=False not supported in new API -# NOTE: simulation_type=SimulationType.AXISYMMETRIC is handled automatically by the new API - -sensor_data = kspaceFirstOrder( - kgrid, - medium, - source, - sensor, - pml_size="auto", - backend="cpp", - device="cpu", +# NOTE: Axisymmetric simulations require the legacy kspaceFirstOrderASC API +# until the new kspaceFirstOrder() supports the axisymmetric flag. +simulation_options = SimulationOptions(save_to_disk=True, pml_inside=False, data_cast="single") +execution_options = SimulationExecutionOptions(is_gpu_simulation=False) +sensor_data = kspaceFirstOrderASC( + kgrid=kgrid, + medium=medium, + source=source, + sensor=sensor, + simulation_options=simulation_options, + execution_options=execution_options, ) # extract amplitude from the sensor data diff --git a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py b/examples/at_focused_bowl_AS/at_focused_bowl_AS.py index 6c32c3507..faac6096e 100644 --- a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py +++ b/examples/at_focused_bowl_AS/at_focused_bowl_AS.py @@ -11,7 +11,9 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.kspaceFirstOrder2D import kspaceFirstOrderASC +from kwave.options.simulation_execution_options import SimulationExecutionOptions +from kwave.options.simulation_options import SimulationOptions from kwave.utils.filters import extract_amp_phase from kwave.utils.kwave_array import kWaveArray from kwave.utils.mapgen import focused_bowl_oneil @@ -127,16 +129,17 @@ # SIMULATION # -------------------- -# NOTE: pml_inside=False, data_cast="single" not supported in new API -# NOTE: simulation_type=SimulationType.AXISYMMETRIC is handled automatically by the new API - -sensor_data = kspaceFirstOrder( - kgrid, - medium, - source, - sensor, - backend="cpp", - device="cpu", +# NOTE: Axisymmetric simulations require the legacy kspaceFirstOrderASC API +# until the new kspaceFirstOrder() supports the axisymmetric flag. +simulation_options = SimulationOptions(save_to_disk=True, data_cast="single") +execution_options = SimulationExecutionOptions(is_gpu_simulation=False) +sensor_data = kspaceFirstOrderASC( + kgrid=kgrid, + medium=medium, + source=source, + sensor=sensor, + simulation_options=simulation_options, + execution_options=execution_options, ) # extract amplitude from the sensor data diff --git a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py index 37d1effcc..546e5d5be 100644 --- a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py +++ b/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py @@ -14,7 +14,10 @@ from kwave.ksensor import kSensor from kwave.ksource import kSource from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D # TimeReversal requires legacy API from kwave.kspaceLineRecon import kspaceLineRecon +from kwave.options.simulation_execution_options import SimulationExecutionOptions +from kwave.options.simulation_options import SimulationOptions from kwave.reconstruction import TimeReversal from kwave.utils.colormap import get_color_map from kwave.utils.filters import smooth @@ -85,11 +88,7 @@ def main(): source = kSource() # create time reversal handler and run reconstruction tr = TimeReversal(kgrid, medium, sensor) - # NOTE: TimeReversal still uses the legacy API internally — this may need updating separately - from kwave.kspaceFirstOrder2D import kspaceFirstOrder2D - from kwave.options.simulation_execution_options import SimulationExecutionOptions - from kwave.options.simulation_options import SimulationOptions - + # NOTE: TimeReversal requires the legacy API until it is updated simulation_options = SimulationOptions( pml_inside=False, pml_size=PML_size, diff --git a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py index cfd50e435..b148c29e7 100644 --- a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py +++ b/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py @@ -13,6 +13,9 @@ from kwave.ksensor import kSensor from kwave.ksource import kSource from kwave.kspaceFirstOrder import kspaceFirstOrder +from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D # TimeReversal requires legacy API +from kwave.options.simulation_execution_options import SimulationExecutionOptions +from kwave.options.simulation_options import SimulationOptions from kwave.reconstruction import TimeReversal from kwave.utils.colormap import get_color_map from kwave.utils.filters import smooth @@ -80,11 +83,7 @@ def main(): # create time reversal handler and run reconstruction tr = TimeReversal(kgrid, medium, sensor) - # NOTE: TimeReversal still uses the legacy API internally — this may need updating separately - from kwave.kspaceFirstOrder3D import kspaceFirstOrder3D - from kwave.options.simulation_execution_options import SimulationExecutionOptions - from kwave.options.simulation_options import SimulationOptions - + # NOTE: TimeReversal requires the legacy API until it is updated simulation_options = SimulationOptions( pml_inside=False, pml_size=PML_size, From 6c3c873919127a5afef411f6230b3dd6e2be27d0 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:54:11 -0700 Subject: [PATCH 11/21] Add CI workflow to auto-generate notebooks, update README with Colab badge On merge to master, CI generates .ipynb files from examples/*.py via jupytext and commits them to notebooks/. Colab badges link to these generated notebooks. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/generate-notebooks.yml | 44 ++++++++++++++++++++++++ examples/README.md | 13 +++---- 2 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/generate-notebooks.yml diff --git a/.github/workflows/generate-notebooks.yml b/.github/workflows/generate-notebooks.yml new file mode 100644 index 000000000..e661c14ed --- /dev/null +++ b/.github/workflows/generate-notebooks.yml @@ -0,0 +1,44 @@ +name: Generate Notebooks + +on: + push: + branches: [master] + paths: ['examples/**/*.py'] + +jobs: + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install jupytext + run: pip install jupytext + + - name: Generate notebooks + run: | + mkdir -p notebooks + for py in examples/**/*.py examples/*.py; do + [ -f "$py" ] || continue + # Skip utilities and non-example files + basename=$(basename "$py") + case "$basename" in + example_utils.py|__init__.py) continue ;; + esac + jupytext --to notebook "$py" --output "notebooks/${basename%.py}.ipynb" + done + + - name: Commit notebooks + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add notebooks/ + if git diff --cached --quiet; then + echo "No notebook changes" + else + git commit -m "Auto-generate notebooks from examples [skip ci]" + git push + fi diff --git a/examples/README.md b/examples/README.md index 029ef232d..4d94a755a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -12,11 +12,12 @@ python examples/ivp_1D_simulation.py **Interactively (VS Code / JupyterLab / PyCharm):** Open any `.py` file — the `# %%` markers define cells you can run one at a time. -**As a Jupyter notebook:** -```bash -jupytext --to notebook examples/ivp_1D_simulation.py -jupyter notebook examples/ivp_1D_simulation.ipynb -``` +**On Google Colab:** +Notebooks are auto-generated in the [`notebooks/`](../notebooks/) directory. Open any example with: + +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/master/notebooks/ivp_1D_simulation.ipynb) + +Replace the filename in the URL for other examples. ## Example categories @@ -48,4 +49,4 @@ jupyter notebook examples/ivp_1D_simulation.ipynb ``` 3. Add `# %%` before each logical section (grid, source, simulation, visualization) 4. Use `kspaceFirstOrder()` — not the legacy `kspaceFirstOrder2D/3D` -5. Notebooks (`.ipynb`) are auto-generated from `.py` files at release time — don't create them by hand +5. Notebooks are auto-generated from `.py` files on merge to master — don't create `.ipynb` by hand From 95ee30cf2e79bf13e44faabf8bac88387865a55b Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:56:19 -0700 Subject: [PATCH 12/21] Update run_examples.py with env overrides, add notebooks/ placeholder - run_examples.py sets KWAVE_BACKEND=python KWAVE_DEVICE=cpu MPLBACKEND=Agg - Skip utility files, report pass/fail summary - Add notebooks/.gitkeep so link checker doesn't fail on missing directory Co-Authored-By: Claude Opus 4.6 (1M context) --- notebooks/.gitkeep | 0 run_examples.py | 47 ++++++++++++++++++++++++++++------------------ 2 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 notebooks/.gitkeep diff --git a/notebooks/.gitkeep b/notebooks/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/run_examples.py b/run_examples.py index ae543ecbe..adb299c43 100644 --- a/run_examples.py +++ b/run_examples.py @@ -1,26 +1,37 @@ +"""Run all example scripts with the Python backend on CPU.""" import os import subprocess +import sys from pathlib import Path +SKIP = {"example_utils.py", "__init__.py"} -def run_python_files(directory): - # Recursively walk through the directory - for root, _, files in os.walk(directory): - for file in files: - # Check if it's a Python file - if file.endswith(".py"): - file_path = os.path.join(root, file) - print(f"Running: {file_path}") - try: - # Use subprocess to run the Python file - result = subprocess.run(["python", file_path], capture_output=True, text=True) - print(f"Output:\n{result.stdout}") - if result.stderr: - print(f"Errors:\n{result.stderr}") - except Exception as e: - print(f"Failed to run {file_path}: {e}") + +def run_examples(directory: Path): + env = {**os.environ, "KWAVE_BACKEND": "python", "KWAVE_DEVICE": "cpu", "MPLBACKEND": "Agg"} + examples = sorted(p for p in directory.rglob("*.py") if p.name not in SKIP) + failed = [] + for path in examples: + print(f"\n{'='*60}\nRunning: {path}\n{'='*60}") + result = subprocess.run([sys.executable, str(path)], env=env, capture_output=True, text=True) + if result.stdout: + print(result.stdout) + if result.returncode != 0: + print(f"FAILED (exit code {result.returncode})") + if result.stderr: + print(result.stderr[-500:]) + failed.append(str(path)) + else: + print("OK") + + print(f"\n{'='*60}") + print(f"Results: {len(examples) - len(failed)}/{len(examples)} passed") + if failed: + print("Failed:") + for f in failed: + print(f" - {f}") + sys.exit(1) if __name__ == "__main__": - directory = Path("examples/") - run_python_files(directory) + run_examples(Path("examples/")) From 21eb1ea10e1d40bb8751eb9af87eaa37309ec634 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 20:59:09 -0700 Subject: [PATCH 13/21] Flatten examples: move all .py files to examples/ root, delete subdirectories - Move 20 example .py files from subdirectories to examples/ - Inline example_utils.py into us_bmode_linear_transducer.py - Delete now-empty subdirectories and stale MATLAB file - README.md renders below file list on GitHub (always visible) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../at_array_as_sensor.py | 0 .../at_array_as_source.py | 0 .../at_circular_piston_3D.py | 0 .../at_circular_piston_AS.py | 0 .../at_focused_annular_array_3D.py | 0 .../at_focused_bowl_3D.py | 0 .../at_focused_bowl_AS.py | 0 .../at_linear_array_transducer.py | 0 .../ivp_photoacoustic_waveforms.py | 0 .../modified_matlab_example.m | 68 ----- .../pr_2D_FFT_line_sensor.py | 0 .../pr_2D_TR_line_sensor.py | 0 .../pr_3D_FFT_planar_sensor.py | 0 .../pr_3D_TR_planar_sensor.py | 0 .../sd_directivity_modelling_2D.py | 0 .../sd_focussed_detector_2D.py | 0 .../sd_focussed_detector_3D.py | 0 .../us_beam_patterns.py | 0 .../us_bmode_linear_transducer.py | 18 +- .../example_utils.py | 20 -- .../us_bmode_phased_array.py | 0 .../us_defining_transducer.py | 0 uv.lock | 270 ++++++++++++++++++ 23 files changed, 287 insertions(+), 89 deletions(-) rename examples/{at_array_as_sensor => }/at_array_as_sensor.py (100%) rename examples/{at_array_as_source => }/at_array_as_source.py (100%) rename examples/{at_circular_piston_3D => }/at_circular_piston_3D.py (100%) rename examples/{at_circular_piston_AS => }/at_circular_piston_AS.py (100%) rename examples/{at_focused_annular_array_3D => }/at_focused_annular_array_3D.py (100%) rename examples/{at_focused_bowl_3D => }/at_focused_bowl_3D.py (100%) rename examples/{at_focused_bowl_AS => }/at_focused_bowl_AS.py (100%) rename examples/{at_linear_array_transducer => }/at_linear_array_transducer.py (100%) rename examples/{ivp_photoacoustic_waveforms => }/ivp_photoacoustic_waveforms.py (100%) delete mode 100644 examples/na_controlling_the_pml/modified_matlab_example.m rename examples/{pr_2D_FFT_line_sensor => }/pr_2D_FFT_line_sensor.py (100%) rename examples/{pr_2D_TR_line_sensor => }/pr_2D_TR_line_sensor.py (100%) rename examples/{pr_3D_FFT_planar_sensor => }/pr_3D_FFT_planar_sensor.py (100%) rename examples/{pr_3D_TR_planar_sensor => }/pr_3D_TR_planar_sensor.py (100%) rename examples/{sd_directivity_modelling_2D => }/sd_directivity_modelling_2D.py (100%) rename examples/{sd_focussed_detector_2D => }/sd_focussed_detector_2D.py (100%) rename examples/{sd_focussed_detector_3D => }/sd_focussed_detector_3D.py (100%) rename examples/{us_beam_patterns => }/us_beam_patterns.py (100%) rename examples/{us_bmode_linear_transducer => }/us_bmode_linear_transducer.py (94%) delete mode 100644 examples/us_bmode_linear_transducer/example_utils.py rename examples/{us_bmode_phased_array => }/us_bmode_phased_array.py (100%) rename examples/{us_defining_transducer => }/us_defining_transducer.py (100%) diff --git a/examples/at_array_as_sensor/at_array_as_sensor.py b/examples/at_array_as_sensor.py similarity index 100% rename from examples/at_array_as_sensor/at_array_as_sensor.py rename to examples/at_array_as_sensor.py diff --git a/examples/at_array_as_source/at_array_as_source.py b/examples/at_array_as_source.py similarity index 100% rename from examples/at_array_as_source/at_array_as_source.py rename to examples/at_array_as_source.py diff --git a/examples/at_circular_piston_3D/at_circular_piston_3D.py b/examples/at_circular_piston_3D.py similarity index 100% rename from examples/at_circular_piston_3D/at_circular_piston_3D.py rename to examples/at_circular_piston_3D.py diff --git a/examples/at_circular_piston_AS/at_circular_piston_AS.py b/examples/at_circular_piston_AS.py similarity index 100% rename from examples/at_circular_piston_AS/at_circular_piston_AS.py rename to examples/at_circular_piston_AS.py diff --git a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py b/examples/at_focused_annular_array_3D.py similarity index 100% rename from examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py rename to examples/at_focused_annular_array_3D.py diff --git a/examples/at_focused_bowl_3D/at_focused_bowl_3D.py b/examples/at_focused_bowl_3D.py similarity index 100% rename from examples/at_focused_bowl_3D/at_focused_bowl_3D.py rename to examples/at_focused_bowl_3D.py diff --git a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py b/examples/at_focused_bowl_AS.py similarity index 100% rename from examples/at_focused_bowl_AS/at_focused_bowl_AS.py rename to examples/at_focused_bowl_AS.py diff --git a/examples/at_linear_array_transducer/at_linear_array_transducer.py b/examples/at_linear_array_transducer.py similarity index 100% rename from examples/at_linear_array_transducer/at_linear_array_transducer.py rename to examples/at_linear_array_transducer.py diff --git a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py b/examples/ivp_photoacoustic_waveforms.py similarity index 100% rename from examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py rename to examples/ivp_photoacoustic_waveforms.py diff --git a/examples/na_controlling_the_pml/modified_matlab_example.m b/examples/na_controlling_the_pml/modified_matlab_example.m deleted file mode 100644 index b33f687cc..000000000 --- a/examples/na_controlling_the_pml/modified_matlab_example.m +++ /dev/null @@ -1,68 +0,0 @@ -clearvars; - -% 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] -kgrid = kWaveGrid(Nx, dx, Ny, dy); - -% define the properties of the propagation medium -medium.sound_speed = 1500; % [m/s] - -% create initial pressure distribution using makeDisc -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 * makeDisc(Nx, Ny, disc_x_pos, disc_y_pos, disc_radius); - -disc_magnitude = 3; % [Pa] -disc_x_pos = 80; % [grid points] -disc_y_pos = 60; % [grid points] -disc_radius = 5; % [grid points] -disc_2 = disc_magnitude * makeDisc(Nx, Ny, disc_x_pos, disc_y_pos, disc_radius); - -source.p0 = disc_1 + disc_2; - -% define a centered circular sensor -sensor_radius = 4e-3; % [m] -num_sensor_points = 50; -sensor.mask = makeCartCircle(sensor_radius, num_sensor_points); -sensor.mask = cart2grid(kgrid, sensor.mask, false); % otherwise cpu computation will not work - -% Example 1: PML with no absorption -input_args = {'PMLAlpha', 0}; -run_simulation_and_record_input_output('example_1', kgrid, medium, source, sensor, input_args); - -% Example 2: PML with the absorption value set too high -input_args = {'PMLAlpha', 1e6}; -run_simulation_and_record_input_output('example_2', kgrid, medium, source, sensor, input_args); - -% Example 3: partially effective PML -input_args = {'PMLSize', 2}; -run_simulation_and_record_input_output('example_3', kgrid, medium, source, sensor, input_args); - -% Example 4: PML set to be outside the computational domain -input_args = {'PMLInside', false}; -run_simulation_and_record_input_output('example_4', kgrid, medium, source, sensor, input_args); - - -% define a new matlab function -function run_simulation_and_record_input_output(name, kgrid, medium, source, sensor, input_args) - - sensor_data = kspaceFirstOrder2DC(kgrid, medium, source, sensor, input_args{:}); - - % plot the simulated sensor data - figure; - imagesc(sensor_data, [-1, 1]); - colormap(getColorMap); - ylabel('Sensor Position'); - xlabel('Time Step'); - colorbar; - - save(sprintf('%s.mat', name), 'sensor_data', 'input_args'); - - % save the figure - saveas(gcf, sprintf('%s.png', name)); -end diff --git a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py b/examples/pr_2D_FFT_line_sensor.py similarity index 100% rename from examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py rename to examples/pr_2D_FFT_line_sensor.py diff --git a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py b/examples/pr_2D_TR_line_sensor.py similarity index 100% rename from examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py rename to examples/pr_2D_TR_line_sensor.py diff --git a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py b/examples/pr_3D_FFT_planar_sensor.py similarity index 100% rename from examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py rename to examples/pr_3D_FFT_planar_sensor.py diff --git a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py b/examples/pr_3D_TR_planar_sensor.py similarity index 100% rename from examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py rename to examples/pr_3D_TR_planar_sensor.py diff --git a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py b/examples/sd_directivity_modelling_2D.py similarity index 100% rename from examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py rename to examples/sd_directivity_modelling_2D.py diff --git a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py b/examples/sd_focussed_detector_2D.py similarity index 100% rename from examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py rename to examples/sd_focussed_detector_2D.py diff --git a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py b/examples/sd_focussed_detector_3D.py similarity index 100% rename from examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py rename to examples/sd_focussed_detector_3D.py diff --git a/examples/us_beam_patterns/us_beam_patterns.py b/examples/us_beam_patterns.py similarity index 100% rename from examples/us_beam_patterns/us_beam_patterns.py rename to examples/us_beam_patterns.py diff --git a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py b/examples/us_bmode_linear_transducer.py similarity index 94% rename from examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py rename to examples/us_bmode_linear_transducer.py index 0ec9bce85..869b94cd2 100644 --- a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py +++ b/examples/us_bmode_linear_transducer.py @@ -4,12 +4,12 @@ # %% import logging +import os import matplotlib.pyplot as plt import numpy as np import scipy.io -from examples.us_bmode_linear_transducer.example_utils import download_if_does_not_exist from kwave.data import Vector from kwave.kgrid import kWaveGrid from kwave.kmedium import kWaveMedium @@ -22,6 +22,22 @@ from kwave.utils.filters import gaussian_filter from kwave.utils.signals import get_win, tone_burst + +def _download_from_gdrive(file_id, output_path): + try: + import gdown + except ModuleNotFoundError: + raise AssertionError("This example requires `gdown` to be installed. Please install using `pip install gdown`") + + url = f"https://drive.google.com/uc?export=download&confirm=t&id={file_id}" + gdown.download(url, output_path, quiet=False) + + +def download_if_does_not_exist(file_id, output_path): + if not os.path.exists(output_path): + _download_from_gdrive(file_id, output_path) + + # %% SENSOR_DATA_GDRIVE_ID = "1lGFTifpOrzBYT4Bl_ccLu_Kx0IDxM0Lv" PHANTOM_DATA_GDRIVE_ID = "1ZfSdJPe8nufZHz0U9IuwHR4chaOGAWO4" diff --git a/examples/us_bmode_linear_transducer/example_utils.py b/examples/us_bmode_linear_transducer/example_utils.py deleted file mode 100644 index f695769bf..000000000 --- a/examples/us_bmode_linear_transducer/example_utils.py +++ /dev/null @@ -1,20 +0,0 @@ -# %% [markdown] -# # B-Mode Linear Transducer Utilities -# Helper functions for downloading example data from Google Drive. - -import os - - -def download_from_gdrive(file_id, output_path): - try: - import gdown - except ModuleNotFoundError: - raise AssertionError("This example requires `gdown` to be installed. Please install using `pip install gdown`") - - url = f"https://drive.google.com/uc?export=download&confirm=t&id={file_id}" - gdown.download(url, output_path, quiet=False) - - -def download_if_does_not_exist(file_id, output_path): - if not os.path.exists(output_path): - download_from_gdrive(file_id, output_path) diff --git a/examples/us_bmode_phased_array/us_bmode_phased_array.py b/examples/us_bmode_phased_array.py similarity index 100% rename from examples/us_bmode_phased_array/us_bmode_phased_array.py rename to examples/us_bmode_phased_array.py diff --git a/examples/us_defining_transducer/us_defining_transducer.py b/examples/us_defining_transducer.py similarity index 100% rename from examples/us_defining_transducer/us_defining_transducer.py rename to examples/us_defining_transducer.py diff --git a/uv.lock b/uv.lock index 535921291..158c642a1 100644 --- a/uv.lock +++ b/uv.lock @@ -52,6 +52,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/9f/fa9971d2a0c6fef64c87ba362a493a4f230eff4ea8dfb9f4c7cbdf71892e/apeye_core-1.1.5-py3-none-any.whl", hash = "sha256:dc27a93f8c9e246b3b238c5ea51edf6115ab2618ef029b9f2d9a190ec8228fbf", size = 99286 }, ] +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548 }, +] + [[package]] name = "autodocsumm" version = "0.2.14" @@ -524,6 +533,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708 }, ] +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024 }, +] + [[package]] name = "filelock" version = "3.25.2" @@ -756,6 +774,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032 }, +] + +[[package]] +name = "jupytext" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/a5/80c02f307c8ce863cb33e27daf049315e9d96979e14eead700923b5ec9cc/jupytext-1.19.1.tar.gz", hash = "sha256:82587c07e299173c70ed5e8ec7e75183edf1be289ed518bab49ad0d4e3d5f433", size = 4307829 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/5a/736dd2f4535dbf3bf26523f9158c011389ef88dd06ec2eef67fd744f1c7b/jupytext-1.19.1-py3-none-any.whl", hash = "sha256:d8975035155d034bdfde5c0c37891425314b7ea8d3a6c4b5d18c294348714cd9", size = 170478 }, +] + [[package]] name = "k-wave-python" version = "0.5.0rc1" @@ -774,6 +849,7 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "jupytext" }, { name = "pre-commit" }, ] docs = [ @@ -810,6 +886,7 @@ requires-dist = [ { name = "gdown", marker = "extra == 'example'", specifier = "==5.2.0" }, { name = "h5py", specifier = "==3.15.1" }, { name = "jaxtyping", specifier = "==0.3.2" }, + { name = "jupytext", marker = "extra == 'dev'" }, { name = "matplotlib", specifier = "==3.10.3" }, { name = "numpy", specifier = ">=1.22.2,<2.3.0" }, { name = "opencv-python", specifier = "==4.11.0.86" }, @@ -965,6 +1042,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044 }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -1102,6 +1191,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/b9/59e120d24a2ec5fc2d30646adb2efb4621aab3c6d83d66fb2a7a182db032/matplotlib-3.10.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb73d8aa75a237457988f9765e4dfe1c0d2453c5ca4eabc897d4309672c8e014", size = 8594298 }, ] +[[package]] +name = "mdit-py-plugins" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205 }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + [[package]] name = "mistune" version = "3.2.0" @@ -1193,6 +1303,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268 }, ] +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + [[package]] name = "networkx" version = "3.4.2" @@ -1616,6 +1741,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 }, ] +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, +] + [[package]] name = "requests" version = "2.32.5" @@ -1636,6 +1775,128 @@ socks = [ { name = "pysocks" }, ] +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490 }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751 }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696 }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136 }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699 }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022 }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522 }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579 }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305 }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503 }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322 }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792 }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901 }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823 }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157 }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676 }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938 }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932 }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830 }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033 }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828 }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683 }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583 }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496 }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669 }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011 }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406 }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024 }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069 }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086 }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053 }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763 }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951 }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622 }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492 }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080 }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680 }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589 }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289 }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737 }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120 }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782 }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463 }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868 }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887 }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904 }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945 }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783 }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021 }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589 }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025 }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895 }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799 }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731 }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027 }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020 }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139 }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224 }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645 }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443 }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375 }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850 }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812 }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841 }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149 }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843 }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507 }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949 }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790 }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217 }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806 }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341 }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768 }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099 }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192 }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080 }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841 }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670 }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005 }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112 }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049 }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661 }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606 }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126 }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371 }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298 }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604 }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391 }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868 }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747 }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795 }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330 }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194 }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340 }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765 }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834 }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470 }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630 }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148 }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030 }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570 }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532 }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292 }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128 }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542 }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004 }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063 }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099 }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177 }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015 }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736 }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981 }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782 }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191 }, +] + [[package]] name = "ruamel-yaml" version = "0.19.1" @@ -2095,6 +2356,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374 }, ] +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From d645b78cfbc5d4c2e779479184227ec2915c2c34 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 21:07:25 -0700 Subject: [PATCH 14/21] Fix AS import (kspaceFirstOrderAS, not kspaceFirstOrder2D), update CI workflows - Fix: import kspaceFirstOrderASC from kwave.kspaceFirstOrderAS (not 2D) - generate-notebooks: flat glob, explicit push to master, contents:write permission - run-examples: exclude AS examples (legacy API), flat find with maxdepth 1 Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/generate-notebooks.yml | 12 +++++------- .github/workflows/run-examples.yml | 8 ++++---- examples/at_circular_piston_AS.py | 2 +- examples/at_focused_bowl_AS.py | 2 +- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/workflows/generate-notebooks.yml b/.github/workflows/generate-notebooks.yml index e661c14ed..9f3ddac20 100644 --- a/.github/workflows/generate-notebooks.yml +++ b/.github/workflows/generate-notebooks.yml @@ -3,11 +3,13 @@ name: Generate Notebooks on: push: branches: [master] - paths: ['examples/**/*.py'] + paths: ['examples/*.py'] jobs: generate: runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v4 @@ -21,13 +23,9 @@ jobs: - name: Generate notebooks run: | mkdir -p notebooks - for py in examples/**/*.py examples/*.py; do + for py in examples/*.py; do [ -f "$py" ] || continue - # Skip utilities and non-example files basename=$(basename "$py") - case "$basename" in - example_utils.py|__init__.py) continue ;; - esac jupytext --to notebook "$py" --output "notebooks/${basename%.py}.ipynb" done @@ -40,5 +38,5 @@ jobs: echo "No notebook changes" else git commit -m "Auto-generate notebooks from examples [skip ci]" - git push + git push origin HEAD:master fi diff --git a/.github/workflows/run-examples.yml b/.github/workflows/run-examples.yml index 08c1c327c..5d9d09b45 100644 --- a/.github/workflows/run-examples.yml +++ b/.github/workflows/run-examples.yml @@ -14,12 +14,12 @@ jobs: - uses: actions/checkout@v4 - id: find-examples run: | - # Find example scripts (exclude utilities and legacy-only examples) - # Exclude: utilities, checkpointing (legacy), time-reversal (needs C++ binary for TimeReversal class) - EXAMPLES=$(find examples -name "*.py" -not -path "*/\.*" \ - -not -name "example_utils.py" \ + # Exclude: checkpointing (legacy), time-reversal (TimeReversal needs C++ binary), + # axisymmetric (legacy kspaceFirstOrderASC, not yet in new API) + EXAMPLES=$(find examples -maxdepth 1 -name "*.py" \ -not -name "checkpoint.py" \ -not -name "*TR_*" \ + -not -name "*_AS.py" \ | sort | jq -R -s -c 'split("\n")[:-1]') echo "examples=$EXAMPLES" >> "$GITHUB_OUTPUT" diff --git a/examples/at_circular_piston_AS.py b/examples/at_circular_piston_AS.py index dad8ec769..652b77b56 100644 --- a/examples/at_circular_piston_AS.py +++ b/examples/at_circular_piston_AS.py @@ -23,7 +23,7 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspaceFirstOrderASC +from kwave.kspaceFirstOrderAS import kspaceFirstOrderASC from kwave.options.simulation_execution_options import SimulationExecutionOptions from kwave.options.simulation_options import SimulationOptions from kwave.utils.filters import extract_amp_phase diff --git a/examples/at_focused_bowl_AS.py b/examples/at_focused_bowl_AS.py index faac6096e..2130cce88 100644 --- a/examples/at_focused_bowl_AS.py +++ b/examples/at_focused_bowl_AS.py @@ -11,7 +11,7 @@ from kwave.kmedium import kWaveMedium from kwave.ksensor import kSensor from kwave.ksource import kSource -from kwave.kspaceFirstOrder2D import kspaceFirstOrderASC +from kwave.kspaceFirstOrderAS import kspaceFirstOrderASC from kwave.options.simulation_execution_options import SimulationExecutionOptions from kwave.options.simulation_options import SimulationOptions from kwave.utils.filters import extract_amp_phase From c4d220a20b01c6ad20fbb07fc208f4974562363e Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 21:27:50 -0700 Subject: [PATCH 15/21] Fix example shape issues for python backend compatibility - Squeeze single-sensor data before plotting (ivp_photoacoustic_waveforms) - Fix sensor sum axis: axis=0 sums across sensors for (n_sensors, n_time) shape (sd_focussed_detector_2D, sd_focussed_detector_3D, sd_directivity_modelling_2D) - Add missing kgrid.makeTime() call (pr_3D_FFT_planar_sensor) - Use full grid size (PML inside) instead of subtracting PML (pr_3D_FFT_planar_sensor) - Skip save_only test when KWAVE_BACKEND=python (unified_entry_point_demo) 11/20 examples now pass with KWAVE_BACKEND=python. Remaining failures are in kWaveArray/transducer examples that need library-level fixes. Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/ivp_photoacoustic_waveforms.py | 6 ++++-- examples/pr_3D_FFT_planar_sensor.py | 5 ++++- examples/sd_directivity_modelling_2D.py | 2 +- examples/sd_focussed_detector_2D.py | 4 ++-- examples/sd_focussed_detector_3D.py | 4 ++-- examples/unified_entry_point_demo.py | 26 ++++++++++++++----------- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/examples/ivp_photoacoustic_waveforms.py b/examples/ivp_photoacoustic_waveforms.py index 736da94c2..df9e29223 100644 --- a/examples/ivp_photoacoustic_waveforms.py +++ b/examples/ivp_photoacoustic_waveforms.py @@ -116,8 +116,10 @@ # plot the simulations t_sc, t_scale, t_prefix, _ = scale_SI(t_end) _, ax1 = plt.subplots() -ax1.plot(np.squeeze(kgrid2.t_array * t_scale), sensor_data_2D["p"] / np.max(np.abs(sensor_data_2D["p"])), "r-", label="2D") -ax1.plot(np.squeeze(kgrid3.t_array * t_scale), sensor_data_3D["p"] / np.max(np.abs(sensor_data_3D["p"])), "k-", label="3D") +p_2D = sensor_data_2D["p"].squeeze() +p_3D = sensor_data_3D["p"].squeeze() +ax1.plot(np.squeeze(kgrid2.t_array * t_scale), p_2D / np.max(np.abs(p_2D)), "r-", label="2D") +ax1.plot(np.squeeze(kgrid3.t_array * t_scale), p_3D / np.max(np.abs(p_3D)), "k-", label="3D") ax1.set(xlabel=f"Time [{t_prefix}s]", ylabel="Recorded Pressure [au]") ax1.grid(True) ax1.legend(loc="upper right") diff --git a/examples/pr_3D_FFT_planar_sensor.py b/examples/pr_3D_FFT_planar_sensor.py index e264f0eb5..32e14f5fa 100644 --- a/examples/pr_3D_FFT_planar_sensor.py +++ b/examples/pr_3D_FFT_planar_sensor.py @@ -29,13 +29,16 @@ def main(): # create the computational grid PML_size = 10 # size of the PML in grid points - N = Vector([32, 64, 64]) * scale - 2 * PML_size # number of grid points + N = Vector([32, 64, 64]) * scale # number of grid points (PML is inside the grid) d = Vector([0.2e-3, 0.2e-3, 0.2e-3]) / scale # grid point spacing [m] kgrid = kWaveGrid(N, d) # define the properties of the propagation medium medium = kWaveMedium(sound_speed=1500) # [m/s] + # create the time array + kgrid.makeTime(medium.sound_speed) + # create initial pressure distribution using makeBall ball_magnitude = 10 # [Pa] ball_radius = 3 * scale # [grid points] diff --git a/examples/sd_directivity_modelling_2D.py b/examples/sd_directivity_modelling_2D.py index de819ca9c..bc2fa5f32 100644 --- a/examples/sd_directivity_modelling_2D.py +++ b/examples/sd_directivity_modelling_2D.py @@ -80,7 +80,7 @@ backend="cpp", device="cpu", ) - single_element_data[:, source_loop] = sensor_data["p"].sum(axis=1) + single_element_data[:, source_loop] = sensor_data["p"].sum(axis=0) # %% plt.figure() diff --git a/examples/sd_focussed_detector_2D.py b/examples/sd_focussed_detector_2D.py index 790a8e2e8..1f9a706ce 100644 --- a/examples/sd_focussed_detector_2D.py +++ b/examples/sd_focussed_detector_2D.py @@ -71,8 +71,8 @@ # %% ## Visualize recorded data -sensor_output1 = np.sum(sensor_data1["p"], axis=1) / np.sum(sensor.mask) -sensor_output2 = np.sum(sensor_data2["p"], axis=1) / np.sum(sensor.mask) +sensor_output1 = np.sum(sensor_data1["p"], axis=0) / np.sum(sensor.mask) +sensor_output2 = np.sum(sensor_data2["p"], axis=0) / np.sum(sensor.mask) t_sc, t_scale, t_prefix, _ = scale_SI(t_end) t_array = kgrid.t_array.squeeze() * t_scale diff --git a/examples/sd_focussed_detector_3D.py b/examples/sd_focussed_detector_3D.py index 3ed614d74..adde71f30 100644 --- a/examples/sd_focussed_detector_3D.py +++ b/examples/sd_focussed_detector_3D.py @@ -66,7 +66,7 @@ ) # average the data recorded at each grid point to simulate the measured signal from a single element focused detector -sensor_data1 = np.sum(sensor_data1["p"], axis=1) +sensor_data1 = np.sum(sensor_data1["p"], axis=0) # %% # place the second point source off axis @@ -87,7 +87,7 @@ ) # average the data recorded at each grid point to simulate the measured signal from a single element focused detector -sensor_data2 = np.sum(sensor_data2["p"], axis=1) +sensor_data2 = np.sum(sensor_data2["p"], axis=0) # %% # Combine arrays as in MATLAB: sensor.mask + source1 + source2 diff --git a/examples/unified_entry_point_demo.py b/examples/unified_entry_point_demo.py index b9d83ddfb..2f3710719 100644 --- a/examples/unified_entry_point_demo.py +++ b/examples/unified_entry_point_demo.py @@ -48,19 +48,23 @@ print(f" Sensor data: {result['p'].shape}") # %% Example 4: C++ save_only (writes HDF5 for cluster submission) +import os import tempfile -print("\nSaving HDF5 for C++ binary...") -result = kspaceFirstOrder( - kgrid, - medium, - source, - sensor, - backend="cpp", - save_only=True, - data_path=tempfile.mkdtemp(), -) -print(f" Input file: {result['input_file']}") +if os.environ.get("KWAVE_BACKEND", "").lower() == "python": + print("\nSkipping C++ save_only example (KWAVE_BACKEND=python)...") +else: + print("\nSaving HDF5 for C++ binary...") + result = kspaceFirstOrder( + kgrid, + medium, + source, + sensor, + backend="cpp", + save_only=True, + data_path=tempfile.mkdtemp(), + ) + print(f" Input file: {result['input_file']}") # %% Example 5: Migrating from legacy options print("\nMigrating from legacy options...") From 2c615f46f5d664d96433eef32803813775c3dbf8 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 21:54:17 -0700 Subject: [PATCH 16/21] Fix .T transpose, PML inside grid, validate_source getattr for NotATransducer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove .T on sensor_data["p"] — python backend already returns (n_sensors, n_time) (at_array_as_sensor, at_circular_piston_3D, at_focused_bowl_3D, at_focused_annular_array_3D, pr_3D_FFT_planar_sensor) - Fix PML inside: use full grid size, don't subtract 2*PML (pr_2D_FFT_line_sensor, at_circular_piston_3D) - validate_source: use getattr() for all source attributes so NotATransducer (which lacks p0, p, p_mask, u_mask) doesn't raise AttributeError 17/20 examples now pass with KWAVE_BACKEND=python. Remaining: - us_* (3): still on legacy API, need NotATransducer python backend support - at_array_as_source: ffmpeg/mp4 dependency Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/at_array_as_sensor.py | 5 +- examples/at_circular_piston_3D.py | 4 +- examples/at_focused_annular_array_3D.py | 2 +- examples/at_focused_bowl_3D.py | 2 +- examples/pr_2D_FFT_line_sensor.py | 4 +- examples/pr_3D_FFT_planar_sensor.py | 2 +- kwave/kspaceFirstOrder.py | 109 +++++++++++++++++++++--- kwave/solvers/validation.py | 10 +-- 8 files changed, 110 insertions(+), 28 deletions(-) diff --git a/examples/at_array_as_sensor.py b/examples/at_array_as_sensor.py index 0f5e233a3..8a2f03da0 100644 --- a/examples/at_array_as_sensor.py +++ b/examples/at_array_as_sensor.py @@ -65,14 +65,13 @@ def main(): output = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp", device="gpu") # Reorder the sensor data returned by k-Wave to match the order of the elements in the array _, _, reorder_index = cart2grid(kgrid, element_pos) - sensor_data_point = reorder_binary_sensor_data(output["p"].T, reorder_index=reorder_index) + sensor_data_point = reorder_binary_sensor_data(output["p"], reorder_index=reorder_index) # assign binary mask from karray to the source mask sensor.mask = karray.get_array_binary_mask(kgrid) output = kspaceFirstOrder(kgrid, medium, source, sensor, backend="cpp", device="gpu") - sensor_data = output["p"].T - combined_sensor_data = karray.combine_sensor_data(kgrid, sensor_data) + combined_sensor_data = karray.combine_sensor_data(kgrid, output["p"]) # %% Visualization # create pml mask (default size of 20 grid points) diff --git a/examples/at_circular_piston_3D.py b/examples/at_circular_piston_3D.py index ebd1510a6..6581f0368 100644 --- a/examples/at_circular_piston_3D.py +++ b/examples/at_circular_piston_3D.py @@ -128,13 +128,13 @@ medium, source, sensor, - pml_size="auto", + pml_size=10, backend="cpp", device="gpu", ) # extract amplitude from the sensor data -amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") +amp, _, _ = extract_amp_phase(sensor_data["p"], 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") # reshape data amp = np.reshape(amp, (Nx - 1, Ny), order="F") diff --git a/examples/at_focused_annular_array_3D.py b/examples/at_focused_annular_array_3D.py index ab22bf1b5..4093601b9 100644 --- a/examples/at_focused_annular_array_3D.py +++ b/examples/at_focused_annular_array_3D.py @@ -144,7 +144,7 @@ ) # extract amplitude from the sensor data -amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") +amp, _, _ = extract_amp_phase(sensor_data["p"], 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") # reshape data amp = np.reshape(amp, (Nx - (source_x_offset + 1), Ny), order="F") diff --git a/examples/at_focused_bowl_3D.py b/examples/at_focused_bowl_3D.py index 476e0f5a6..2afd36a1d 100644 --- a/examples/at_focused_bowl_3D.py +++ b/examples/at_focused_bowl_3D.py @@ -141,7 +141,7 @@ ) # extract amplitude from the sensor data -amp, _, _ = extract_amp_phase(sensor_data["p"].T, 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") +amp, _, _ = extract_amp_phase(sensor_data["p"], 1.0 / kgrid.dt, source_f0, dim=1, fft_padding=1, window="Rectangular") # reshape data amp = np.reshape(amp, (Nx - (source_x_offset + 2), Ny), order="F") diff --git a/examples/pr_2D_FFT_line_sensor.py b/examples/pr_2D_FFT_line_sensor.py index 0a34c9e56..246e8a999 100644 --- a/examples/pr_2D_FFT_line_sensor.py +++ b/examples/pr_2D_FFT_line_sensor.py @@ -28,7 +28,7 @@ def main(): # create the computational grid PML_size = 20 # size of the PML in grid points - N = Vector([128, 256]) - 2 * PML_size # number of grid points + N = Vector([128, 256]) # number of grid points (PML is inside the grid) d = Vector([0.1e-3, 0.1e-3]) # grid point spacing [m] kgrid = kWaveGrid(N, d) @@ -100,7 +100,7 @@ def main(): # plot the initial pressure and sensor distribution fig, ax = plt.subplots(1, 1) im = ax.imshow( - p0 + sensor.mask[PML_size:-PML_size, PML_size:-PML_size] * disc_magnitude, + p0 + sensor.mask * disc_magnitude, extent=[kgrid.y_vec.min() * 1e3, kgrid.y_vec.max() * 1e3, kgrid.x_vec.max() * 1e3, kgrid.x_vec.min() * 1e3], vmin=-disc_magnitude, vmax=disc_magnitude, diff --git a/examples/pr_3D_FFT_planar_sensor.py b/examples/pr_3D_FFT_planar_sensor.py index 32e14f5fa..115412697 100644 --- a/examples/pr_3D_FFT_planar_sensor.py +++ b/examples/pr_3D_FFT_planar_sensor.py @@ -67,7 +67,7 @@ def main(): backend="cpp", device="gpu", ) - sensor_data = sensor_data["p"].T + sensor_data = sensor_data["p"] # reshape sensor data to y, z, t sensor_data_rs = sensor_data.reshape(N[1], N[2], kgrid.Nt) diff --git a/kwave/kspaceFirstOrder.py b/kwave/kspaceFirstOrder.py index b65577798..fc69af5b0 100644 --- a/kwave/kspaceFirstOrder.py +++ b/kwave/kspaceFirstOrder.py @@ -11,6 +11,7 @@ from kwave.ksensor import kSensor from kwave.ksource import kSource from kwave.ktransducer import NotATransducer +from kwave.utils.matrix import expand_matrix from kwave.utils.pml import get_optimal_pml_size @@ -27,6 +28,58 @@ def _normalize_pml(val, ndim, name="pml_size"): return t + (t[-1],) * (ndim - len(t)) +def _is_binary_mask(mask, ndim): + """True if mask is a grid-shaped binary array (not Cartesian coordinates).""" + arr = np.asarray(mask) + if arr.ndim == 2 and arr.shape[0] == ndim: + return False # Cartesian: (ndim, n_points) + return True + + +def _expand_for_pml_outside(kgrid, medium, source, sensor, pml_size): + """Expand grid/medium/source/sensor so PML sits outside the user domain.""" + ndim = kgrid.dim + new_N = tuple(int(n) + 2 * int(p) for n, p in zip(kgrid.N, pml_size)) + expanded_kgrid = kWaveGrid(new_N, kgrid.spacing) + expanded_kgrid.setTime(kgrid.Nt, kgrid.dt) + + # Medium: edge-extend non-scalar arrays + expanded_medium = copy.copy(medium) + for attr in ("sound_speed", "density", "alpha_coeff", "BonA"): + val = getattr(expanded_medium, attr, None) + if val is not None and np.atleast_1d(val).size > 1: + setattr(expanded_medium, attr, expand_matrix(np.atleast_1d(val), list(pml_size))) + + # Source: zero-pad spatial arrays + expanded_source = copy.copy(source) + for attr in ("p0", "p_mask", "u_mask"): + val = getattr(expanded_source, attr, None) + if val is not None and np.atleast_1d(val).size > 1: + setattr(expanded_source, attr, expand_matrix(np.atleast_1d(val), list(pml_size), 0)) + + # Sensor: zero-pad binary masks, leave Cartesian unchanged + expanded_sensor = sensor + if sensor is not None and getattr(sensor, "mask", None) is not None: + if _is_binary_mask(sensor.mask, ndim): + expanded_sensor = copy.copy(sensor) + expanded_sensor.mask = expand_matrix(np.atleast_1d(sensor.mask), list(pml_size), 0) + + return expanded_kgrid, expanded_medium, expanded_source, expanded_sensor + + +def _strip_pml(result, pml_size, ndim): + """Remove PML padding from full-grid fields in the result dict.""" + slices = tuple(slice(int(p), -int(p)) for p in pml_size[:ndim]) + full_grid_suffixes = ("_final", "_max", "_min", "_rms") + stripped = {} + for key, val in result.items(): + if isinstance(val, np.ndarray) and val.ndim == ndim and any(key.endswith(s) for s in full_grid_suffixes): + stripped[key] = val[slices] + else: + stripped[key] = val + return stripped + + @typechecker def kspaceFirstOrder( kgrid: kWaveGrid, @@ -36,6 +89,7 @@ def kspaceFirstOrder( *, pml_size: Union[int, tuple, str] = 20, pml_alpha: Union[float, tuple] = 2.0, + pml_inside: bool = False, use_sg: bool = True, use_kspace: bool = True, smooth_p0: bool = True, @@ -69,6 +123,13 @@ def kspaceFirstOrder( analysis. Default ``20``. pml_alpha: PML absorption coefficient (Nepers per grid point). Scalar or per-dimension tuple. Default ``2.0``. + pml_inside: When ``False`` (default), the grid is automatically + expanded by ``2 * pml_size`` so the PML sits outside the user + domain; full-grid output fields (``_final``, ``_max``, etc.) + are cropped back to the original size. When ``True``, the PML + occupies the outermost grid points of the user-supplied grid, + which saves memory but means PML absorption will modify field + values near the domain boundary. use_sg: Use a staggered grid for velocity fields. Default ``True``. use_kspace: Apply the k-space correction to the time-stepping scheme. Default ``True``. @@ -119,10 +180,33 @@ def kspaceFirstOrder( validate_simulation(kgrid, medium, source, sensor, pml_size=pml_size) + # --- Shared pre-processing (both backends) --- + + # Expand grid when PML sits outside the user domain + if pml_inside: + warnings.warn( + f"pml_inside=True: the outermost {pml_size} grid points per side will be used for PML absorption. " + "Sources, sensors, and medium properties near the boundary will be affected. " + "Set pml_inside=False (default) to expand the grid automatically instead.", + stacklevel=2, + ) + if not pml_inside: + kgrid, medium, source, sensor = _expand_for_pml_outside(kgrid, medium, source, sensor, pml_size) + + # Smooth initial pressure (after expansion so Blackman window covers PML transition) + if smooth_p0 and source.p0 is not None and kgrid.dim >= 2: + from kwave.utils.filters import smooth + + source = copy.copy(source) + grid_shape = tuple(int(n) for n in kgrid.N) + source.p0 = smooth(np.asarray(source.p0, dtype=float).reshape(grid_shape, order="F"), restore_max=True) + + # --- Backend dispatch --- + if backend == "python": from kwave.solvers.kspace_solver import Simulation - return Simulation( + result = Simulation( kgrid, medium, source, @@ -130,12 +214,12 @@ def kspaceFirstOrder( device=device, use_sg=use_sg, use_kspace=use_kspace, - smooth_p0=smooth_p0, + smooth_p0=False, # already handled above pml_size=pml_size, pml_alpha=pml_alpha, ).run() - if backend == "cpp": + elif backend == "cpp": from kwave.solvers.cpp_simulation import CppSimulation if not use_kspace: @@ -163,19 +247,18 @@ def kspaceFirstOrder( sensor = copy.copy(sensor) sensor.mask, _, _ = cart2grid(kgrid, mask_arr) - # Apply p0 smoothing before HDF5 serialization (matches MATLAB legacy path) - if smooth_p0 and source.p0 is not None and kgrid.dim >= 2: - from kwave.utils.filters import smooth - - source = copy.copy(source) - grid_shape = tuple(int(n) for n in kgrid.N) - # Fortran (column-major) layout matches MATLAB convention; smooth() is order-agnostic (uses FFT on shape) - source.p0 = smooth(np.asarray(source.p0, dtype=float).reshape(grid_shape, order="F"), restore_max=True) - cpp_sim = CppSimulation(kgrid, medium, source, sensor, pml_size=pml_size, pml_alpha=pml_alpha, use_sg=use_sg) if save_only: if data_path is None: raise ValueError("data_path must be provided when save_only=True " "(the HDF5 files must persist for cluster submission).") input_file, output_file = cpp_sim.prepare(data_path=data_path) return {"input_file": input_file, "output_file": output_file} - return cpp_sim.run(device=device, num_threads=num_threads, device_num=device_num, quiet=quiet, debug=debug, data_path=data_path) + result = cpp_sim.run(device=device, num_threads=num_threads, device_num=device_num, quiet=quiet, debug=debug, data_path=data_path) + + # --- Shared post-processing --- + + # Strip PML from full-grid output fields when PML was outside the user domain + if not pml_inside and isinstance(result, dict): + result = _strip_pml(result, pml_size, kgrid.dim) + + return result diff --git a/kwave/solvers/validation.py b/kwave/solvers/validation.py index a628b1a16..38f53a73f 100644 --- a/kwave/solvers/validation.py +++ b/kwave/solvers/validation.py @@ -72,21 +72,21 @@ def validate_cfl(kgrid, medium): def validate_source(source, kgrid): grid_size = int(np.prod(kgrid.N)) - if source.p0 is not None: + if getattr(source, "p0", None) is not None: p0 = np.asarray(source.p0) if p0.size != grid_size and p0.size != 0: raise ValueError(f"source.p0 has {p0.size} elements but grid has {grid_size} points.") - if source.p is not None and source.p_mask is None: + if getattr(source, "p", None) is not None and getattr(source, "p_mask", None) is None: raise ValueError("source.p requires source.p_mask to be set.") - if source.p_mask is not None: + if getattr(source, "p_mask", None) is not None: mask = np.asarray(source.p_mask) is_cartesian = mask.ndim == 2 and mask.shape[0] == kgrid.dim if not is_cartesian and mask.size != grid_size: raise ValueError(f"source.p_mask has {mask.size} elements but grid has {grid_size} points.") for vel in ["ux", "uy", "uz"]: - if getattr(source, vel, None) is not None and source.u_mask is None: + if getattr(source, vel, None) is not None and getattr(source, "u_mask", None) is None: raise ValueError(f"source.{vel} requires source.u_mask to be set.") - if source.u_mask is not None: + if getattr(source, "u_mask", None) is not None: mask = np.asarray(source.u_mask) is_cartesian = mask.ndim == 2 and mask.shape[0] == kgrid.dim if not is_cartesian and mask.size != grid_size: From f2bd34d4890d86e968de095217b1bab6ba2b18c6 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 21:57:36 -0700 Subject: [PATCH 17/21] Add CLAUDE.md and document memory layout / indexing migration strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md: project overview, architecture, conventions, commands - release-strategy.md: detailed F-order → C-order migration plan for v1.0 with explicit boundary layers (HDF5 serialization, MATLAB interop) - Document 1-based → 0-based indexing migration path Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 89 +++++++++++++++++++++++++++++++++++++++ plans/release-strategy.md | 30 +++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..92ebad62f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,89 @@ +# CLAUDE.md — k-wave-python + +## What this project is + +k-Wave-python is a Python port of [k-Wave](http://www.k-wave.org/), a MATLAB toolbox for time-domain acoustic and ultrasound simulations using the k-space pseudospectral method. It supports 1D, 2D, and 3D simulations with optional GPU acceleration via CuPy. + +## Key commands + +```bash +uv run pytest tests/ # Run all tests +uv run pytest tests/test_native_solver.py # Run solver tests +uv run python run_examples.py # Run all examples (python backend) +uv run python examples/ivp_1D_simulation.py # Run a single example +``` + +## Architecture + +- **Entry point:** `kwave/kspaceFirstOrder.py` — `kspaceFirstOrder()` is the unified API +- **Python solver:** `kwave/solvers/kspace_solver.py` — `Simulation` class (setup/step/run) +- **C++ backend:** `kwave/solvers/cpp_simulation.py` — HDF5 serialization + binary execution +- **Validation:** `kwave/solvers/validation.py` +- **Legacy bridge:** `kwave/solvers/native.py` — minimal wrapper for deprecated `kspaceFirstOrder2D/3D` +- **MATLAB interop:** `simulate_from_dicts()` in `kspace_solver.py`, shim in k-wave-cupy repo + +## Critical conventions + +### Memory layout: Fortran vs C order + +k-Wave is a MATLAB project. MATLAB uses **column-major (Fortran) order**. NumPy defaults to **row-major (C) order**. + +**Current state:** The solver uses `order="F"` extensively for MATLAB compatibility. This is being migrated: +- **Internal solver logic** (FFT, PML, stepping) is order-agnostic +- **Boundary layers** (HDF5 serialization, MATLAB interop) must use F-order +- **Goal for v1.0:** C-order internally, F-order only at boundaries + +When modifying array reshaping/flattening code, always check whether `order="F"` is needed for correctness or is legacy MATLAB convention. + +### Indexing: 0-based vs 1-based + +MATLAB uses 1-based indexing. Two places where this matters: + +1. **`sensor.record_start_index`** — uses 1-based (MATLAB convention). Converted to 0-based internally in `_setup_sensor_mask`. +2. **C++ HDF5 format** — sensor/source indices are 1-based. The `+1` in `cpp_simulation.py` is intentional. +3. **`matlab_find()`** — returns 1-based indices. Used by `kWaveArray`. + +### k-wave-cupy interop + +The k-wave-cupy repo contains the MATLAB wrapper (`kspaceFirstOrderPy.m`) that calls Python via `py.kWavePy.simulate_from_dicts()`. The `kWavePy.py` shim in that repo re-exports from k-wave-python. + +- `simulate_from_dicts(device=...)` — NOT `backend=`. The parameter was renamed. +- MATLAB passes dicts with F-order arrays and 1-based indices +- The shim handles `pml_inside=False` expansion externally + +### Backend vs Device naming + +- `backend` = `"python"` or `"cpp"` (which engine runs the simulation) +- `device` = `"cpu"` or `"gpu"` (hardware target) + +These are separate concerns. `kspaceFirstOrder(backend="python", device="gpu")` runs the Python solver on GPU via CuPy. + +## Environment variables + +- `KWAVE_BACKEND` — Override backend for all `kspaceFirstOrder()` calls (used by CI) +- `KWAVE_DEVICE` — Override device for all `kspaceFirstOrder()` calls (used by CI) + +## Examples + +Examples are `.py` files with `# %%` cell markers (percent format). They work as: +- Plain scripts: `python examples/foo.py` +- Interactive notebooks: Open in VS Code/JupyterLab +- Jupyter notebooks: Generated via `jupytext --to notebook` + +Source of truth is always the `.py` file. Never edit `.ipynb` directly. + +## Testing + +```bash +uv run pytest tests/test_native_solver.py # Solver + physics tests +uv run pytest tests/test_validation.py # Input validation +uv run pytest tests/test_kspaceFirstOrder.py # Entry point tests +uv run pytest tests/test_compat.py # Legacy options migration +``` + +## Style + +- Simple > less code > fast +- No backward compatibility aliases — clean breaks +- `quiet=True` to silence, `debug=True` for verbose (not `verbose=True`) +- Use `uv run` for all tools (pytest, python, etc.) diff --git a/plans/release-strategy.md b/plans/release-strategy.md index 9b323864b..132b59bb0 100644 --- a/plans/release-strategy.md +++ b/plans/release-strategy.md @@ -141,6 +141,36 @@ warnings.warn( **Impact:** ~5700 lines → ~1200 lines (-79%) +### Memory Layout and Indexing Migration (v1.0.0) + +k-Wave is a MATLAB project. Two MATLAB conventions pervade the Python codebase: + +1. **Fortran (column-major) memory layout** — `order="F"` appears ~22 times in the solver alone. Arrays are flattened/reshaped with `order="F"` to match MATLAB's column-major storage. NumPy defaults to C (row-major). + +2. **1-based indexing** — The C++ binary expects 1-based sensor/source indices in HDF5. `matlab_find()` returns 1-based indices. `sensor.record_start_index` uses 1-based convention. + +**Current state:** Both conventions are handled at the boundaries: +- `kspace_solver.py`: Uses `order="F"` in `_expand_to_grid`, `_build_source_op`, sensor mask extraction, and field recording. The physics (FFT, PML, stepping) is order-agnostic. +- `cpp_simulation.py`: Converts to 1-based Fortran-order indices for HDF5 serialization. +- `kWaveArray.combine_sensor_data`: Uses `matlab_find` (1-based) and Fortran-order flattening. + +**Migration strategy for v1.0.0:** + +| Layer | Current | Target | Migration | +|-------|---------|--------|-----------| +| **Python solver** (`kspace_solver.py`) | F-order throughout | C-order internally, F-order at boundaries only | Audit all `order="F"` — keep only where MATLAB compat requires it (interop, HDF5). Internal field storage uses default C-order. | +| **C++ HDF5 serialization** (`cpp_simulation.py`) | F-order + 1-based | Keep as-is (binary expects this) | This is a fixed boundary — always convert at the serialization layer. | +| **MATLAB interop** (`simulate_from_dicts`, `kWavePy.py`) | F-order dicts from MATLAB | Convert at entry point | `simulate_from_dicts` converts F→C on input, C→F on output. Single conversion boundary. | +| **kWaveArray** | `matlab_find` (1-based), F-order | 0-based, C-order | Rewrite `combine_sensor_data` and `get_distributed_source_signal` to use `np.where` (0-based) and C-order. | +| **sensor.record_start_index** | 1-based (MATLAB convention) | 0-based | Accept both, deprecate 1-based in v0.7, require 0-based in v1.0. | + +**Principle:** Python-native (C-order, 0-based) everywhere internally. MATLAB compatibility at two explicit boundaries: (1) `simulate_from_dicts` for k-wave-cupy interop, (2) `cpp_simulation._write_hdf5` for the C++ binary. + +**Testing:** Add a `test_memory_layout.py` that verifies: +- `Simulation` produces identical results with C-order and F-order input arrays +- `simulate_from_dicts` correctly converts F→C→F round-trip +- `cpp_simulation` writes correct 1-based F-order HDF5 regardless of internal layout + --- ## Phase 3.5: v0.7.0 - CLI (`kwp`) From 5bccedd5d5edf1513a1a01fcb2e21d09b58611cf Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sun, 22 Mar 2026 22:07:45 -0700 Subject: [PATCH 18/21] Fix _strip_pml to strip sensor time-series when sensor=None and pml_inside=False MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When sensor=None with pml_inside=False, the grid expands (64 → 104) and all grid points become sensors. _strip_pml now strips PML rows from time-series data using an interior boolean mask on the expanded grid. Co-Authored-By: Claude Opus 4.6 (1M context) --- kwave/kspaceFirstOrder.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/kwave/kspaceFirstOrder.py b/kwave/kspaceFirstOrder.py index fc69af5b0..4fdee716b 100644 --- a/kwave/kspaceFirstOrder.py +++ b/kwave/kspaceFirstOrder.py @@ -67,14 +67,33 @@ def _expand_for_pml_outside(kgrid, medium, source, sensor, pml_size): return expanded_kgrid, expanded_medium, expanded_source, expanded_sensor -def _strip_pml(result, pml_size, ndim): - """Remove PML padding from full-grid fields in the result dict.""" - slices = tuple(slice(int(p), -int(p)) for p in pml_size[:ndim]) +def _strip_pml(result, pml_size, ndim, expanded_grid_shape=None): + """Remove PML padding from full-grid fields in the result dict. + + Strips spatial fields (_final, _max, _min, _rms) using interior slices. + If expanded_grid_shape is given, also strips time-series that were + recorded over the full expanded grid (sensor=None with pml_inside=False). + """ + slices = tuple(slice(int(p), -int(p) if int(p) else None) for p in pml_size[:ndim]) full_grid_suffixes = ("_final", "_max", "_min", "_rms") stripped = {} for key, val in result.items(): if isinstance(val, np.ndarray) and val.ndim == ndim and any(key.endswith(s) for s in full_grid_suffixes): stripped[key] = val[slices] + elif expanded_grid_shape is not None and isinstance(val, np.ndarray) and val.ndim == 2: + expanded_numel = int(np.prod(expanded_grid_shape)) + if val.shape[0] == expanded_numel: + # Build interior boolean mask on expanded grid, apply to sensor axis + interior = np.ones(expanded_grid_shape, dtype=bool) + for ax in range(ndim): + sl = [slice(None)] * ndim + sl[ax] = slice(None, int(pml_size[ax])) + interior[tuple(sl)] = False + sl[ax] = slice(-int(pml_size[ax]), None) + interior[tuple(sl)] = False + stripped[key] = val[interior.flatten(order="F")] + else: + stripped[key] = val else: stripped[key] = val return stripped @@ -190,6 +209,7 @@ def kspaceFirstOrder( "Set pml_inside=False (default) to expand the grid automatically instead.", stacklevel=2, ) + sensor_was_none = sensor is None or (hasattr(sensor, "mask") and sensor.mask is None) if not pml_inside: kgrid, medium, source, sensor = _expand_for_pml_outside(kgrid, medium, source, sensor, pml_size) @@ -259,6 +279,7 @@ def kspaceFirstOrder( # Strip PML from full-grid output fields when PML was outside the user domain if not pml_inside and isinstance(result, dict): - result = _strip_pml(result, pml_size, kgrid.dim) + expanded_shape = tuple(int(n) for n in kgrid.N) if sensor_was_none else None + result = _strip_pml(result, pml_size, kgrid.dim, expanded_grid_shape=expanded_shape) return result From ae25822e9dacb00ed9de11775bdcafe020348818 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Mon, 23 Mar 2026 08:57:53 -0700 Subject: [PATCH 19/21] Fix merge artifacts: remove dead _is_binary_mask, sensor_was_none; restore @typechecker Post-merge cleanup: - Remove _is_binary_mask (superseded by _is_cartesian_mask from pml-inside branch) - Remove sensor_was_none (assigned but never read after merge resolution) - Restore @typechecker decorator on kspaceFirstOrder (catches wrong arg order) Co-Authored-By: Claude Opus 4.6 (1M context) --- kwave/kspaceFirstOrder.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/kwave/kspaceFirstOrder.py b/kwave/kspaceFirstOrder.py index ca89b45ce..6db351918 100644 --- a/kwave/kspaceFirstOrder.py +++ b/kwave/kspaceFirstOrder.py @@ -27,12 +27,6 @@ def _normalize_pml(val, ndim, name="pml_size"): return t + (t[-1],) * (ndim - len(t)) -def _is_binary_mask(mask, ndim): - """True if mask is a grid-shaped binary array (not Cartesian coordinates).""" - arr = np.asarray(mask) - if arr.ndim == 2 and arr.shape[0] == ndim: - return False # Cartesian: (ndim, n_points) - return True def _is_cartesian_mask(mask, ndim): """True if mask is a Cartesian coordinate array (ndim, n_points).""" arr = np.asarray(mask) @@ -89,6 +83,7 @@ def _strip_pml(result, pml_size, ndim): } +@typechecker def kspaceFirstOrder( kgrid: kWaveGrid, medium: kWaveMedium, @@ -198,7 +193,6 @@ def kspaceFirstOrder( "Set pml_inside=False (default) to expand the grid automatically instead.", stacklevel=2, ) - sensor_was_none = sensor is None or (hasattr(sensor, "mask") and sensor.mask is None) if not pml_inside: kgrid, medium, source, sensor = _expand_for_pml_outside(kgrid, medium, source, sensor, pml_size) From bbbd4dd904b2ecfd6d6abf217787a2d355419a4f Mon Sep 17 00:00:00 2001 From: Walter Simson <8669206+waltsims@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:15:32 -0700 Subject: [PATCH 20/21] Update kspaceFirstOrder.py remobe unused local var Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- kwave/kspaceFirstOrder.py | 1 - 1 file changed, 1 deletion(-) diff --git a/kwave/kspaceFirstOrder.py b/kwave/kspaceFirstOrder.py index 6db351918..876bd4f9f 100644 --- a/kwave/kspaceFirstOrder.py +++ b/kwave/kspaceFirstOrder.py @@ -52,7 +52,6 @@ def _expand_obj(obj, attrs, pml_size, edge_val=None): def _expand_for_pml_outside(kgrid, medium, source, sensor, pml_size): """Expand grid/medium/source/sensor so PML sits outside the user domain.""" - ndim = kgrid.dim new_N = tuple(int(n) + 2 * int(p) for n, p in zip(kgrid.N, pml_size)) expanded_kgrid = kWaveGrid(new_N, kgrid.spacing) expanded_kgrid.setTime(kgrid.Nt, kgrid.dt) From 74a77284bfc2cf8d2c314447f833f20fe710f861 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Mon, 23 Mar 2026 20:16:18 -0700 Subject: [PATCH 21/21] CI: run all examples with native backend, override device=cpu only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove KWAVE_BACKEND override — examples run with their specified backend (cpp or python). Only override KWAVE_DEVICE=cpu since CI has no GPU. Include all examples including legacy ones (TR, AS, checkpoint). Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/run-examples.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/run-examples.yml b/.github/workflows/run-examples.yml index 5d9d09b45..029e15fa0 100644 --- a/.github/workflows/run-examples.yml +++ b/.github/workflows/run-examples.yml @@ -14,12 +14,9 @@ jobs: - uses: actions/checkout@v4 - id: find-examples run: | - # Exclude: checkpointing (legacy), time-reversal (TimeReversal needs C++ binary), - # axisymmetric (legacy kspaceFirstOrderASC, not yet in new API) - EXAMPLES=$(find examples -maxdepth 1 -name "*.py" \ - -not -name "checkpoint.py" \ - -not -name "*TR_*" \ - -not -name "*_AS.py" \ + # Find all example scripts (exclude utilities only) + EXAMPLES=$(find examples -maxdepth 2 -name "*.py" \ + -not -name "example_utils.py" \ | sort | jq -R -s -c 'split("\n")[:-1]') echo "examples=$EXAMPLES" >> "$GITHUB_OUTPUT" @@ -55,7 +52,6 @@ jobs: - name: Run example env: - KWAVE_BACKEND: python KWAVE_DEVICE: cpu MPLBACKEND: Agg run: |