Coverage for kwave/utils/misc.py: 28%

1 from datetime import datetime

2 from typing import Union

3 import scipy

4

5 import numpy as np

6

7

8 def get_date_string():

9 return datetime.now().strftime("%d-%b-%Y-%H-%M-%S")

10

11

12 def 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))

16

17

18 def ndgrid(*args):

19 return np.array(np.meshgrid(*args, indexing='ij'))

20

21

22 def sinc(x):

23 return np.sinc(x / np.pi)

24

25

26 def round_even(x):

27 """

28 Rounds to the nearest even integer.

29

30 Args:

31 x (float): inpput value

32

33 Returns:

34 (int): nearest odd integer.

35 """

36 return 2 * round(x / 2)

37

38

39 def round_odd(x):

40 """

41 Rounds to the nearest odd integer.

42

43 Args:

44 x (float): input value

45

46 Returns:

47 (int): nearest odd integer.

48

49 """

50 return 2 * round((x + 1) / 2) - 1

51

52

53 def 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.

62

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 [].

69

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.

74

75 Args:

76 radius:

77 diameter:

78 velocity:

79 frequency:

80 sound_speed:

81 density:

82 axial_positions:

83 lateral_positions:

84

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]

93

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]

97

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

107

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)

113

114 # compute pressure

115 P = E * np.sin(k * d / 2)

116

117 # replace values where axial_position is equal to the radius with limit

118 P[np.abs(axial_positions - radius) < float_eps] = k * h

119

120 # calculate magnitude of the on - axis pressure

121 axial_pressure = density * sound_speed * velocity * np.abs(P)

122 return axial_pressure

123

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

128

129 # replace origin with limit

130 lateral_pressure[lateral_positions == 0] = density * sound_speed * velocity * k * h

131 return lateral_pressure

132

133 # wave number

134 k = 2 * np.pi * frequency / sound_speed

135

136 # height of rim

137 h = radius - np.sqrt(radius ** 2 - (diameter / 2) ** 2)

138

139 p_axial = None

140 p_lateral = None

141

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()

146

147 return p_axial, p_lateral

148

149 return [p_axial, p_lateral]

150

151

152 def 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.

162

163 Args:

164 A: matrix to search

165 a: value to find

166

167 Returns:

168 val

169 idx

170 """

171

172 assert isinstance(A, np.ndarray), "A must be an np.array"

173

174 idx = np.unravel_index(np.argmin(abs(A - a)), A.shape)

175 return A[idx], idx