Coverage for kwave/utils/misc.py: 28%
47 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-24 11:55 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2022-10-24 11:55 -0700
1from datetime import datetime
2from typing import Union
3import scipy
5import numpy as np
8def get_date_string():
9 return datetime.now().strftime("%d-%b-%Y-%H-%M-%S")
12def gaussian(x, magnitude=None, mean=0, variance=1):
13 if magnitude is None:
14 magnitude = np.sqrt(2 * np.pi * variance)
15 return magnitude * np.exp(-(x - mean) ** 2 / (2 * variance))
18def ndgrid(*args):
19 return np.array(np.meshgrid(*args, indexing='ij'))
22def sinc(x):
23 return np.sinc(x / np.pi)
26def round_even(x):
27 """
28 Rounds to the nearest even integer.
30 Args:
31 x (float): inpput value
33 Returns:
34 (int): nearest odd integer.
35 """
36 return 2 * round(x / 2)
39def round_odd(x):
40 """
41 Rounds to the nearest odd integer.
43 Args:
44 x (float): input value
46 Returns:
47 (int): nearest odd integer.
49 """
50 return 2 * round((x + 1) / 2) - 1
53def focused_bowl_oneil(radius: float, diameter: float, velocity: float, frequency: float, sound_speed: float,
54 density: float, axial_positions: Union[np.array, float, list] = None,
55 lateral_positions: Union[np.array, float, list] = None) -> [float, float]:
56 """
57 focused_bowl_oneil calculates O'Neil's solution (O'Neil, H. Theory of
58 focusing radiators. J. Acoust. Soc. Am., 21(5), 516-526, 1949) for
59 the axial and lateral pressure amplitude generated by a focused bowl
60 transducer when uniformly driven by a continuous wave sinusoid at a
61 given frequency and normal surface velocity.
63 The solution is evaluated at the positions along the beam axis given
64 by axial_position (where 0 corresponds to the transducer surface),
65 and lateral positions through the geometric focus given by
66 lateral_position (where 0 corresponds to the beam axis). To return
67 only the axial or lateral pressure, set the either axial_position or
68 lateral_position to [].
70 Note, O'Neil's formulae are derived under the assumptions of the
71 Rayleigh integral, which are valid when the transducer diameter is
72 large compared to both the transducer height and the acoustic
73 wavelength.
75 Args:
76 radius:
77 diameter:
78 velocity:
79 frequency:
80 sound_speed:
81 density:
82 axial_positions:
83 lateral_positions:
85 Example:
86 # define transducer parameters
87 radius = 140e-3 # [m]
88 diameter = 120e-3 # [m]
89 velocity = 100e-3 # [m / s]
90 frequency = 1e6 # [Hz]
91 sound_speed = 1500 # [m / s]
92 density = 1000 # [kg / m ^ 3]
94 # define position vectors
95 axial_position = np.arange(0, 250e-3 + 1e-4, 1e-4) # [m]
96 lateral_position = np.arange(-15e-3, 15e-3 + 1e-4, 1e-4) # [m]
98 # evaluate pressure
99 [p_axial, p_lateral] = focused_bowl_oneil(radius, diameter,
100 velocity, frequency, sound_speed, density,
101 axial_position, lateral_position)
102 Returns:
103 p_axial: pressure amplitude at the axial_position [Pa]
104 p_lateral: pressure amplitude at the lateral_position [Pa]
105 """
106 float_eps = np.finfo(float).eps
108 def calculate_axial_pressure() -> float:
109 # calculate distances
110 B = np.sqrt((axial_positions - h) ** 2 + (diameter / 2) ** 2)
111 d = B - axial_positions
112 E = 2 / (1 - axial_positions / radius)
114 # compute pressure
115 P = E * np.sin(k * d / 2)
117 # replace values where axial_position is equal to the radius with limit
118 P[np.abs(axial_positions - radius) < float_eps] = k * h
120 # calculate magnitude of the on - axis pressure
121 axial_pressure = density * sound_speed * velocity * np.abs(P)
122 return axial_pressure
124 def calculate_lateral_pressure() -> float:
125 # calculate magnitude of the lateral pressure at the geometric focus
126 Z = k * lateral_positions * diameter / (2 * radius)
127 lateral_pressure = 2. * density * sound_speed * velocity * k * h * scipy.special.jv(1, Z) / Z
129 # replace origin with limit
130 lateral_pressure[lateral_positions == 0] = density * sound_speed * velocity * k * h
131 return lateral_pressure
133 # wave number
134 k = 2 * np.pi * frequency / sound_speed
136 # height of rim
137 h = radius - np.sqrt(radius ** 2 - (diameter / 2) ** 2)
139 p_axial = None
140 p_lateral = None
142 if lateral_positions is not None:
143 p_lateral = calculate_lateral_pressure()
144 if axial_positions is not None:
145 p_axial = calculate_axial_pressure()
147 return p_axial, p_lateral
149 return [p_axial, p_lateral]
152def find_closest(A, a):
153 """
154 find_closest returns the value and index of the item in A that is
155 closest to the value a. For vectors, value and index correspond to
156 the closest element in A. For matrices, value and index are row
157 vectors corresponding to the closest element from each column. For
158 N-D arrays, the function finds the closest value along the first
159 matrix dimension (singleton dimensions are removed before the
160 search). If there is more than one element with the closest value,
161 the index of the first one is returned.
163 Args:
164 A: matrix to search
165 a: value to find
167 Returns:
168 val
169 idx
170 """
172 assert isinstance(A, np.ndarray), "A must be an np.array"
174 idx = np.unravel_index(np.argmin(abs(A - a)), A.shape)
175 return A[idx], idx