harmonization.dictionary
1import os 2import warnings 3 4import numpy as np 5import nibabel as nib 6 7from itertools import cycle 8from ast import literal_eval 9from time import time 10 11from scipy.ndimage import zoom 12 13from autodmri.blocks import extract_patches 14from nlsam.angular_tools import angular_neighbors 15from nlsam.denoiser import greedy_set_finder 16 17from harmonization.solver import online_DL, solve_l1 18from harmonization.recon import depimp_zoom, reconstruct_from_blocks 19 20 21def get_global_D(datasets, outfilename, block_size, block_up, ncores=None, batchsize=32, niter=500, 22 use_std=False, positivity=False, fit_intercept=True, center=True, nlambdas=100, 23 b0_threshold=20, split_b0s=True, **kwargs): 24 25 # get the data shape so we can preallocate some arrays 26 # we also have to assume all datasets have the same 3D shape obviously 27 shape = nib.load(datasets[0]['data']).header.get_data_shape() 28 29 # block size depends on the upsampling factor, so we pick the biggest one 30 upsample = np.prod(literal_eval(block_up)) > np.prod(literal_eval(block_size)) 31 32 if upsample: 33 current_block_size = literal_eval(block_up) 34 else: 35 current_block_size = literal_eval(block_size) 36 37 n_atoms = int(np.prod(current_block_size) * 2) 38 b0_block_size = tuple(current_block_size[:-1]) + ((current_block_size[-1] + 1,)) 39 overlap = b0_block_size 40 to_denoise = np.empty(shape[:-1] + (current_block_size[-1] + 1,), dtype=np.float32) 41 42 train_list = [] 43 variance_large = [] 44 45 for filename in datasets: 46 47 print(f"Now feeding dataset {filename['data']}") 48 49 mask = nib.load(filename['mask']).get_fdata(caching='unchanged').astype(bool) 50 data = nib.load(filename['data']).get_fdata(caching='unchanged', dtype=np.float32) 51 bvals = np.loadtxt(filename['bval']) 52 bvecs = np.loadtxt(filename['bvec']) 53 54 if np.shape(bvecs)[0] == 3: 55 bvecs = bvecs.T 56 57 if mask.shape != data.shape[:-1]: 58 error = f'mask shape {mask.shape} does not fit with the data shape {data.shape}' 59 raise ValueError(error) 60 61 data *= mask[..., None] 62 b0_loc = np.where(bvals <= b0_threshold)[0] 63 dwis = np.where(bvals > b0_threshold)[0] 64 num_b0s = len(b0_loc) 65 66 # We also convert bvecs associated with b0s to exactly (0,0,0), which 67 # is not always the case when we hack around with the scanner. 68 bvecs = np.where(bvals[:, None] <= b0_threshold, 0, bvecs) 69 70 # Average all b0s if we don't split them in the training set 71 if num_b0s > 1 and not split_b0s: 72 num_b0s = 1 73 data[..., b0_loc] = np.mean(data[..., b0_loc], axis=-1, keepdims=True) 74 75 # Split the b0s in a cyclic fashion along the training data 76 # If we only had one, cycle just return b0_loc indefinitely, 77 # else we go through all indexes. 78 np.random.shuffle(b0_loc) 79 split_b0s_idx = cycle(b0_loc) 80 sym_bvecs = np.vstack((bvecs, -bvecs)) 81 82 neighbors = angular_neighbors(sym_bvecs, current_block_size[-1] - 1) % data.shape[-1] 83 neighbors = neighbors[:data.shape[-1]] # everything was doubled for symmetry 84 85 full_indexes = [(dwi,) + tuple(neighbors[dwi]) for dwi in range(data.shape[-1]) if dwi in dwis] 86 indexes = greedy_set_finder(full_indexes) 87 88 # If we have more b0s than indexes, then we have to add a few more blocks since 89 # we won't do a full cycle. If we have more b0s than indexes after that, then it breaks. 90 if num_b0s > len(indexes): 91 the_rest = [rest for rest in full_indexes if rest not in indexes] 92 indexes += the_rest[:(num_b0s - len(indexes))] 93 94 if num_b0s > len(indexes): 95 error = f'Seems like you still have more b0s {num_b0s} than available blocks {len(indexes)}, either average them or deactivate subsampling.' 96 raise ValueError(error) 97 98 # whole global centering 99 if center: 100 data -= data.mean(axis=-1, keepdims=True) 101 102 for i, idx in enumerate(indexes): 103 b0_loc = tuple((next(split_b0s_idx),)) 104 105 # if we mix datasets, then we may need to change the storage array size 106 if to_denoise.shape[:-1] != data.shape[:-1]: 107 del to_denoise 108 to_denoise = np.empty(data.shape[:-1] + (current_block_size[-1] + 1,), dtype=np.float32) 109 110 to_denoise[..., 0] = data[..., b0_loc].squeeze() 111 to_denoise[..., 1:] = data[..., idx] 112 113 patches = extract_patches(to_denoise, b0_block_size, overlap, flatten=False) 114 axis = tuple(range(patches.ndim//2, patches.ndim)) 115 mask_patch = np.count_nonzero(patches, axis=axis) > np.prod(b0_block_size) // 2 116 patches = patches[mask_patch].reshape(-1, np.prod(b0_block_size)) 117 118 if use_std: 119 try: 120 variance = nib.load(filename['std']).get_fdata(caching='unchanged', dtype=np.float32)**2 * mask 121 variance = np.broadcast_to(variance[..., None], data.shape) 122 variance = extract_patches(variance, b0_block_size, overlap, flatten=False) 123 axis = tuple(range(variance.ndim//2, variance.ndim)) 124 variance = np.median(variance, axis=axis)[mask_patch].ravel() 125 except IOError: 126 warnings.warn(f"Volume {filename['std']} not found! Skipping using variance.") 127 variance = [None] 128 else: 129 variance = [None] 130 131 # check to build with np.r_ the whole list from stringnames instead 132 train_list += [patches] 133 variance_large += list(variance) 134 135 del data, mask, patches, variance 136 137 print('Fed everything in') 138 139 lengths = [l.shape[0] for l in train_list] 140 train_data = np.empty((np.sum(lengths), np.prod(b0_block_size))) 141 142 step = 0 143 for i in range(len(train_list)): 144 length = lengths[i] 145 idx = slice(step, step + length) 146 train_data[idx] = train_list[i].reshape(-1, np.prod(b0_block_size)) 147 step += length 148 149 del train_list 150 151 # we have variance as a N elements list - so check one element to see if it's an array 152 if variance_large[0] is not None: 153 variance_large = np.asarray(variance_large).ravel() 154 else: 155 variance_large = None 156 157 D = online_DL(train_data, ncores=ncores, positivity=positivity, fit_intercept=fit_intercept, standardize=True, 158 nlambdas=nlambdas, niter=niter, batchsize=batchsize, n_atoms=n_atoms, variance=variance_large, 159 progressbar=True, use_joblib=True) 160 161 return D 162 163 164def rebuild(data, mask, D, block_size, block_up, ncores=-1, nlambdas=100, 165 positivity=False, variance=None, fix_mean=True, fit_intercept=False, use_crossval=False): 166 167 data = data * mask[..., None] 168 169 if variance is not None: 170 variance *= mask 171 172 if len(block_size) == len(block_up): 173 last = block_up[-1] 174 else: 175 last = block_size[-1] 176 177 factor = np.divide(block_up, block_size) 178 new_shape = (int(data.shape[0] * factor[0]), 179 int(data.shape[1] * factor[1]), 180 int(data.shape[2] * factor[2]), 181 last) 182 overlap = (1, 1, 1, last) 183 new_overlap = overlap 184 185 if block_size == block_up: 186 D_depimpe = np.copy(D) 187 else: 188 D_depimpe = depimp_zoom(D, block_size, block_up) 189 190 blocks = extract_patches(data, block_size, overlap, flatten=False).reshape(-1, np.prod(block_size)) 191 del data 192 193 # skip empty rows from training since they are probably masked background 194 mask = np.count_nonzero(blocks, axis=1) > np.prod(block_size) // 2 195 196 # get the variance as blocks 197 if variance is not None: 198 variance = extract_patches(variance, block_size[:-1], overlap[:-1], flatten=True) 199 axis = tuple(range(1, len(block_size) + 1)) 200 variance = np.median(variance, axis=axis) 201 202 # if we are on an edge, variance can be 0, so truncate those cases as well 203 np.logical_and(mask, variance > 0, out=mask) 204 variance = variance[mask] 205 206 blocks = blocks[mask] 207 208 tt = time() 209 X_small_denoised, alpha, intercept, _ = solve_l1(blocks, D_depimpe, variance=variance, return_all=True, nlambdas=nlambdas, use_joblib=True, 210 positivity=positivity, fit_intercept=True, standardize=True, progressbar=True, verbose=5, 211 ncores=ncores, use_crossval=use_crossval) 212 213 print(f'total time was {round((time() - tt) / 60, 2)} mins') 214 215 # we actually only want alpha and intercept in this step and throw out X if we do upsampling normally 216 # for ease of use reason, we now use directly X for the reconstruction, but we should 217 # 1. multiply back X 218 # 2a. feed X in an empty array with a mask 219 # 220 # OR 221 # 222 # 2b. reconstruct the array and average things internally according to a mask 223 224 if block_size != block_up: 225 X_small_denoised = np.dot(D, alpha).T + intercept 226 227 X_final = np.zeros(((mask.shape[0],) + block_up), dtype=np.float32) 228 X_final[mask] = X_small_denoised.reshape(-1, *block_up) 229 recon = reconstruct_from_blocks(X_final, new_shape, block_size, block_up[:-1], new_overlap, weights=None) 230 231 del X_final, mask 232 return recon 233 234 235def harmonize_my_data(dataset, kwargs): 236 outpath = kwargs['outpath'] 237 path_D = kwargs['outfilename'] 238 center = kwargs['center'] 239 block_up = literal_eval(kwargs['block_up']) 240 block_size = literal_eval(kwargs['block_size']) 241 positivity = kwargs['positivity'] 242 fit_intercept = kwargs['fit_intercept'] 243 fix_mean = kwargs['fix_mean'] 244 use_crossval = kwargs['use_crossval'] 245 split_b0s = kwargs['split_b0s'] 246 b0_threshold = kwargs['b0_threshold'] 247 ncores = kwargs['ncores'] 248 nlambdas = kwargs['nlambdas'] 249 ext = kwargs['ext'] 250 overwrite = kwargs['overwrite'] 251 252 upsample = np.prod(block_up) > np.prod(block_size) 253 254 print(f"Now rebuilding {dataset['data']}") 255 D = np.load(path_D) 256 257 if dataset['folder'] is None: 258 relfolder = '' 259 else: 260 relfolder = dataset['folder'] 261 262 new_suffix = '_' + os.path.splitext(os.path.basename(path_D))[0] + ext 263 new_filename = os.path.basename(dataset['data']).replace(ext, new_suffix) 264 output_filename = os.path.join(outpath, relfolder, new_filename) 265 266 # Create subfolders tree if it does not exist 267 if not os.path.exists(os.path.dirname(output_filename)): 268 os.makedirs(os.path.dirname(output_filename), exist_ok=True) 269 270 if os.path.isfile(output_filename): 271 if overwrite: 272 warnings.warn(f'File already exists! Overwriting {output_filename}') 273 pass 274 else: 275 warnings.warn(f'File already exists! Skipping {output_filename}') 276 return 277 278 vol = nib.load(dataset['data']) 279 data = vol.get_fdata(caching='unchanged', dtype=np.float32) 280 affine = vol.affine 281 header = vol.header 282 283 mask = nib.load(dataset['mask']).get_fdata(caching='unchanged').astype(bool) 284 bvals = np.loadtxt(dataset['bval']) 285 bvecs = np.loadtxt(dataset['bvec']) 286 287 if np.shape(bvecs)[0] == 3: 288 bvecs = bvecs.T 289 290 if mask.shape != data.shape[:-1]: 291 error = f'mask shape {mask.shape} does not fit with the data shape {data.shape}' 292 raise ValueError(error) 293 294 if center: 295 # we need to pull down the 3D volume mean for the upsampling part to make sense though 296 # We use nanmean because we implicitly exclude voxels with a value of 0 from the mean this way 297 data_mean = np.nanmean(np.where(data != 0, data, np.nan), axis=(0, 1, 2), keepdims=True) 298 data -= data_mean 299 300 variance = None 301 302 # number of blocks is without b0, so we +1 the last dimension 303 if len(block_up) < data.ndim: 304 current_block_size = block_size[:-1] + (block_size[-1] + 1,) 305 current_block_up = block_up + (block_size[-1] + 1,) 306 else: 307 current_block_size = block_size[:-1] + (block_size[-1] + 1,) 308 current_block_up = block_up[:-1] + (block_up[-1] + 1,) 309 310 print(f'Output filename is {output_filename}') 311 312 factor = np.divide(current_block_up, current_block_size) 313 314 b0_loc = np.where(bvals <= b0_threshold)[0] 315 dwis = np.where(bvals > b0_threshold)[0] 316 num_b0s = len(b0_loc) 317 318 # We also convert bvecs associated with b0s to exactly (0,0,0), which 319 # is not always the case when we hack around with the scanner. 320 bvecs = np.where(bvals[:, None] <= b0_threshold, 0, bvecs) 321 322 # Average all b0s if we don't split them in the training set 323 if num_b0s > 1 and not split_b0s: 324 num_b0s = 1 325 data[..., b0_loc] = np.mean(data[..., b0_loc], axis=-1, keepdims=True) 326 327 # Split the b0s in a cyclic fashion along the training data 328 # If we only had one, cycle just return b0_loc indefinitely, 329 # else we go through all indexes. 330 np.random.shuffle(b0_loc) 331 split_b0s_idx = cycle(b0_loc) 332 sym_bvecs = np.vstack((bvecs, -bvecs)) 333 334 neighbors = angular_neighbors(sym_bvecs, current_block_size[-1] - 2) % data.shape[-1] 335 neighbors = neighbors[:data.shape[-1]] # everything was doubled for symmetry 336 337 full_indexes = [(dwi,) + tuple(neighbors[dwi]) for dwi in range(data.shape[-1]) if dwi in dwis] 338 indexes = greedy_set_finder(full_indexes) 339 340 # If we have more b0s than indexes, then we have to add a few more blocks since 341 # we won't do a full cycle. If we have more b0s than indexes after that, then it breaks. 342 if num_b0s > len(indexes): 343 the_rest = [rest for rest in full_indexes if rest not in indexes] 344 indexes += the_rest[:(num_b0s - len(indexes))] 345 346 if num_b0s > len(indexes): 347 error = f'Seems like you still have more b0s {num_b0s} than available blocks {len(indexes)}, either average them or deactivate subsampling.' 348 raise ValueError(error) 349 350 # Stuff happens here / we only pimp up dwis, not b0s 351 # actually we can't really pimp 4D stuff, SH are there for that 352 predicted_size = (int(data.shape[0] * factor[0]), 353 int(data.shape[1] * factor[1]), 354 int(data.shape[2] * factor[2]), 355 data.shape[-1]) 356 357 predicted = np.zeros(predicted_size, dtype=np.float32) 358 divider = np.zeros(predicted.shape[-1]) 359 360 # Put all idx + b0 in this array in each iteration 361 to_denoise = np.empty(data.shape[:-1] + (current_block_size[-1],), dtype=np.float64) 362 363 for i, idx in enumerate(indexes, start=1): 364 b0_loc = tuple((next(split_b0s_idx),)) 365 to_denoise[..., 0] = data[..., b0_loc].squeeze() 366 to_denoise[..., 1:] = data[..., idx] 367 divider[list(b0_loc + idx)] += 1 368 369 print(f'Now rebuilding volumes {np.hstack((b0_loc, idx))} / block {i} out of {len(indexes)}.') 370 371 predicted[..., b0_loc + idx] += rebuild(to_denoise, 372 mask, 373 D, 374 block_size=current_block_size, 375 block_up=current_block_up, 376 ncores=ncores, 377 positivity=positivity, 378 fix_mean=fix_mean, 379 nlambdas=nlambdas, 380 # center=center, 381 fit_intercept=fit_intercept, 382 use_crossval=use_crossval, 383 variance=variance) 384 predicted /= divider 385 386 # If we upsample, the mask does not match anymore, so we also resample it 387 if upsample: 388 factors = 1 / np.divide(mask.shape, predicted.shape[:-1]) 389 mask = zoom(mask, factors, order=0, mode='grid-constant', grid_mode=True) 390 mask_output_filename = output_filename.replace(ext, '_mask' + ext) 391 nib.Nifti1Image(mask.astype(np.int8), affine, header).to_filename(mask_output_filename) 392 393 if center: 394 np.add(predicted, data_mean, where=mask[..., None], out=predicted) 395 396 # clip negatives, which could happens at the borders 397 predicted[predicted < 0] = 0 398 399 nib.Nifti1Image(predicted, affine, header).to_filename(output_filename)
def
get_global_D( datasets, outfilename, block_size, block_up, ncores=None, batchsize=32, niter=500, use_std=False, positivity=False, fit_intercept=True, center=True, nlambdas=100, b0_threshold=20, split_b0s=True, **kwargs):
22def get_global_D(datasets, outfilename, block_size, block_up, ncores=None, batchsize=32, niter=500, 23 use_std=False, positivity=False, fit_intercept=True, center=True, nlambdas=100, 24 b0_threshold=20, split_b0s=True, **kwargs): 25 26 # get the data shape so we can preallocate some arrays 27 # we also have to assume all datasets have the same 3D shape obviously 28 shape = nib.load(datasets[0]['data']).header.get_data_shape() 29 30 # block size depends on the upsampling factor, so we pick the biggest one 31 upsample = np.prod(literal_eval(block_up)) > np.prod(literal_eval(block_size)) 32 33 if upsample: 34 current_block_size = literal_eval(block_up) 35 else: 36 current_block_size = literal_eval(block_size) 37 38 n_atoms = int(np.prod(current_block_size) * 2) 39 b0_block_size = tuple(current_block_size[:-1]) + ((current_block_size[-1] + 1,)) 40 overlap = b0_block_size 41 to_denoise = np.empty(shape[:-1] + (current_block_size[-1] + 1,), dtype=np.float32) 42 43 train_list = [] 44 variance_large = [] 45 46 for filename in datasets: 47 48 print(f"Now feeding dataset {filename['data']}") 49 50 mask = nib.load(filename['mask']).get_fdata(caching='unchanged').astype(bool) 51 data = nib.load(filename['data']).get_fdata(caching='unchanged', dtype=np.float32) 52 bvals = np.loadtxt(filename['bval']) 53 bvecs = np.loadtxt(filename['bvec']) 54 55 if np.shape(bvecs)[0] == 3: 56 bvecs = bvecs.T 57 58 if mask.shape != data.shape[:-1]: 59 error = f'mask shape {mask.shape} does not fit with the data shape {data.shape}' 60 raise ValueError(error) 61 62 data *= mask[..., None] 63 b0_loc = np.where(bvals <= b0_threshold)[0] 64 dwis = np.where(bvals > b0_threshold)[0] 65 num_b0s = len(b0_loc) 66 67 # We also convert bvecs associated with b0s to exactly (0,0,0), which 68 # is not always the case when we hack around with the scanner. 69 bvecs = np.where(bvals[:, None] <= b0_threshold, 0, bvecs) 70 71 # Average all b0s if we don't split them in the training set 72 if num_b0s > 1 and not split_b0s: 73 num_b0s = 1 74 data[..., b0_loc] = np.mean(data[..., b0_loc], axis=-1, keepdims=True) 75 76 # Split the b0s in a cyclic fashion along the training data 77 # If we only had one, cycle just return b0_loc indefinitely, 78 # else we go through all indexes. 79 np.random.shuffle(b0_loc) 80 split_b0s_idx = cycle(b0_loc) 81 sym_bvecs = np.vstack((bvecs, -bvecs)) 82 83 neighbors = angular_neighbors(sym_bvecs, current_block_size[-1] - 1) % data.shape[-1] 84 neighbors = neighbors[:data.shape[-1]] # everything was doubled for symmetry 85 86 full_indexes = [(dwi,) + tuple(neighbors[dwi]) for dwi in range(data.shape[-1]) if dwi in dwis] 87 indexes = greedy_set_finder(full_indexes) 88 89 # If we have more b0s than indexes, then we have to add a few more blocks since 90 # we won't do a full cycle. If we have more b0s than indexes after that, then it breaks. 91 if num_b0s > len(indexes): 92 the_rest = [rest for rest in full_indexes if rest not in indexes] 93 indexes += the_rest[:(num_b0s - len(indexes))] 94 95 if num_b0s > len(indexes): 96 error = f'Seems like you still have more b0s {num_b0s} than available blocks {len(indexes)}, either average them or deactivate subsampling.' 97 raise ValueError(error) 98 99 # whole global centering 100 if center: 101 data -= data.mean(axis=-1, keepdims=True) 102 103 for i, idx in enumerate(indexes): 104 b0_loc = tuple((next(split_b0s_idx),)) 105 106 # if we mix datasets, then we may need to change the storage array size 107 if to_denoise.shape[:-1] != data.shape[:-1]: 108 del to_denoise 109 to_denoise = np.empty(data.shape[:-1] + (current_block_size[-1] + 1,), dtype=np.float32) 110 111 to_denoise[..., 0] = data[..., b0_loc].squeeze() 112 to_denoise[..., 1:] = data[..., idx] 113 114 patches = extract_patches(to_denoise, b0_block_size, overlap, flatten=False) 115 axis = tuple(range(patches.ndim//2, patches.ndim)) 116 mask_patch = np.count_nonzero(patches, axis=axis) > np.prod(b0_block_size) // 2 117 patches = patches[mask_patch].reshape(-1, np.prod(b0_block_size)) 118 119 if use_std: 120 try: 121 variance = nib.load(filename['std']).get_fdata(caching='unchanged', dtype=np.float32)**2 * mask 122 variance = np.broadcast_to(variance[..., None], data.shape) 123 variance = extract_patches(variance, b0_block_size, overlap, flatten=False) 124 axis = tuple(range(variance.ndim//2, variance.ndim)) 125 variance = np.median(variance, axis=axis)[mask_patch].ravel() 126 except IOError: 127 warnings.warn(f"Volume {filename['std']} not found! Skipping using variance.") 128 variance = [None] 129 else: 130 variance = [None] 131 132 # check to build with np.r_ the whole list from stringnames instead 133 train_list += [patches] 134 variance_large += list(variance) 135 136 del data, mask, patches, variance 137 138 print('Fed everything in') 139 140 lengths = [l.shape[0] for l in train_list] 141 train_data = np.empty((np.sum(lengths), np.prod(b0_block_size))) 142 143 step = 0 144 for i in range(len(train_list)): 145 length = lengths[i] 146 idx = slice(step, step + length) 147 train_data[idx] = train_list[i].reshape(-1, np.prod(b0_block_size)) 148 step += length 149 150 del train_list 151 152 # we have variance as a N elements list - so check one element to see if it's an array 153 if variance_large[0] is not None: 154 variance_large = np.asarray(variance_large).ravel() 155 else: 156 variance_large = None 157 158 D = online_DL(train_data, ncores=ncores, positivity=positivity, fit_intercept=fit_intercept, standardize=True, 159 nlambdas=nlambdas, niter=niter, batchsize=batchsize, n_atoms=n_atoms, variance=variance_large, 160 progressbar=True, use_joblib=True) 161 162 return D
def
rebuild( data, mask, D, block_size, block_up, ncores=-1, nlambdas=100, positivity=False, variance=None, fix_mean=True, fit_intercept=False, use_crossval=False):
165def rebuild(data, mask, D, block_size, block_up, ncores=-1, nlambdas=100, 166 positivity=False, variance=None, fix_mean=True, fit_intercept=False, use_crossval=False): 167 168 data = data * mask[..., None] 169 170 if variance is not None: 171 variance *= mask 172 173 if len(block_size) == len(block_up): 174 last = block_up[-1] 175 else: 176 last = block_size[-1] 177 178 factor = np.divide(block_up, block_size) 179 new_shape = (int(data.shape[0] * factor[0]), 180 int(data.shape[1] * factor[1]), 181 int(data.shape[2] * factor[2]), 182 last) 183 overlap = (1, 1, 1, last) 184 new_overlap = overlap 185 186 if block_size == block_up: 187 D_depimpe = np.copy(D) 188 else: 189 D_depimpe = depimp_zoom(D, block_size, block_up) 190 191 blocks = extract_patches(data, block_size, overlap, flatten=False).reshape(-1, np.prod(block_size)) 192 del data 193 194 # skip empty rows from training since they are probably masked background 195 mask = np.count_nonzero(blocks, axis=1) > np.prod(block_size) // 2 196 197 # get the variance as blocks 198 if variance is not None: 199 variance = extract_patches(variance, block_size[:-1], overlap[:-1], flatten=True) 200 axis = tuple(range(1, len(block_size) + 1)) 201 variance = np.median(variance, axis=axis) 202 203 # if we are on an edge, variance can be 0, so truncate those cases as well 204 np.logical_and(mask, variance > 0, out=mask) 205 variance = variance[mask] 206 207 blocks = blocks[mask] 208 209 tt = time() 210 X_small_denoised, alpha, intercept, _ = solve_l1(blocks, D_depimpe, variance=variance, return_all=True, nlambdas=nlambdas, use_joblib=True, 211 positivity=positivity, fit_intercept=True, standardize=True, progressbar=True, verbose=5, 212 ncores=ncores, use_crossval=use_crossval) 213 214 print(f'total time was {round((time() - tt) / 60, 2)} mins') 215 216 # we actually only want alpha and intercept in this step and throw out X if we do upsampling normally 217 # for ease of use reason, we now use directly X for the reconstruction, but we should 218 # 1. multiply back X 219 # 2a. feed X in an empty array with a mask 220 # 221 # OR 222 # 223 # 2b. reconstruct the array and average things internally according to a mask 224 225 if block_size != block_up: 226 X_small_denoised = np.dot(D, alpha).T + intercept 227 228 X_final = np.zeros(((mask.shape[0],) + block_up), dtype=np.float32) 229 X_final[mask] = X_small_denoised.reshape(-1, *block_up) 230 recon = reconstruct_from_blocks(X_final, new_shape, block_size, block_up[:-1], new_overlap, weights=None) 231 232 del X_final, mask 233 return recon
def
harmonize_my_data(dataset, kwargs):
236def harmonize_my_data(dataset, kwargs): 237 outpath = kwargs['outpath'] 238 path_D = kwargs['outfilename'] 239 center = kwargs['center'] 240 block_up = literal_eval(kwargs['block_up']) 241 block_size = literal_eval(kwargs['block_size']) 242 positivity = kwargs['positivity'] 243 fit_intercept = kwargs['fit_intercept'] 244 fix_mean = kwargs['fix_mean'] 245 use_crossval = kwargs['use_crossval'] 246 split_b0s = kwargs['split_b0s'] 247 b0_threshold = kwargs['b0_threshold'] 248 ncores = kwargs['ncores'] 249 nlambdas = kwargs['nlambdas'] 250 ext = kwargs['ext'] 251 overwrite = kwargs['overwrite'] 252 253 upsample = np.prod(block_up) > np.prod(block_size) 254 255 print(f"Now rebuilding {dataset['data']}") 256 D = np.load(path_D) 257 258 if dataset['folder'] is None: 259 relfolder = '' 260 else: 261 relfolder = dataset['folder'] 262 263 new_suffix = '_' + os.path.splitext(os.path.basename(path_D))[0] + ext 264 new_filename = os.path.basename(dataset['data']).replace(ext, new_suffix) 265 output_filename = os.path.join(outpath, relfolder, new_filename) 266 267 # Create subfolders tree if it does not exist 268 if not os.path.exists(os.path.dirname(output_filename)): 269 os.makedirs(os.path.dirname(output_filename), exist_ok=True) 270 271 if os.path.isfile(output_filename): 272 if overwrite: 273 warnings.warn(f'File already exists! Overwriting {output_filename}') 274 pass 275 else: 276 warnings.warn(f'File already exists! Skipping {output_filename}') 277 return 278 279 vol = nib.load(dataset['data']) 280 data = vol.get_fdata(caching='unchanged', dtype=np.float32) 281 affine = vol.affine 282 header = vol.header 283 284 mask = nib.load(dataset['mask']).get_fdata(caching='unchanged').astype(bool) 285 bvals = np.loadtxt(dataset['bval']) 286 bvecs = np.loadtxt(dataset['bvec']) 287 288 if np.shape(bvecs)[0] == 3: 289 bvecs = bvecs.T 290 291 if mask.shape != data.shape[:-1]: 292 error = f'mask shape {mask.shape} does not fit with the data shape {data.shape}' 293 raise ValueError(error) 294 295 if center: 296 # we need to pull down the 3D volume mean for the upsampling part to make sense though 297 # We use nanmean because we implicitly exclude voxels with a value of 0 from the mean this way 298 data_mean = np.nanmean(np.where(data != 0, data, np.nan), axis=(0, 1, 2), keepdims=True) 299 data -= data_mean 300 301 variance = None 302 303 # number of blocks is without b0, so we +1 the last dimension 304 if len(block_up) < data.ndim: 305 current_block_size = block_size[:-1] + (block_size[-1] + 1,) 306 current_block_up = block_up + (block_size[-1] + 1,) 307 else: 308 current_block_size = block_size[:-1] + (block_size[-1] + 1,) 309 current_block_up = block_up[:-1] + (block_up[-1] + 1,) 310 311 print(f'Output filename is {output_filename}') 312 313 factor = np.divide(current_block_up, current_block_size) 314 315 b0_loc = np.where(bvals <= b0_threshold)[0] 316 dwis = np.where(bvals > b0_threshold)[0] 317 num_b0s = len(b0_loc) 318 319 # We also convert bvecs associated with b0s to exactly (0,0,0), which 320 # is not always the case when we hack around with the scanner. 321 bvecs = np.where(bvals[:, None] <= b0_threshold, 0, bvecs) 322 323 # Average all b0s if we don't split them in the training set 324 if num_b0s > 1 and not split_b0s: 325 num_b0s = 1 326 data[..., b0_loc] = np.mean(data[..., b0_loc], axis=-1, keepdims=True) 327 328 # Split the b0s in a cyclic fashion along the training data 329 # If we only had one, cycle just return b0_loc indefinitely, 330 # else we go through all indexes. 331 np.random.shuffle(b0_loc) 332 split_b0s_idx = cycle(b0_loc) 333 sym_bvecs = np.vstack((bvecs, -bvecs)) 334 335 neighbors = angular_neighbors(sym_bvecs, current_block_size[-1] - 2) % data.shape[-1] 336 neighbors = neighbors[:data.shape[-1]] # everything was doubled for symmetry 337 338 full_indexes = [(dwi,) + tuple(neighbors[dwi]) for dwi in range(data.shape[-1]) if dwi in dwis] 339 indexes = greedy_set_finder(full_indexes) 340 341 # If we have more b0s than indexes, then we have to add a few more blocks since 342 # we won't do a full cycle. If we have more b0s than indexes after that, then it breaks. 343 if num_b0s > len(indexes): 344 the_rest = [rest for rest in full_indexes if rest not in indexes] 345 indexes += the_rest[:(num_b0s - len(indexes))] 346 347 if num_b0s > len(indexes): 348 error = f'Seems like you still have more b0s {num_b0s} than available blocks {len(indexes)}, either average them or deactivate subsampling.' 349 raise ValueError(error) 350 351 # Stuff happens here / we only pimp up dwis, not b0s 352 # actually we can't really pimp 4D stuff, SH are there for that 353 predicted_size = (int(data.shape[0] * factor[0]), 354 int(data.shape[1] * factor[1]), 355 int(data.shape[2] * factor[2]), 356 data.shape[-1]) 357 358 predicted = np.zeros(predicted_size, dtype=np.float32) 359 divider = np.zeros(predicted.shape[-1]) 360 361 # Put all idx + b0 in this array in each iteration 362 to_denoise = np.empty(data.shape[:-1] + (current_block_size[-1],), dtype=np.float64) 363 364 for i, idx in enumerate(indexes, start=1): 365 b0_loc = tuple((next(split_b0s_idx),)) 366 to_denoise[..., 0] = data[..., b0_loc].squeeze() 367 to_denoise[..., 1:] = data[..., idx] 368 divider[list(b0_loc + idx)] += 1 369 370 print(f'Now rebuilding volumes {np.hstack((b0_loc, idx))} / block {i} out of {len(indexes)}.') 371 372 predicted[..., b0_loc + idx] += rebuild(to_denoise, 373 mask, 374 D, 375 block_size=current_block_size, 376 block_up=current_block_up, 377 ncores=ncores, 378 positivity=positivity, 379 fix_mean=fix_mean, 380 nlambdas=nlambdas, 381 # center=center, 382 fit_intercept=fit_intercept, 383 use_crossval=use_crossval, 384 variance=variance) 385 predicted /= divider 386 387 # If we upsample, the mask does not match anymore, so we also resample it 388 if upsample: 389 factors = 1 / np.divide(mask.shape, predicted.shape[:-1]) 390 mask = zoom(mask, factors, order=0, mode='grid-constant', grid_mode=True) 391 mask_output_filename = output_filename.replace(ext, '_mask' + ext) 392 nib.Nifti1Image(mask.astype(np.int8), affine, header).to_filename(mask_output_filename) 393 394 if center: 395 np.add(predicted, data_mean, where=mask[..., None], out=predicted) 396 397 # clip negatives, which could happens at the borders 398 predicted[predicted < 0] = 0 399 400 nib.Nifti1Image(predicted, affine, header).to_filename(output_filename)