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

1from datetime import datetime 

2from typing import Union 

3import scipy 

4 

5import numpy as np 

6 

7 

8def get_date_string(): 

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

10 

11 

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

16 

17 

18def ndgrid(*args): 

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

20 

21 

22def sinc(x): 

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

24 

25 

26def 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 

39def 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 

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. 

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 

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. 

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