def test_simulated_2d(self): # # Kind of neat - seeing how phase changes with coil sensitivity... # view(np.angle(coil_ims0)) # Do GS solution to ESM then take SOS recon_gs = np.zeros(self.coil_ims0.shape,dtype='complex') for ii in range(self.num_coils): recon_gs[ii,...] = gs_recon(self.coil_ims0[ii,...],self.coil_ims1[ii,...],self.coil_ims2[ii,...],self.coil_ims3[ii,...]) # view(np.angle(recon_gs)) # realize this is actually a movie - they all just look the same... recon_gs_sos = sos(recon_gs,axes=(0)) view(recon_gs_sos) # Do PCA n_components = 4 pca0 = coil_pca(self.coil_ims0,coil_dim=0,n_components=n_components) pca1 = coil_pca(self.coil_ims1,coil_dim=0,n_components=n_components) pca2 = coil_pca(self.coil_ims2,coil_dim=0,n_components=n_components) pca3,expl_var = coil_pca(self.coil_ims3,coil_dim=0,n_components=n_components,give_explained_var=True) # view(expl_var.real) # view(np.angle(pca3)) # Do GS solution to ESM then take SOS, this time using PCA'd data recon_pca_gs = np.zeros(pca0.shape,dtype='complex') for ii in range(n_components): # view(np.concatenate((pca0[ii,...],pca1[ii,...],pca2[ii,...],pca3[ii,...]))) recon_pca_gs[ii,...] = gs_recon(pca0[ii,...],pca1[ii,...],pca2[ii,...],pca3[ii,...]) # view(np.angle(recon_pca_gs)) recon_pca_gs_sos = sos(recon_pca_gs,axes=(0))
def get_coil_combine_funs(N, v='-v0'): '''Return list of functions that perform coil combination.''' fft = lambda x0, ax=(1, 2): np.fft.fftshift( np.fft.fft2(np.fft.fftshift(x0, axes=ax), axes=ax), axes=ax) ifft = lambda x0, ax=(1, 2): np.fft.fftshift( np.fft.ifft2(np.fft.fftshift(x0, axes=ax), axes=ax), axes=ax) return ( [ # Walsh lambda x0: np.sum(walsh(x0)[0].conj() * x0, axis=0), # Inati lambda x0: inati(x0)[1], # PCA (imspace) lambda x0: coil_pca(x0, coil_dim=0, n_components=1), # PCA (kspace) lambda x0: ifft(coil_pca( fft(x0, ax=(1, 2)), coil_dim=0, n_components=1), ax=(1, 2)), # Direct method lambda x0: np.sum(np.moveaxis( bart(1, 'caldir %d' % int(N / 2), fft(np.moveaxis(x0, 0, -1)[:, :, None, :], ax=(0, 1))). squeeze(), -1, 0).conj() * x0, axis=0), # Geometric lambda x0: ifft(bart( 1, 'cc -p 1 -A -G', fft(np.moveaxis(x0, 0, -1)[:, :, None, :], ax=(0, 1))), ax=(0, 1)).squeeze(), # # ESPIRiT -- using cc, don't use this one! # lambda x0: ifft(bart(1, 'cc -p 1 -A -E', fft( # x0.T[:, :, None, :], ax=(0, 1))), ax=(0, 1)).squeeze() # ESPIRiT -- using ecalib, use this one! # use -v for numerical phantom lambda x0: np.sum(np.moveaxis( bart(1, 'ecalib -a -m1 -P -S %s' % v, fft(np.moveaxis(x0, 0, -1)[:, :, None, :], ax=(0, 1))). squeeze(), -1, 0).conj() * x0, axis=0), # SVD -- suprisingly good! lambda x0: ifft(bart( 1, 'cc -p 1 -A -S', fft(np.moveaxis(x0, 0, -1)[:, :, None, :], ax=(0, 1))), ax=(0, 1)).squeeze() ], [ 'Walsh', 'Inati', 'PCA (image space)', 'PCA (k-space)', 'Direct', 'Geometric', 'ESPIRiT', 'SVD' ])
def test_simulated_2d_kspace(self): # Do PCA on kspace n_components = 4 pca0 = coil_pca(self.kspace_coil_ims0,coil_dim=0,n_components=n_components) pca1 = coil_pca(self.kspace_coil_ims1,coil_dim=0,n_components=n_components) pca2 = coil_pca(self.kspace_coil_ims2,coil_dim=0,n_components=n_components) pca3,expl_var = coil_pca(self.kspace_coil_ims3,coil_dim=0,n_components=n_components,give_explained_var=True) # view(expl_var.imag) # Put it back in image space pca0 = np.fft.ifftshift(np.fft.ifft2(np.fft.ifftshift(pca0,axes=(1,2)),axes=(1,2)),axes=(1,2)) pca1 = np.fft.ifftshift(np.fft.ifft2(np.fft.ifftshift(pca1,axes=(1,2)),axes=(1,2)),axes=(1,2)) pca2 = np.fft.ifftshift(np.fft.ifft2(np.fft.ifftshift(pca2,axes=(1,2)),axes=(1,2)),axes=(1,2)) pca3 = np.fft.ifftshift(np.fft.ifft2(np.fft.ifftshift(pca3,axes=(1,2)),axes=(1,2)),axes=(1,2)) # Do GS solution to ESM then take SOS, this time using PCA'd data recon_pca_gs = np.zeros(pca0.shape,dtype='complex') for ii in range(n_components): # view(np.concatenate((pca0[ii,...],pca1[ii,...],pca2[ii,...],pca3[ii,...]))) recon_pca_gs[ii,...] = gs_recon(pca0[ii,...],pca1[ii,...],pca2[ii,...],pca3[ii,...]) # view(np.angle(recon_pca_gs)) recon_pca_gs_sos = sos(recon_pca_gs,axes=(0))
def view(image, load_opts=None, is_raw=None, is_line=None, prep=None, fft=False, fft_axes=None, fftshift=None, avg_axis=None, coil_combine_axis=None, coil_combine_method='walsh', coil_combine_opts=None, is_imspace=False, mag=None, phase=False, log=False, imshow_opts={'cmap': 'gray'}, montage_axis=None, montage_opts={'padding_width': 2}, movie_axis=None, movie_interval=50, movie_repeat=True, save_npy=False, debug_level=logging.DEBUG, test_run=False): '''Image viewer to quickly inspect data. Parameters ---------- image : str or array_like Name of the file including the file extension or numpy array. load_opts : dict, optional Options to pass to data loader. is_raw : bool, optional Inform if data is raw. Will attempt to guess from extension. is_line : bool, optional Whether or not this is a line plot (as opposed to image). prep : callable, optional Lambda function to process the data before it's displayed. fft : bool, optional Whether or not to perform n-dimensional FFT of data. fft_axes : tuple, optional Axis to perform FFT over, determines dimension of n-dim FFT. fftshift : bool, optional Whether or not to perform fftshift. Defaults to True if fft. avg_axis : int, optional Take average over given set of axes. coil_combine_axis : int, optional Which axis to perform coil combination over. coil_combine_method : {'walsh', 'inati', 'pca'}, optional Method to use to combine coils. coil_combine_opts : dict, optional Options to pass to the coil combine method. is_imspace : bool, optional Whether or not the data is in image space. For coil combine. mag : bool, optional View magnitude image. Defaults to True if data is complex. phase : bool, optional View phase image. log : bool, optional View log of magnitude data. Defaults to False. imshow_opts : dict, optional Options to pass to imshow. Defaults to { 'cmap'='gray' }. montage_axis : int, optional Which axis is the number of images to be shown. montage_opts : dict, optional Additional options to pass to the skimage.util.montage. movie_axis : int, optional Which axis is the number of frames of the movie. movie_interval : int, optional Interval to give to animation frames. movie_repeat : bool, optional Whether or not to put movie on endless loop. save_npy : bool, optional Whether or not to save the output as npy file. debug_level : logging_level, optional Level of verbosity. See logging module. test_run : bool, optional Doesn't show figure, returns debug object. Mostly for testing. Returns ------- data : array_like Image data shown in plot. dict, optional All local variables when test_run=True. Raises ------ Exception When file type is not in ['dat', 'npy', 'mat', 'h5']. ValueError When coil combine requested, but fft_axes not set. AssertionError When Walsh coil combine requested but len(fft_axes) =/= 2. ValueError When there are too many dimension to display. ''' # Set up logging... logging.basicConfig(format='%(levelname)s: %(message)s', level=debug_level) # Add some default empty params if load_opts is None: load_opts = dict() if coil_combine_opts is None: coil_combine_opts = dict() # If the user wants to look at numpy matrix, recognize that # filename is the matrix: if isinstance(image, np.ndarray): logging.info('Image is a numpy array!') data = image elif isinstance(image, list): # If user sends a list, try casting to numpy array logging.info('Image is a list, trying to cast as numpy array...') data = np.array(image) else: # Find the file extension ext = pathlib.Path(image).suffix # If the user says data is raw, then trust the user if is_raw or (ext == '.dat'): data = load_raw(image, **load_opts) elif ext == '.npy': data = np.load(image, **load_opts) elif ext == '.mat': # Help out the user a little bit... If only one # nontrivial key is found then go ahead and assume it's # that one data = None if not list(load_opts): keys = mat_keys(image, no_print=True) if len(keys) == 1: logging.info(('No key supplied, but one key for' ' mat dictionary found (%s), using' ' it...'), keys[0]) data = load_mat(image, key=keys[0]) # If we can't help the user out, just load it as normal if data is None: data = load_mat(image, **load_opts) elif ext == '.h5': data = load_ismrmrd(image, **load_opts) else: raise Exception('File type %s not understood!' % ext) # Right off the bat, remove singleton dimensions if 1 in data.shape: logging.info('Current shape %s: Removing singleton dimensions...', str(data.shape)) data = data.squeeze() logging.info('New shape: %s', str(data.shape)) # Average out over any axis specified if avg_axis is not None: data = np.mean(data, axis=avg_axis) # Let's collapse the coil dimension using the specified algorithm if coil_combine_axis is not None: # We'll need to know the fft_axes if the data is in kspace if not is_imspace and fft_axes is None: msg = ('fft_axes required to do coil combination of ' 'k-space data!') raise ValueError(msg) if coil_combine_method == 'walsh': msg = 'Walsh only works with 2D images!' assert len(fft_axes) == 2, msg logging.info('Performing Walsh 2d coil combine across axis %d...', list(range(data.ndim))[coil_combine_axis]) # We need to do this is image domain... if not is_imspace: fft_data = np.fft.ifftshift(np.fft.ifftn(data, axes=fft_axes), axes=fft_axes) else: fft_data = data # walsh expects (coil,y,x) fft_data = np.moveaxis(fft_data, coil_combine_axis, 0) csm_walsh, _ = calculate_csm_walsh(fft_data, **coil_combine_opts) fft_data = np.sum(csm_walsh * np.conj(fft_data), axis=0, keepdims=True) # Sum kept the axis where coil used to be so we can rely # on fft_axes to be correct when do the FT back to kspace fft_data = np.moveaxis(fft_data, 0, coil_combine_axis) # Now move back to kspace and squeeze the dangling axis if not is_imspace: data = np.fft.fftn(np.fft.fftshift(fft_data, axes=fft_axes), axes=fft_axes).squeeze() else: data = fft_data.squeeze() elif coil_combine_method == 'inati': logging.info('Performing Inati coil combine across axis %d...', list(range(data.ndim))[coil_combine_axis]) # Put things into image space if we need to if not is_imspace: fft_data = np.fft.ifftshift(np.fft.ifftn(data, axes=fft_axes), axes=fft_axes) else: fft_data = data # inati expects (coil,z,y,x) fft_data = np.moveaxis(fft_data, coil_combine_axis, 0) _, fft_data = calculate_csm_inati_iter(fft_data, **coil_combine_opts) # calculate_csm_inati_iter got rid of the axis, so we # need to add it back in so we can use the same fft_axes fft_data = np.expand_dims(fft_data, coil_combine_axis) # Now move back to kspace and squeeze the dangling axis if not is_imspace: data = np.fft.fftn(np.fft.fftshift(fft_data, axes=fft_axes), axes=fft_axes).squeeze() else: data = fft_data.squeeze() elif coil_combine_method == 'pca': logging.info('Performing PCA coil combine across axis %d...', list(range(data.ndim))[coil_combine_axis]) # We don't actually care whether we do this is in kspace # or imspace if not is_imspace: logging.info(('PCA doesn\'t care that image might not be in' 'image space.')) if 'n_components' not in coil_combine_opts: n_components = int(data.shape[coil_combine_axis] / 2) logging.info('Deciding to use %d components.', n_components) coil_combine_opts['n_components'] = n_components data = coil_pca(data, coil_dim=coil_combine_axis, **coil_combine_opts) else: logging.error('Coil combination method "%s" not supported!', coil_combine_method) logging.warning('Attempting to skip coil combination!') # Show the image. Let's also try to help the user out again. If # we have 3 dimensions, one of them is probably a montage or a # movie. If the user didn't tell us anything, it's going to # crash anyway, so let's try guessing what's going on... if (data.ndim > 2) and (movie_axis is None) and (montage_axis is None): logging.info('Data has %d dimensions!', data.ndim) # We will always assume that inplane resolution is larger # than the movie/montage dimensions # If only 3 dims, then one must be montage/movie dimension if data.ndim == 3: # assume inplane resolution larger than movie/montage dim min_axis = np.argmin(data.shape) # Assume 10 is the most we'll want to montage if data.shape[min_axis] < 10: logging.info('Guessing axis %d is montage...', min_axis) montage_axis = min_axis else: logging.info('Guessing axis %d is movie...', min_axis) movie_axis = min_axis # If 4 dims, guess smaller dim will be montage, larger guess # movie elif data.ndim == 4: montage_axis = np.argmin(data.shape) # Consider the 4th dimension as the color channel in # skimontage montage_opts['multichannel'] = True # Montage will go through skimontage which will remove the # montage_axis dimension, so find the movie dimension # without the montage dimension: tmp = np.delete(data.shape[:], montage_axis) movie_axis = np.argmin(tmp) logging.info(('Guessing axis %d is montage, axis %d will be ' 'movie...'), montage_axis, movie_axis) # fft and fftshift will require fft_axes. If the user didn't # give us axes, let's try to guess them: if (fft or (fftshift is not False)) and (fft_axes is None): all_axes = list(range(data.ndim)) if (montage_axis is not None) and (movie_axis is not None): fft_axes = np.delete( all_axes, [all_axes[montage_axis], all_axes[movie_axis]]) elif montage_axis is not None: fft_axes = np.delete(all_axes, all_axes[montage_axis]) elif movie_axis is not None: fft_axes = np.delete(all_axes, all_axes[movie_axis]) else: fft_axes = all_axes logging.info('User did not supply fft_axes, guessing %s...', str(fft_axes)) # Perform n-dim FFT across fft_axes if desired if fft: data = np.fft.fftn(data, axes=fft_axes) # Perform fftshift if desired. If the user does not specify # fftshift, if fft is performed, then fftshift will also be # performed. To override this behavior, simply supply # fftshift=False in the arguments. Similarly, to force fftshift # even if no fft was performed, supply fftshift=True. if fft and (fftshift is None): fftshift = True elif fftshift is None: fftshift = False if fftshift: data = np.fft.fftshift(data, axes=fft_axes) # Take absolute value to view if necessary, must take abs before # log if np.iscomplexobj(data) or (mag is True) or (log is True): data = np.abs(data) if log: # Don't take log of 0! data[data == 0] = np.nan data = np.log(data) # If we asked for phase, let's work out how we'll do that if phase and ((mag is None) or (mag is True)): # TODO: figure out which axis to concatenate the phase onto data = np.concatenate((data, np.angle(data)), axis=fft_axes[-1]) elif phase and (mag is False): data = np.angle(data) # Run any processing before imshow if callable(prep): data = prep(data) # If it's just a line plot, skip all the montage, movie stuff if is_line: montage_axis = None movie_axis = None if montage_axis is not None: # We can deal with 4 dimensions if we allow multichannel if data.ndim == 4 and 'multichannel' not in montage_opts: montage_opts['multichannel'] = True # When we move the movie_axis to the end, we will need to # adjust the montage axis in case we displace it. We # need to move it to the end so skimontage will consider # it the multichannel data = np.moveaxis(data, movie_axis, -1) if movie_axis < montage_axis: montage_axis -= 1 # Put the montage axis in front data = np.moveaxis(data, montage_axis, 0) try: data = skimontage(data, **montage_opts) except ValueError: # Multichannel might be erronously set montage_opts['multichannel'] = False data = skimontage(data, **montage_opts) if data.ndim == 3: # If we had 4 dimensions, we just lost one, so now we # need to know where the movie dimension went off to... if movie_axis > montage_axis: movie_axis -= 1 # Move the movie axis back, it's no longer the color # channel data = np.moveaxis(data, -1, movie_axis) if movie_axis is not None: fig = plt.figure() data = np.moveaxis(data, movie_axis, -1) im = plt.imshow(data[..., 0], **imshow_opts) def updatefig(frame): '''Animation function for figure.''' im.set_array(data[..., frame]) return im, # pylint: disable=R1707 _ani = animation.FuncAnimation(fig, updatefig, frames=data.shape[-1], interval=movie_interval, blit=True, repeat=movie_repeat) if not test_run: plt.show() else: if data.ndim == 1 or is_line: plt.plot(data) elif data.ndim == 2: # Just a regular old 2d image... plt.imshow(np.nan_to_num(data), **imshow_opts) else: raise ValueError('%d is too many dimensions!' % data.ndim) if not test_run: plt.show() # Save what we looked at if desired if save_npy: if ext: filename = image else: filename = 'view-output' np.save(filename, data) # If we're testing, return all the local vars if test_run: return locals() return data
axes=(1, 2)), axes=(1, 2)) view(np.angle(coil_ims0)) # Do GS solution to ESM then take SOS recon_gs = np.zeros(coil_ims0.shape, dtype='complex') for ii in range(num_coils): recon_gs[ii, ...] = gs_recon(coil_ims0[ii, ...], coil_ims1[ii, ...], coil_ims2[ii, ...], coil_ims3[ii, ...]) recon_gs_sos = sos(recon_gs, axes=(0)) view(recon_gs_sos) # Do PCA n_components = 4 pca0 = coil_pca(coil_ims0, coil_dim=0, n_components=n_components) pca1 = coil_pca(coil_ims1, coil_dim=0, n_components=n_components) pca2 = coil_pca(coil_ims2, coil_dim=0, n_components=n_components) pca3, expl_var = coil_pca(coil_ims3, coil_dim=0, n_components=n_components, give_explained_var=True) view(expl_var.real) view(np.angle(pca3)) # Do GS solution to ESM then take SOS, this time using PCA'd data recon_pca_gs = np.zeros(pca0.shape, dtype='complex') for ii in range(n_components): recon_pca_gs[ii, ...] = gs_recon(pca0[ii, ...], pca1[ii, ...], pca2[ii, ...], pca3[ii, ...]) view(np.angle(recon_pca_gs))