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
« 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
5import numpy as np
6from scipy.interpolate import interp1d
8from kwave.utils.tictoc import TicToc
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
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
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
56def scale_SI(x):
57 # force the input to be a scalar
58 x = np.max(x)
60 # check for a negative input
61 if x < 0:
62 x = -x
63 negative = True
64 else:
65 negative = False
67 if x == 0:
69 # if x is zero, don't scale
70 x_sc = x
71 prefix = ''
72 prefix_fullname = ''
73 scale = 1
75 elif x < 1:
77 # update index and input
78 x_sc = x * 1e3
79 sym_index = 1
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
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]
99 elif x >= 1000:
101 # update index and input
102 x_sc = x * 1e-3
103 sym_index = 1
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
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]
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
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
140def db2neper(alpha, y=1):
141 """
142 DB2NEPER Convert decibels to nepers.
144 DESCRIPTION:
145 db2neper converts an attenuation coefficient in units of
146 dB / (MHz ^ y cm) to units of Nepers / ((rad / s) ^ y m).
148 USAGE:
149 alpha = db2neper(alpha)
150 alpha = db2neper(alpha, y)
152 INPUTS:
153 alpha - attenuation in dB / (MHz ^ y cm)
155 OPTIONAL INPUTS:
156 y - power law exponent(default=1)
158 OUTPUTS:
159 alpha - attenuation in Nepers / ((rad / s) ^ y m)
161 ABOUT:
162 author - Bradley Treeby
163 date - 27 th March 2009
164 last update - 4 th June 2017
166 This function is part of the k - Wave Toolbox(http: // www.k - wave.org)
167 Copyright(C) 2009 - 2017 Bradley Treeby
169 See also neper2db
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.
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.
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 / >.
184 set default y value if not given by user
185 """
187 # calculate conversion
188 alpha = 100 * alpha * (1e-6 / (2 * math.pi)) ** y / (20 * np.log10(np.exp(1)))
189 return alpha
192def neper2db(alpha, y=1):
193 """
194 NEPER2DB Convert nepers to decibels.
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
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 """
237 # calculate conversion
238 alpha = 20 * math.log10(math.exp(1)) * alpha * (2 * math.pi * 1e6) ** y / 100
239 return alpha
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])
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:
264 if resolution is None:
265 resolution = (256, 256) # in pixels
267 x_resolution, y_resolution = resolution
269 # assign the inputs
270 x, y = image_size
272 # start the timer
273 TicToc.tic()
275 # update command line status
276 print('Computing ultrasound scan conversion...')
278 # extract a_line parameters
279 Nt = scan_lines.shape[1]
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]
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'))
290 # convert new points to polar coordinates
291 [th_cart, r_cart] = cart2pol(pos_mat_x_new, pos_mat_y_new)
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
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.
304 # Modifications -start
305 queries = np.array([r_cart.flatten(), th_cart.flatten()]).T
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
318 b_mode[np.isnan(b_mode)] = 0
320 # update command line status
321 print(f' completed in {scale_time(TicToc.toc())}')
323 return b_mode
326def cart2pol(x, y):
327 rho = np.sqrt(x**2 + y**2)
328 phi = np.arctan2(y, x)
329 return phi, rho
332def revolve2D(mat2D):
333 # start timer
334 TicToc.tic()
336 # update command line status
337 print('Revolving 2D matrix to form a 3D matrix...')
339 # get size of matrix
340 m, n = mat2D.shape
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)
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)
351 # create empty image matrix
352 mat3D = np.zeros((m, 2 * n - 1, 2 * n - 1))
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)
359 # update command line status
360 print(f' completed in {scale_time(TicToc.toc())}s')
361 return mat3D