Coverage for kwave/utils/matrixutils.py: 20%
116 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 typing import Tuple
2from skimage.transform import resize as si_resize
3import numpy as np
4import warnings
6from .tictoc import TicToc
7from .conversionutils import scale_time
8from .checkutils import num_dim2, is_number
9from .interputils import interpolate2D
12def expand_matrix(matrix, exp_coeff, edge_val=None):
13 """
14 Enlarge a matrix by extending the edge values.
16 expandMatrix enlarges an input matrix by extension of the values at
17 the outer faces of the matrix (endpoints in 1D, outer edges in 2D,
18 outer surfaces in 3D). Alternatively, if an input for edge_val is
19 given, all expanded matrix elements will have this value. The values
20 for exp_coeff are forced to be real positive integers (or zero).
22 Note, indexing is done inline with other k-Wave functions using
23 mat(x) in 1D, mat(x, y) in 2D, and mat(x, y, z) in 3D.
24 Args:
25 matrix: the matrix to enlarge
26 exp_coeff: the number of elements to add in each dimension
27 in 1D: [a] or [x_start, x_end]
28 in 2D: [a] or [x, y] or
29 [x_start, x_end, y_start, y_end]
30 in 3D: [a] or [x, y, z] or
31 [x_start, x_end, y_start, y_end, z_start, z_end]
32 (here 'a' is applied to all dimensions)
33 edge_val: value to use in the matrix expansion
34 Returns:
35 expanded matrix
36 """
37 opts = {}
38 matrix = np.squeeze(matrix)
40 if edge_val is None:
41 opts['mode'] = 'edge'
42 else:
43 opts['mode'] = 'constant'
44 opts['constant_values'] = edge_val
46 exp_coeff = np.array(exp_coeff).astype(int).squeeze()
47 n_coeff = exp_coeff.size
48 assert n_coeff > 0
50 if n_coeff == 1:
51 opts['pad_width'] = exp_coeff
52 elif len(matrix.shape) == 1:
53 assert n_coeff <= 2
54 opts['pad_width'] = exp_coeff
55 elif len(matrix.shape) == 2:
56 if n_coeff == 2:
57 opts['pad_width'] = exp_coeff
58 if n_coeff == 4:
59 opts['pad_width'] = [(exp_coeff[0], exp_coeff[1]), (exp_coeff[2], exp_coeff[3])]
60 elif len(matrix.shape) == 3:
61 if n_coeff == 3:
62 opts['pad_width'] = np.tile(np.expand_dims(exp_coeff, axis=-1), [1, 2])
63 if n_coeff == 6:
64 opts['pad_width'] = [(exp_coeff[0], exp_coeff[1]), (exp_coeff[2], exp_coeff[3]), (exp_coeff[4], exp_coeff[5])]
66 return np.pad(matrix, **opts)
69def matlab_find(arr, val=0, mode='neq'):
70 if not isinstance(arr, np.ndarray):
71 arr = np.array(arr)
72 if mode == 'neq':
73 arr = np.where(arr.flatten(order='F') != val)[0] + 1 # +1 due to matlab indexing
74 else: # 'eq'
75 arr = np.where(arr.flatten(order='F') == val)[0] + 1 # +1 due to matlab indexing
76 return np.expand_dims(arr, -1) # compatibility, n => [n, 1]
79def matlab_mask(arr, mask, diff=None):
80 if diff is None:
81 return np.expand_dims(arr.ravel(order='F')[mask.ravel(order='F')], axis=-1) # compatibility, n => [n, 1]
82 else:
83 return np.expand_dims(arr.ravel(order='F')[mask.ravel(order='F') + diff], axis=-1) # compatibility, n => [n, 1]
86def unflatten_matlab_mask(arr, mask, diff=None):
87 if diff is None:
88 return np.unravel_index(mask.ravel(order='F'), arr.shape, order='F')
89 else:
90 return np.unravel_index(mask.ravel(order='F') + diff, arr.shape, order='F')
92def matlab_assign(matrix: np.ndarray, indices, values):
93 original_shape = matrix.shape
94 matrix = matrix.flatten(order='F')
95 matrix[indices] = values
96 return matrix.reshape(original_shape, order='F')
98# def _resize1D():
99# # extract the original number of pixels from the size of the matrix
100# [Nx_input, Ny_input] = size(mat);
101#
102# # extract the desired number of pixels
103# if Ny_input == 1
104# Nx_output = varargin{2}(1);
105# Ny_output = 1;
106# else
107# Nx_output = 1;
108# Ny_output = varargin{2}(1);
109# end
110#
111# # update command line status
112# disp([' input grid size: ' num2str(Nx_input) ' by ' num2str(Ny_input) ' elements']);
113# disp([' output grid size: ' num2str(Nx_output) ' by ' num2str(Ny_output) ' elements']);
114#
115# # check the size is different to the input size
116# if Nx_input ~= Nx_output || Ny_input ~= Ny_output
117#
118# # resize the input matrix to the desired number of pixels
119# if Ny_input == 1
120# mat_rs = interp1((0:1/(Nx_input - 1):1)', mat, (0:1/(Nx_output - 1):1)', interp_mode);
121# else
122# mat_rs = interp1((0:1/(Ny_input - 1):1), mat, (0:1/(Ny_output - 1):1), interp_mode);
123# end
124#
125# else
126# mat_rs = mat;
127# end
128# pass
130def _resize2D(mat,new_size, interp_mode='linear'):
131 """
132 2D specification of resize method
134 Args:
135 mat:
136 new_size:
137 interp_mode:
139 Returns:
140 mat_rs:
141 """
143 # extract the original number of pixels from the size of the matrix
144 Nx_input, Ny_input = mat.shape
146 # extract the desired number of pixels
147 Nx_output, Ny_output = new_size
149 # update command line status
150 print(f' input grid size: {Nx_input} by {Ny_input} elements')
151 print(f' output grid size: {Nx_output} by {Ny_output} elements')
153 # check the size is different to the input size
154 if Nx_input != Nx_output or Ny_input != Ny_output:
156 # resize the input matrix to the desired number of pixels
157 inp_y = np.arange(0, 1 + 1e-8, 1 / (Ny_input - 1))
158 inp_x = np.arange(0, 1 + 1e-8, 1 / (Nx_input - 1))
160 out_y = np.arange(0, 1 + 1e-8, 1 / (Ny_output - 1))
161 out_x = np.arange(0, 1 + 1e-8, 1 / (Nx_output - 1))
163 mat_rs = interpolate2D([inp_x, inp_y], mat, [out_x, out_y], method=interp_mode, copy_nans=False)
164 print(mat_rs.shape)
166 # mat_rs = interp2(0:1/(Ny_input - 1):1, (0:1/(Nx_input - 1):1)', mat, 0:1/(Ny_output - 1):1, (0:1/(Nx_output - 1):1)', interp_mode);
167 else:
168 mat_rs = mat
169 return mat_rs
171# def _resize3D(mat, resolution, interp_mode='linear'):
172# # extract the original number of pixels from the size of the matrix
173# [Nx_input, Ny_input, Nz_input] = mat.shape
174#
175# # extract the desired number of pixels
176# Nx_output, Ny_output, Nz_output = resolution
177#
178# # update command line status
179# print(f' input grid size: {Nx_input} by {Ny_input} by {Nz_input} elements')
180# print(f' output grid size: {Nx_output} by {Ny_output} by {Nz_output} elements')
181#
182# # create normalised plaid grids of current discretisation
183# [x_mat, y_mat, z_mat] = ndgrid((0:Nx_input-1)/(Nx_input-1), (0:Ny_input-1)/(Ny_input-1), (0:Nz_input-1)/(Nz_input-1));
184#
185# # create plaid grids of desired discretisation
186# [x_mat_interp, y_mat_interp, z_mat_interp] = ndgrid((0:Nx_output-1)/(Nx_output-1), (0:Ny_output-1)/(Ny_output-1), (0:Nz_output-1)/(Nz_output-1));
187#
188# # compute interpolation; for a matrix indexed as [M, N, P], the
189# # axis variables must be given in the order N, M, P
190# mat_rs = interp3(y_mat, x_mat, z_mat, mat, y_mat_interp, x_mat_interp, z_mat_interp, interp_mode);
191# pass
194def resize(mat, new_size, interp_mode='linear'):
195 """
196 resize: resamples a "matrix" of spatial samples to a desired "resolution" or spatial sampling frequency via interpolation
198 Args:
199 mat: matrix to be "resized" i.e. resampled
200 new_size: desired output resolution
201 interp_mode: interpolation method
203 Returns:
204 res_mat: "resized" matrix
206 """
207 # TODO: wrap scikit image resize
208 # https://scikit-image.org/docs/dev/api/skimage.transform.html#skimage.transform.resize
209 # start the timer
210 TicToc.tic()
212 # update command line status
213 print('Resizing matrix...')
215 # check inputs
216 assert num_dim2(mat) == len(new_size), \
217 'Resolution input must have the same number of elements as data dimensions.'
219 mat_rs = np.squeeze(si_resize(mat, new_size, order=0))
220 # if num_dim2(mat) == 1:
221 # raise NotImplementedError
222 # elif num_dim2(mat) == 2:
223 # mat_rs = _resize2D(mat, new_size, interp_mode)
224 # elif num_dim2(mat) == 3:
225 # raise NotImplementedError
226 # else:
227 # raise ValueError('Input matrix must be 1, 2 or 3 dimensional.')
229 # update command line status
230 print(f' completed in {scale_time(TicToc.toc())}')
231 assert mat_rs.shape == tuple(new_size)
232 return mat_rs
235def smooth(mat, restore_max=False, window_type='Blackman'):
236 """
237 Smooth a matrix
238 Returns:
240 """
241 DEF_USE_ROTATION = True
243 assert is_number(mat) and np.all(~np.isinf(mat))
244 assert isinstance(restore_max, bool)
245 assert isinstance(window_type, str)
247 # get the grid size
248 grid_size = mat.shape
250 # remove singleton dimensions
251 if num_dim2(mat) != len(grid_size):
252 grid_size = np.squeeze(grid_size)
254 # use a symmetric filter for odd grid sizes, and a non-symmetric filter for
255 # even grid sizes to ensure the DC component of the window has a value of
256 # unity
257 window_symmetry = (np.array(grid_size) % 2).astype(bool)
259 # get the window, taking the absolute value to discard machine precision
260 # negative values
261 from .kutils import get_win
262 win, _ = get_win(grid_size, type_=window_type,
263 rotation=DEF_USE_ROTATION, symmetric=window_symmetry)
264 win = np.abs(win)
266 # rotate window if input mat is (1, N)
267 if mat.shape[0] == 1: # is row?
268 win = win.T
270 # apply the filter
271 mat_sm = np.real(np.fft.ifftn(np.fft.fftn(mat) * np.fft.ifftshift(win)))
273 # restore magnitude if required
274 if restore_max:
275 mat_sm = (np.abs(mat).max() / np.abs(mat_sm).max()) * mat_sm
276 return mat_sm
279def gradient_FD(f, dx=None, dim=None, deriv_order=None, accuracy_order=None):
280 """
281 A wrapper of the numpy gradient method for use in the k-wave library.
283 gradient_FD calculates the gradient of an n-dimensional input matrix
284 using the finite-difference method. For one-dimensional inputs, the
285 gradient is always computed along the non-singleton dimension. For
286 higher dimensional inputs, the gradient for singleton dimensions is
287 returned as 0. For elements in the centre of the grid, the gradient
288 is computed using centered finite-differences. For elements on the
289 edge of the grid, the gradient is computed using forward or backward
290 finite-differences. The order of accuracy of the finite-difference
291 approximation is controlled by accuracy_order (default = 2). The
292 calculations are done using sparse multiplication, so the input
293 matrix is always cast to double precision.
295 Args:
296 f:
297 dx: array of values for the grid point spacing in each
298 dimension. If a value for dim is given, dn is the
299 spacing in dimension dim.
300 dim: optional input to specify a single dimension over which to compute the gradient for
301 n-dimension input functions
302 deriv_order: order of the derivative to compute, e.g., use 1 to
303 compute df/dx, 2 to compute df^2/dx^2, etc.
304 (default = 1)
305 accuracy_order: order of accuracy for the finite difference
306 coefficients. Because centered differences are
307 used, this must be set to an integer multiple of
308 2 (default = 2)
310 Returns:
311 fx, fy, ... gradient
313 """
314 if deriv_order:
315 warnings.warn("deriv_order is no longer a supported argument.", DeprecationWarning)
316 if accuracy_order:
317 warnings.warn("accuracy_order is no longer a supported argument.", DeprecationWarning)
319 if dim is not None and dx is not None:
320 return np.gradient(f, dx, axis=dim)
321 elif dim is not None:
322 return np.gradient(f, axis=dim)
323 elif dx is not None:
324 return np.gradient(f, dx)
325 else:
326 return np.gradient(f)
329def min_nd(matrix: np.ndarray) -> Tuple[float, Tuple]:
330 min_val, linear_index = np.min(matrix), matrix.argmin()
331 numpy_index = np.unravel_index(linear_index, matrix.shape)
332 matlab_index = tuple(idx + 1 for idx in numpy_index)
333 return min_val, matlab_index
336def max_nd(matrix: np.ndarray) -> Tuple[float, Tuple]:
337 max_val, linear_index = np.max(matrix), matrix.argmax()
338 numpy_index = np.unravel_index(linear_index, matrix.shape)
339 matlab_index = tuple(idx + 1 for idx in numpy_index)
340 return max_val, matlab_index