Coverage for kwave/utils/conversionutils.py: 14%

128 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-10-24 11:55 -0700

1import math 

2from math import floor 

3from typing import Optional, Tuple 

4 

5import numpy as np 

6from scipy.interpolate import interp1d 

7 

8from kwave.utils.tictoc import TicToc 

9 

10 

11def scale_time(seconds): 

12 # switch to calculating years, weeks, and days if larger than 100 hours 

13 if seconds > (60 * 60 * 100): 

14 years = floor(seconds / (60 * 60 * 24 * 365)) 

15 seconds = seconds - years * 60 * 60 * 24 * 365 

16 weeks = floor(seconds / (60 * 60 * 24 * 7)) 

17 seconds = seconds - weeks * 60 * 60 * 24 * 7 

18 days = floor(seconds / (60 * 60 * 24)) 

19 seconds = seconds - days * 60 * 60 * 24 

20 else: 

21 years = 0 

22 weeks = 0 

23 days = 0 

24 

25 # calculate hours, minutes, and seconds 

26 hours = floor(seconds / (60 * 60)) 

27 seconds = seconds - hours * 60 * 60 

28 minutes = floor(seconds / 60) 

29 seconds = seconds - minutes * 60 

30 

31 # write out as a string, to keep the output manageable, only the largest 

32 # three units are written 

33 if years > 0: 

34 time = f'{years} years, {weeks} weeks, and {days} days' 

35 elif weeks > 0: 

36 time = f'{weeks} weeks, {days} days, and {hours} hours' 

37 elif days > 0: 

38 time = f'{days} days, {hours} hours, and {minutes} min' 

39 elif hours > 0: 

40 seconds = np.round(seconds, 4) 

41 if np.abs(seconds - int(seconds)) < 1e-4: 

42 seconds = int(seconds) 

43 time = f'{hours}hours {minutes}min {seconds}s' 

44 elif minutes > 0: 

45 seconds = np.round(seconds, 4) 

46 if np.abs(seconds - int(seconds)) < 1e-4: 

47 seconds = int(seconds) 

48 time = f'{minutes}min {seconds}s' 

49 else: 

50 precision = 10 # manually tuned number 

51 seconds = round(seconds, precision) 

52 time = f'{seconds}s' 

53 return time 

54 

55 

56def scale_SI(x): 

57 # force the input to be a scalar 

58 x = np.max(x) 

59 

60 # check for a negative input 

61 if x < 0: 

62 x = -x 

63 negative = True 

64 else: 

65 negative = False 

66 

67 if x == 0: 

68 

69 # if x is zero, don't scale 

70 x_sc = x 

71 prefix = '' 

72 prefix_fullname = '' 

73 scale = 1 

74 

75 elif x < 1: 

76 

77 # update index and input 

78 x_sc = x * 1e3 

79 sym_index = 1 

80 

81 # find scaling parameter 

82 while x_sc < 1 and sym_index < 8: 

83 x_sc = x_sc * 1e3 

84 sym_index = sym_index + 1 

85 

86 # define SI unit scalings 

87 units = { 

88 1: ('m', 'milli', 1e3), 

89 2: ('u', 'micro', 1e6), 

90 3: ('n', 'nano', 1e9), 

91 4: ('p', 'pico', 1e12), 

92 5: ('f', 'femto', 1e15), 

93 6: ('a', 'atto', 1e18), 

94 7: ('z', 'zepto', 1e21), 

95 8: ('y', 'yocto', 1e24), 

96 } 

97 prefix, prefix_fullname, scale = units[sym_index] 

98 

99 elif x >= 1000: 

100 

101 # update index and input 

102 x_sc = x * 1e-3 

103 sym_index = 1 

104 

105 # find scaling parameter 

106 while x_sc >= 1000 and sym_index < 8: 

107 x_sc = x_sc * 1e-3 

108 sym_index = sym_index + 1 

109 

110 # define SI unit scalings 

111 units = { 

112 1: ('k', 'kilo', 1e-3), 

113 2: ('M', 'mega', 1e-6), 

114 3: ('G', 'giga', 1e-9), 

115 4: ('T', 'tera', 1e-12), 

116 5: ('P', 'peta', 1e-15), 

117 6: ('E', 'exa', 1e-18), 

118 7: ('Z', 'zetta', 1e-21), 

119 8: ('Y', 'yotta', 1e-24) 

120 } 

121 prefix, prefix_fullname, scale = units[sym_index] 

122 

123 else: 

124 # if x is between 1 and 1000, don't scale 

125 x_sc = x 

126 prefix = '' 

127 prefix_fullname = '' 

128 scale = 1 

129 

130 # form scaling into a string 

131 round_decimals = 6 # TODO this needs to be tuned 

132 x_sc = x_sc.round(round_decimals) 

133 if (x_sc - int(x_sc)) < (0.1 ** round_decimals): 

134 # avoid values like X.0, instead have only X 

135 x_sc = int(x_sc) 

136 x_sc = f'-{x_sc}{prefix}' if negative else f'{x_sc}{prefix}' 

137 return x_sc, scale, prefix, prefix_fullname 

138 

139 

140def db2neper(alpha, y=1): 

141 """ 

142 DB2NEPER Convert decibels to nepers. 

143 

144 DESCRIPTION: 

145 db2neper converts an attenuation coefficient in units of 

146 dB / (MHz ^ y cm) to units of Nepers / ((rad / s) ^ y m). 

147 

148 USAGE: 

149 alpha = db2neper(alpha) 

150 alpha = db2neper(alpha, y) 

151 

152 INPUTS: 

153 alpha - attenuation in dB / (MHz ^ y cm) 

154 

155 OPTIONAL INPUTS: 

156 y - power law exponent(default=1) 

157 

158 OUTPUTS: 

159 alpha - attenuation in Nepers / ((rad / s) ^ y m) 

160 

161 ABOUT: 

162 author - Bradley Treeby 

163 date - 27 th March 2009 

164 last update - 4 th June 2017 

165 

166 This function is part of the k - Wave Toolbox(http: // www.k - wave.org) 

167 Copyright(C) 2009 - 2017 Bradley Treeby 

168 

169 See also neper2db 

170 

171 This file is part of k - Wave.k - Wave is free software: you can 

172 redistribute it and / or modify it under the terms of the GNU Lesser 

173 General Public License as published by the Free Software Foundation, 

174 either version 3 of the License, or (at your option) any later version. 

175 

176 k - Wave is distributed in the hope that it will be useful, but WITHOUT ANY 

177 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 

178 FOR A PARTICULAR PURPOSE.See the GNU Lesser General Public License for 

179 more details. 

180 

181 You should have received a copy of the GNU Lesser General Public License 

182 along with k - Wave.If not, see < http:// www.gnu.org / licenses / >. 

183 

184 set default y value if not given by user 

185 """ 

186 

187 # calculate conversion 

188 alpha = 100 * alpha * (1e-6 / (2 * math.pi)) ** y / (20 * np.log10(np.exp(1))) 

189 return alpha 

190 

191 

192def neper2db(alpha, y=1): 

193 """ 

194 NEPER2DB Convert nepers to decibels. 

195 

196 DESCRIPTION: 

197 % neper2db converts an attenuation coefficient in units of 

198 % Nepers / ((rad / s) ^ y m) to units of dB / (MHz ^ y cm). 

199 % 

200 % USAGE: 

201 % alpha = neper2db(alpha) 

202 % alpha = neper2db(alpha, y) 

203 % 

204 % INPUTS: 

205 % alpha - attenuation in Nepers / ((rad / s) ^ y m) 

206 % 

207 % OPTIONAL INPUTS: 

208 % y - power law exponent(default=1) 

209 % 

210 % OUTPUTS: 

211 % alpha - attenuation in dB / (MHz ^ y cm) 

212 % 

213 % ABOUT: 

214 % author - Bradley Treeby 

215 % date - 3 rd December 2009 

216 % last update - 7 th June 2017 

217 % 

218 % This function is part of the k - Wave Toolbox(http: // www.k - wave.org) 

219 % Copyright(C) 2009 - 2017 Bradley Treeby 

220 % 

221 % See also db2neper 

222 

223 % This file is part of k - Wave.k - Wave is free software: you can 

224 % redistribute it and / or modify it under the terms of the GNU Lesser 

225 % General Public License as published by the Free Software Foundation, 

226 % either version 3 of the License, or (at your option) any later version. 

227 % 

228 % k - Wave is distributed in the hope that it will be useful, but WITHOUT ANY 

229 % WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 

230 % FOR A PARTICULAR PURPOSE.See the GNU Lesser General Public License for 

231 % more details. 

232 % 

233 % You should have received a copy of the GNU Lesser General Public License 

234 % along with k - Wave.If not, see < http:// www.gnu.org / licenses / >. 

235 """ 

236 

237 # calculate conversion 

238 alpha = 20 * math.log10(math.exp(1)) * alpha * (2 * math.pi * 1e6) ** y / 100 

239 return alpha 

240 

241 

242def cast_to_type(data, matlab_type: str): 

243 if not isinstance(data, np.ndarray): 

244 data = np.array(data) 

245 type_map = { 

246 'single': np.float32, 

247 'double': np.float64, 

248 'uint64': np.uint64, 

249 'uint32': np.uint32, 

250 'uint16': np.uint16, 

251 } 

252 return data.astype(type_map[matlab_type]) 

253 

254 

255def scan_conversion( 

256 scan_lines: np.ndarray, 

257 steering_angles, 

258 image_size: Tuple[float, float], 

259 c0, 

260 dt, 

261 resolution: Optional[Tuple[int, int]] 

262) -> np.ndarray: 

263 

264 if resolution is None: 

265 resolution = (256, 256) # in pixels 

266 

267 x_resolution, y_resolution = resolution 

268 

269 # assign the inputs 

270 x, y = image_size 

271 

272 # start the timer 

273 TicToc.tic() 

274 

275 # update command line status 

276 print('Computing ultrasound scan conversion...') 

277 

278 # extract a_line parameters 

279 Nt = scan_lines.shape[1] 

280 

281 # calculate radius variable based on the sound speed in the medium and the 

282 # round trip distance 

283 r = c0 * np.arange(1, Nt + 1) * dt / 2 # [m] 

284 

285 # create regular Cartesian grid to remap to 

286 pos_vec_y_new = np.linspace(0, 1, y_resolution) * y - y / 2 

287 pos_vec_x_new = np.linspace(0, 1, x_resolution) * x 

288 [pos_mat_x_new, pos_mat_y_new] = np.array(np.meshgrid(pos_vec_x_new, pos_vec_y_new, indexing='ij')) 

289 

290 # convert new points to polar coordinates 

291 [th_cart, r_cart] = cart2pol(pos_mat_x_new, pos_mat_y_new) 

292 

293 # TODO: move this import statement at the top of the file 

294 # Not possible now due to cyclic dependencies 

295 from kwave.utils.interputils import interpolate2D_with_queries 

296 

297 # below part has some modifications 

298 # we flatten the _cart matrices and build queries 

299 # then we get values at the query locations 

300 # and reshape the values to the desired size 

301 # These three steps can be accomplished in one step in Matlab 

302 # However, we don't want to add custom logic to the `interpolate2D_with_queries` method. 

303 

304 # Modifications -start 

305 queries = np.array([r_cart.flatten(), th_cart.flatten()]).T 

306 

307 b_mode = interpolate2D_with_queries( 

308 [r, 2 * np.pi * steering_angles / 360], 

309 scan_lines.T, 

310 queries, 

311 method='linear', 

312 copy_nans=False 

313 ) 

314 image_size_points = (len(pos_vec_x_new), len(pos_vec_y_new)) 

315 b_mode = b_mode.reshape(image_size_points) 

316 # Modifications -end 

317 

318 b_mode[np.isnan(b_mode)] = 0 

319 

320 # update command line status 

321 print(f' completed in {scale_time(TicToc.toc())}') 

322 

323 return b_mode 

324 

325 

326def cart2pol(x, y): 

327 rho = np.sqrt(x**2 + y**2) 

328 phi = np.arctan2(y, x) 

329 return phi, rho 

330 

331 

332def revolve2D(mat2D): 

333 # start timer 

334 TicToc.tic() 

335 

336 # update command line status 

337 print('Revolving 2D matrix to form a 3D matrix...') 

338 

339 # get size of matrix 

340 m, n = mat2D.shape 

341 

342 # create the reference axis for the 2D image 

343 r_axis_one_sided = np.arange(0, n) 

344 r_axis_two_sided = np.arange(-(n-1), n) 

345 

346 # compute the distance from every pixel in the z-y cross-section of the 3D 

347 # matrix to the rotation axis 

348 z, y = np.meshgrid(r_axis_two_sided, r_axis_two_sided) 

349 r = np.sqrt(y**2 + z**2) 

350 

351 # create empty image matrix 

352 mat3D = np.zeros((m, 2 * n - 1, 2 * n - 1)) 

353 

354 # loop through each cross section and create 3D matrix 

355 for x_index in range(m): 

356 interp = interp1d(x=r_axis_one_sided, y=mat2D[x_index, :], kind='linear', bounds_error=False, fill_value=0) 

357 mat3D[x_index, :, :] = interp(r) 

358 

359 # update command line status 

360 print(f' completed in {scale_time(TicToc.toc())}s') 

361 return mat3D