-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathus_beam_patterns.py
More file actions
272 lines (199 loc) · 7.77 KB
/
us_beam_patterns.py
File metadata and controls
272 lines (199 loc) · 7.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/usr/bin/env python
# coding: utf-8
# In[ ]:
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.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
from kwave.utils.signals import tone_burst
# In[ ]:
# 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
USE_STATISTICS = True
# define the grid
PML_X_SIZE = 20
PML_Y_SIZE = 10
PML_Z_SIZE = 10
Nx = 128 - 2 * PML_X_SIZE
Ny = 64 - 2 * PML_Y_SIZE
Nz = 64 - 2 * PML_Z_SIZE
x = 40e-3
dx = x / Nx
dy = dx
dz = dx
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)
# create the time array - using a time == time to travel the hypot of the grid
t_end = 45e-6 # alternatively, use np.sqrt(kgrid.x_size ** 2 + kgrid.y_size ** 2) / medium.sound_speed
kgrid.makeTime(medium.sound_speed, t_end=t_end)
# In[ ]:
# define the input signal
source_strength = 1e6
tone_burst_freq = 0.5e6
tone_burst_cycles = 5
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
transducer.element_width = 1
transducer.element_length = 12
transducer.element_spacing = 0
transducer.radius = np.inf
# calculate the width of the transducer in grid points
transducer_width = transducer.number_elements * transducer.element_width + (transducer.number_elements - 1) * transducer.element_spacing
# use this to position the transducer in the middle of the computational grid
transducer.position = np.round([1, Ny / 2 - transducer_width / 2, Nz / 2 - transducer.element_length / 2])
transducer = kWaveTransducerSimple(kgrid, **transducer)
# In[ ]:
not_transducer = dotdict()
not_transducer.sound_speed = medium.sound_speed # sound speed [m/s]
not_transducer.focus_distance = 20e-3 # focus distance [m]
not_transducer.elevation_focus_distance = 19e-3 # focus distance in the elevation plane [m]
not_transducer.steering_angle = 0 # steering angle [degrees]
not_transducer.transmit_apodization = "Rectangular"
not_transducer.receive_apodization = "Rectangular"
not_transducer.active_elements = np.ones((transducer.number_elements, 1))
not_transducer.input_signal = input_signal
not_transducer = NotATransducer(transducer, kgrid, **not_transducer)
# In[ ]:
sensor_mask = np.zeros((Nx, Ny, Nz))
if MASK_PLANE == "xy":
sensor_mask[:, :, Nz // 2] = 1
# store y axis properties
Nj = Ny
j_vec = kgrid.y_vec
j_label = "y"
elif MASK_PLANE == "xz":
sensor_mask[:, Ny // 2, :] = 1
# store z axis properties
Nj = Nz
j_vec = kgrid.z_vec
j_label = "z"
# In[ ]:
sensor = kSensor(sensor_mask)
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,
)
# In[ ]:
if USE_STATISTICS:
fig, axes = plt.subplots(1, 2)
fig.set_figwidth(8)
fig.set_figheight(6)
for ind, measures in enumerate(sensor.record):
im1 = axes[ind].imshow(
sensor_data[measures].reshape([Nj, Nx]).T * 1e-6,
extent=[
min(j_vec * 1e3),
max(j_vec * 1e3),
min((kgrid.x_vec - min(kgrid.x_vec)) * 1e3),
max((kgrid.x_vec - min(kgrid.x_vec)) * 1e3),
],
aspect="auto",
cmap="jet",
)
axes[ind].set_xlabel("y-position (mm)")
axes[ind].set_ylabel("x-position (mm)")
title_text = f"Total Beam Pattern Using {str.upper(measures.split('_')[1])} \n of Recorded Pressure"
axes[ind].set_title(title_text)
axes[ind].set_yticks(axes[ind].get_yticks().tolist())
axes[ind].set_yticklabels(axes[ind].get_yticklabels()[::-1])
axes[ind].set_xlim([-10, 10])
fig.colorbar(im1, label="Pressure [MPa]", orientation="vertical")
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
[freq, amp_spect, _] = spect(sensor_data_array, 1 / kgrid.dt, dim=2)
# compute the index at which the source frequency and its harmonics occur
[f1_value, f1_index] = find_closest(freq, tone_burst_freq)
[f2_value, f2_index] = find_closest(freq, 2 * tone_burst_freq)
# extract the amplitude at the source frequency and store
beam_pattern_f1 = np.squeeze(amp_spect[:, :, f1_index])
# extract the amplitude at the second harmonic and store
beam_pattern_f2 = np.squeeze(amp_spect[:, :, f2_index])
# 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)
fig.set_figheight(6)
for ind, measure in enumerate([beam_pattern_f1, beam_pattern_f2, beam_pattern_total]):
im1 = axes[ind].imshow(
np.squeeze(measure) * 1e-6,
extent=[
min(j_vec * 1e3),
max(j_vec * 1e3),
min((kgrid.x_vec - min(kgrid.x_vec)) * 1e3),
max((kgrid.x_vec - min(kgrid.x_vec)) * 1e3),
],
aspect="auto",
cmap="jet",
)
axes[ind].set_xlabel("y-position (mm)")
axes[ind].set_ylabel("x-position (mm)")
axes[ind].set_yticks(axes[ind].get_yticks().tolist())
axes[ind].set_yticklabels(axes[ind].get_yticklabels()[::-1])
axes[0].set_title("Beam Pattern \nAt Source Fundamental")
axes[1].set_title("Beam Pattern \nAt Second Harmonic")
axes[2].set_title("Total Beam Pattern\nUsing Integral Of Recorded Pressure")
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), :]
directivity_f2 = beam_pattern_f2[round(not_transducer.focus_distance / dx), :]
# Normalize the directivity
directivity_f1 /= np.max(directivity_f1)
directivity_f2 /= np.max(directivity_f2)
# Compute relative angles from the transducer
if MASK_PLANE == "xy":
horz_axis = ((np.arange(Ny) + 1) - Ny / 2) * dy
else:
horz_axis = ((np.arange(Nz) + 1) - Nz / 2) * dz
angles = 180 * np.arctan2(horz_axis, not_transducer.focus_distance) / np.pi
# Plot the directivity
plt.figure()
plt.plot(angles, directivity_f1, "k-", label="Fundamental")
plt.plot(angles, directivity_f2, "k--", label="Second Harmonic")
plt.axis("tight")
plt.xlabel("Angle [deg]")
plt.ylabel("Normalized Amplitude")
plt.legend(loc=2)
plt.xlim([-25, 25])
plt.show()