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

1from typing import Tuple 

2from skimage.transform import resize as si_resize 

3import numpy as np 

4import warnings 

5 

6from .tictoc import TicToc 

7from .conversionutils import scale_time 

8from .checkutils import num_dim2, is_number 

9from .interputils import interpolate2D 

10 

11 

12def expand_matrix(matrix, exp_coeff, edge_val=None): 

13 """ 

14 Enlarge a matrix by extending the edge values. 

15 

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

21 

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) 

39 

40 if edge_val is None: 

41 opts['mode'] = 'edge' 

42 else: 

43 opts['mode'] = 'constant' 

44 opts['constant_values'] = edge_val 

45 

46 exp_coeff = np.array(exp_coeff).astype(int).squeeze() 

47 n_coeff = exp_coeff.size 

48 assert n_coeff > 0 

49 

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

65 

66 return np.pad(matrix, **opts) 

67 

68 

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] 

77 

78 

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] 

84 

85 

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

91 

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

97 

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 

129 

130def _resize2D(mat,new_size, interp_mode='linear'): 

131 """ 

132 2D specification of resize method 

133 

134 Args: 

135 mat: 

136 new_size: 

137 interp_mode: 

138 

139 Returns: 

140 mat_rs: 

141 """ 

142 

143 # extract the original number of pixels from the size of the matrix 

144 Nx_input, Ny_input = mat.shape 

145 

146 # extract the desired number of pixels 

147 Nx_output, Ny_output = new_size 

148 

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

152 

153 # check the size is different to the input size 

154 if Nx_input != Nx_output or Ny_input != Ny_output: 

155 

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

159 

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

162 

163 mat_rs = interpolate2D([inp_x, inp_y], mat, [out_x, out_y], method=interp_mode, copy_nans=False) 

164 print(mat_rs.shape) 

165 

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 

170 

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 

192 

193 

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 

197 

198 Args: 

199 mat: matrix to be "resized" i.e. resampled 

200 new_size: desired output resolution 

201 interp_mode: interpolation method 

202 

203 Returns: 

204 res_mat: "resized" matrix 

205 

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

211 

212 # update command line status 

213 print('Resizing matrix...') 

214 

215 # check inputs 

216 assert num_dim2(mat) == len(new_size), \ 

217 'Resolution input must have the same number of elements as data dimensions.' 

218 

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

228 

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 

233 

234 

235def smooth(mat, restore_max=False, window_type='Blackman'): 

236 """ 

237 Smooth a matrix 

238 Returns: 

239 

240 """ 

241 DEF_USE_ROTATION = True 

242 

243 assert is_number(mat) and np.all(~np.isinf(mat)) 

244 assert isinstance(restore_max, bool) 

245 assert isinstance(window_type, str) 

246 

247 # get the grid size 

248 grid_size = mat.shape 

249 

250 # remove singleton dimensions 

251 if num_dim2(mat) != len(grid_size): 

252 grid_size = np.squeeze(grid_size) 

253 

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) 

258 

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) 

265 

266 # rotate window if input mat is (1, N) 

267 if mat.shape[0] == 1: # is row? 

268 win = win.T 

269 

270 # apply the filter 

271 mat_sm = np.real(np.fft.ifftn(np.fft.fftn(mat) * np.fft.ifftshift(win))) 

272 

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 

277 

278 

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. 

282 

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. 

294 

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) 

309 

310 Returns: 

311 fx, fy, ... gradient 

312 

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) 

318 

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) 

327 

328 

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 

334 

335 

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