def plot_synthetic_banding(self, input, output, results): imgs = [] imgs.append(input[:, :, 0] + 1j * input[:, :, 1]) imgs.append(input[:, :, 2] + 1j * input[:, :, 3]) imgs.append(output[:, :, 0] + 1j * output[:, :, 1]) imgs.append(output[:, :, 2] + 1j * output[:, :, 3]) # Run the elliptical model on input dataset as ground truth from mr_utils.recon.ssfp import gs_recon pc0 = input[:, :, 0] + 1j * input[:, :, 1] pc90 = output[:, :, 0] + 1j * output[:, :, 1] pc180 = input[:, :, 2] + 1j * input[:, :, 3] pc270 = output[:, :, 2] + 1j * output[:, :, 3] recon_truth = gs_recon(pc0, pc90, pc180, pc270) imgs.append(recon_truth) imgs.append(results[:, :, 0] + 1j * results[:, :, 1]) imgs.append(results[:, :, 2] + 1j * results[:, :, 3]) # Run the elliptical model to verify the reconstruction pc0 = input[:, :, 0] + 1j * input[:, :, 1] pc90 = results[:, :, 0] + 1j * results[:, :, 1] pc180 = input[:, :, 2] + 1j * input[:, :, 3] pc270 = results[:, :, 2] + 1j * results[:, :, 3] recon_using_prediction = gs_recon(pc0, pc90, pc180, pc270) imgs.append(recon_using_prediction) count = len(imgs) # Count of plots for i in range(count): plt.subplot(1, count, i + 1) plt.imshow(np.abs(imgs[i]), cmap='gray') plt.axis('off') plt.show()
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 test_gs_recon3d(self): from mr_utils.recon.ssfp import gs_recon, gs_recon3d # Try individually I0 = gs_recon(self.I1, self.I2, self.I3, self.I4) I0 = np.stack((I0, gs_recon(self.I1, self.I2, self.I3, self.I4)), axis=-1) I1 = gs_recon3d(np.stack((self.I1, self.I1), axis=-1), np.stack((self.I2, self.I2), axis=-1), np.stack((self.I3, self.I3), axis=-1), np.stack((self.I4, self.I4), axis=-1)) self.assertTrue(np.allclose(I0, I1))
def get_true_im_numerical_phantom(): # Load in params for simulation params = get_numerical_phantom_params(SNR=None) dim = params['dim'] pc_vals = params['pc_vals'] # Find true im by using no noise gs_recon averaged over several # different phase-cycles to remove residual banding # true_im = bssfp_2d_cylinder(dims=(dim,dim),phase_cyc=0) true_im = np.zeros((dim, dim), dtype='complex') avgs = [0, np.pi / 6, np.pi / 3, np.pi / 4] # avgs = [ 0 ] for ii, extra in enumerate(avgs): pc0 = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=(pc_vals[0] + extra)) pc1 = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=(pc_vals[1] + extra)) pc2 = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=(pc_vals[2] + extra)) pc3 = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=(pc_vals[3] + extra)) true_im += gs_recon(pc0, pc1, pc2, pc3) true_im /= len(avgs) true_im += 1j * true_im # view(np.concatenate((true_im,pc0))) return (true_im)
def load_brain(self): # Load npy data for each phase cycle pc0 = np.load( './data/brain/meas_MID23_TRUFI_STW_TE2_5_FID33594.dat_avg_coil_combined.npy' ) pc90 = np.load( './data/brain/meas_MID24_TRUFI_STW_TE2_5_dphi_90_FID33595.dat_avg_coil_combined.npy' ) pc180 = np.load( './data/brain/meas_MID25_TRUFI_STW_TE2_5_dphi_180_FID33596.dat_avg_coil_combined.npy' ) pc270 = np.load( './data/brain/meas_MID26_TRUFI_STW_TE2_5_dphi_270_FID33597.dat_avg_coil_combined.npy' ) # Shape should be (z,x,y,pc) imgs = np.stack((pc0, pc90, pc180, pc270)).transpose((3, 1, 2, 0)) # view(imgs.T) # out = np.stack((pc90,pc270)).transpose((3,1,2,0)) # Output should be solution to ESM for each slice out = np.zeros((imgs.shape[0], imgs.shape[1], imgs.shape[2]), dtype='complex') for kk in range(imgs.shape[0]): out[kk, ...] = gs_recon(pc0[..., kk], pc90[..., kk], pc180[..., kk], pc270[..., kk]) # view(out) return imgs, out
def test_gs_recon(self): from mr_utils.recon.ssfp import gs_recon_for_loop, gs_recon # Make sure it doesn't matter if we go pixel by pixel or do the whole # matrix at once I0 = gs_recon_for_loop(self.I1, self.I2, self.I3, self.I4) I1 = gs_recon(self.I1, self.I2, self.I3, self.I4) self.assertTrue(np.allclose(I0, I1))
def gs_then_cc(coil_ims, noise_ims): '''Do GS recon on each coil then coil combine.''' # How many coils? nc = coil_ims.shape[0] # Now the other way gs = np.zeros(coil_ims.shape[:-1], dtype='complex') n = np.zeros(noise_ims.shape[:-1], dtype='complex') for ii in range(nc): gs[ii, ...] = gs_recon(coil_ims[ii, ...], pc_axis=-1) # GS recon for the noise, too, to make sure we track what # happens to the noise characteristics n[ii, ...] = gs_recon(noise_ims[ii, ...], pc_axis=-1) csm1 = walsh(gs, n) return np.sum(csm1 * np.conj(gs), axis=0)
def test_gs(self): from mr_utils.sim.ssfp import ssfp from mr_utils.recon.ssfp import gs_recon pcs = np.zeros((4, self.dim, self.dim), dtype='complex') for ii, pc in enumerate([0, np.pi / 2, np.pi, 3 * np.pi / 2]): pcs[ii, ...] = ssfp(**self.args, phase_cyc=pc) # view(pcs) recon = gs_recon(*[x.squeeze() for x in np.split(pcs, 4)])
def cc_then_gs_no_avg(coil_ims, noise_ims): '''Coil combine using individual csm then GS recon.''' # How many phase-cycles? npcs = coil_ims.shape[-1] # Coil combine each phase-cycle separately cc1 = np.zeros(coil_ims.shape[1:], dtype='complex') for ii in range(npcs): csm = walsh(coil_ims[..., ii], noise_ims[..., ii]) cc1[..., ii] = np.sum(csm * np.conj(coil_ims[..., ii]), axis=0) return gs_recon(cc1, pc_axis=-1)
def cc_then_gs_avg_pc(coil_ims, noise_ims): '''Do coil combine averaging correcting for PCs then do GS.''' # How many phase-cycles? npcs = coil_ims.shape[-1] # Average the correlation matrices csm0 = walsh_gs(coil_ims, coil_axis=0, pc_axis=-1, avg_method='pc') # Apply to each phase-cycle cc0 = np.zeros(coil_ims.shape[1:], dtype='complex') for ii in range(npcs): cc0[..., ii] = np.sum(csm0 * np.conj(coil_ims[..., ii]), axis=0) return gs_recon(cc0, pc_axis=-1)
def gs_field_map(I0, I1, I2, I3, TR, gs_recon_opts=None): '''Use the elliptical signal model to estimate the field map. Parameters ---------- I0 : array_like First of the first phase-cycle pair (0 degrees). I2 : array_like Second of the first phase-cycle pair (180 degrees). I1 : array_like First of the second phase-cycle pair (90 degrees). I3 : array_like Second of the second phase-cycle pair (270 degrees). TR : float Repetition time of acquisitons in ms. gs_recon_opts : dict, optional Options to pass to gs_recon. Returns ------- gsfm : array_like Wrapped field map in hertz. Notes ----- I0, I2 and I1, I3 must be phase-cycle pairs, meaning I0, I2 are separated by 180 degrees and I1, I3 are separated by 180 degrees. It does not matter what the actual phase-cycles are. Implements field map estimation given in [1]_. References ---------- .. [1] Taylor, Meredith, et al. "MRI Field Mapping using bSSFP Elliptical Signal model." Proceedings of the ISMRM Annual Conference (2017). ''' if gs_recon_opts is None: gs_recon_opts = {} # TE = 2*TR gs_sol = gs_recon(I0, I1, I2, I3, **gs_recon_opts) # gsfm = np.angle(gs_sol)/(2*np.pi*TE) gsfm = np.angle(gs_sol) / (np.pi * TR) return gsfm
def get_ims(N, npcs, nc, radius, SNR=np.inf): '''Generate coil images and truth image.''' pcs = np.linspace(0, 2 * np.pi, npcs, endpoint=False) coil_sens = gbs(N, number_of_coils=nc) # Now get phase-cycles coil_ims = np.zeros((nc, N, N, len(pcs)), dtype='complex') for ii, pc in enumerate(pcs): ims = bssfp_2d_cylinder(dims=(N, N), phase_cyc=pc, radius=radius) # Apply coil sensitivities to coil images coil_ims[:, ..., ii] = ims * coil_sens # Add noise if SNR != np.inf: im_r = np.mean(np.abs(coil_ims.real)[np.nonzero(coil_ims.real)]) im_i = np.mean(np.abs(coil_ims.imag)[np.nonzero(coil_ims.imag)]) noise_std_r = im_r / SNR noise_std_i = im_i / SNR nr = np.random.normal(0, noise_std_r, coil_ims.shape) ni = np.random.normal(0, noise_std_i, coil_ims.shape) coil_ims += nr + 1j * ni nr = np.random.normal(0, noise_std_r, coil_ims.shape) ni = np.random.normal(0, noise_std_i, coil_ims.shape) noise_ims = nr + 1j * ni else: noise_ims = np.zeros(coil_ims.shape) # view(coil_ims) # Get truth image pcs_true = np.linspace(0, 2 * np.pi, 16, endpoint=False) ngs = int(pcs_true.size / 4) im_true = np.zeros((N, N, ngs), dtype='complex') tmp = np.zeros((N, N, pcs_true.size), dtype='complex') for ii, pc in enumerate(pcs_true): tmp[..., ii] = bssfp_2d_cylinder(dims=(N, N), phase_cyc=pc, radius=radius) for ii in range(ngs): im_true[..., ii] = gs_recon(tmp[..., ii::ngs], pc_axis=-1) im_true = np.mean(im_true, axis=-1) return (coil_ims, noise_ims, im_true)
def cc_then_gs_avg_csm(coil_ims, noise_ims): '''Coil combine using averaged csm then GS recon. ''' # How many phase-cycles? npcs = coil_ims.shape[-1] # Average the coil sensitivities for each phase-cycle csm0 = np.zeros(coil_ims.shape, dtype='complex') for ii in range(npcs): csm0[..., ii] = walsh(coil_ims[..., ii], noise_ims[..., ii]) csm0 = np.mean(csm0, axis=-1) # Apply to each phase-cycle cc0 = np.zeros(coil_ims.shape[1:], dtype='complex') for ii in range(npcs): cc0[..., ii] = np.sum(csm0 * np.conj(coil_ims[..., ii]), axis=0) return gs_recon(cc0, pc_axis=-1)
def test_noisy_gs_recon(self): from mr_utils.recon.ssfp import gs_recon, gs_recon_for_loop # Add in gaussian noise on both real,imag channels m, std = 0, .08 n1 = np.random.normal(m, std, size=self.I1.shape) + 1j * np.random.normal( m, std, size=self.I1.shape) n2 = np.random.normal(m, std, size=self.I1.shape) + 1j * np.random.normal( m, std, size=self.I1.shape) n3 = np.random.normal(m, std, size=self.I1.shape) + 1j * np.random.normal( m, std, size=self.I1.shape) n4 = np.random.normal(m, std, size=self.I1.shape) + 1j * np.random.normal( m, std, size=self.I1.shape) I0 = gs_recon(self.I1 + n1, self.I2 + n2, self.I3 + n3, self.I4 + n4) I1 = gs_recon_for_loop(self.I1 + n1, self.I2 + n2, self.I3 + n3, self.I4 + n4) self.assertTrue(np.allclose(I0, I1))
def get_true_im_numerical_phantom(): '''Get reference bSSFP simulated phantom. As the geometric solution to the elliptical signal model still has some residual banding, do it a few times at a bunch of different phase cycles to remove virually all banding. This ensures that the contrast will be comparable to the banded phantoms. Returns ------- true_im : array_like Banding free reference image with true bSSFP contrast. ''' # Load in params for simulation params = get_numerical_phantom_params(SNR=None) dim = params['dim'] pc_vals = params['pc_vals'] # Find true im by using no noise gs_recon averaged over several # different phase-cycles to remove residual banding # true_im = bssfp_2d_cylinder(dims=(dim,dim),phase_cyc=0) true_im = np.zeros((dim, dim), dtype='complex') avgs = [0, np.pi / 6, np.pi / 3, np.pi / 4] # avgs = [ 0 ] for extra in avgs: pc0 = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=(pc_vals[0] + extra)) pc1 = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=(pc_vals[1] + extra)) pc2 = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=(pc_vals[2] + extra)) pc3 = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=(pc_vals[3] + extra)) true_im += gs_recon(pc0, pc1, pc2, pc3) true_im /= len(avgs) true_im += 1j * true_im # view(np.concatenate((true_im,pc0))) return true_im
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))
# Get a 2D image, since we can't seem to replicate using a single # voxel N = 64 df_noise_std = 0 radius = .8 TR = 6e-3 alpha = np.deg2rad(30) npcs = 4 pcs = np.linspace(0, 2*np.pi, npcs, endpoint=False) PD, T1, T2 = cylinder_2d(dims=(N, N), radius=radius) df = 1000 min_df, max_df = -df, df fx = np.linspace(min_df, max_df, N) fy = np.zeros(N) df, _ = np.meshgrid(fx, fy) if df_noise_std > 0: n = np.random.normal(0, df_noise_std, df.shape) df += n I = ssfp(T1, T2, TR, alpha, df, pcs, PD, phi_rf=0) recon = gs_recon(I, pc_axis=0) recon_no_second = gs_recon(I, pc_axis=0, second_pass=False) I0 = np.concatenate(( I, recon[None, ...], recon_no_second[None, ...]), axis=0) view(I0, montage_axis=0)
def comparison_knee(): '''Coil by coil, Walsh method, and Inati iterative method for knee data.''' # Load the knee data dir = '/home/nicholas/Documents/rawdata/SSFP_SPECTRA_dphiOffset_08022018/' files = [ 'meas_MID362_TRUFI_STW_TE3_FID29379', 'meas_MID363_TRUFI_STW_TE3_dphi_45_FID29380', 'meas_MID364_TRUFI_STW_TE3_dphi_90_FID29381', 'meas_MID365_TRUFI_STW_TE3_dphi_135_FID29382', 'meas_MID366_TRUFI_STW_TE3_dphi_180_FID29383', 'meas_MID367_TRUFI_STW_TE3_dphi_225_FID29384', 'meas_MID368_TRUFI_STW_TE3_dphi_270_FID29385', 'meas_MID369_TRUFI_STW_TE3_dphi_315_FID29386' ] pc_vals = [0, 45, 90, 135, 180, 225, 270, 315] dims = (512, 256) num_coils = 4 num_avgs = 16 # # Load in raw once, then save as npy with collapsed avg dimension # pcs = np.zeros((len(files),dims[0],dims[1],num_coils),dtype='complex') # for ii,file in enumerate(files): # pcs[ii,...] = np.mean(load_raw('%s/%s.dat' % (dir,file),use='s2i'),axis=-1) # np.save('%s/te3.npy' % dir,pcs) # pcs looks like (pc,x,y,coil) pcs = np.load('%s/te3.npy' % dir) pcs = np.fft.fftshift(np.fft.fft2(pcs, axes=(1, 2)), axes=(1, 2)) # print(pcs.shape) # view(pcs,fft=True,montage_axis=0,movie_axis=3) # Do recon then coil combine coils0 = np.zeros((pcs.shape[-1], pcs.shape[1], pcs.shape[2]), dtype='complex') coils1 = coils0.copy() for ii in range(pcs.shape[-1]): # We have two sets: 0,90,180,27 and 45,135,225,315 idx0 = [0, 2, 4, 6] idx1 = [1, 3, 5, 7] coils0[ii, ...] = gs_recon(*[x.squeeze() for x in pcs[idx0, :, :, ii]]) coils1[ii, ...] = gs_recon(*[x.squeeze() for x in pcs[idx1, :, :, ii]]) # Then do the coil combine csm_walsh, _ = calculate_csm_walsh(coils0) im_est_recon_then_walsh0 = np.sum(csm_walsh * np.conj(coils0), axis=0) # view(im_est_recon_then_walsh0) csm_walsh, _ = calculate_csm_walsh(coils1) im_est_recon_then_walsh1 = np.sum(csm_walsh * np.conj(coils1), axis=0) # view(im_est_recon_then_walsh1) rip0 = ripple(im_est_recon_then_walsh0) rip1 = ripple(im_est_recon_then_walsh1) print('recon then walsh: ', np.mean([rip0, rip1])) # Now try inati csm_inati, im_est_recon_then_inati0 = calculate_csm_inati_iter(coils0, smoothing=5) csm_inati, im_est_recon_then_inati1 = calculate_csm_inati_iter(coils1, smoothing=5) rip0 = ripple(im_est_recon_then_inati0) rip1 = ripple(im_est_recon_then_inati1) print('recon then inati: ', np.mean([rip0, rip1])) # Now try sos im_est_recon_then_sos0 = sos(coils0, axes=0) im_est_recon_then_sos1 = sos(coils1, axes=0) rip0 = ripple(im_est_recon_then_sos0) rip1 = ripple(im_est_recon_then_sos1) print('recon then sos: ', np.mean([rip0, rip1])) # view(im_est_recon_then_sos) ## Now the other way, combine then recon pcs0 = np.zeros((2, pcs.shape[0], pcs.shape[1], pcs.shape[2]), dtype='complex') pcs1 = pcs0.copy() for ii in range(pcs.shape[0]): # Walsh it up csm_walsh, _ = calculate_csm_walsh(pcs[ii, ...].transpose((2, 0, 1))) pcs0[0, ii, ...] = np.sum(csm_walsh * np.conj(pcs[ii, ...].transpose( (2, 0, 1))), axis=0) # view(pcs0[ii,...]) # Inati it up csm_inati, pcs0[1, ii, ...] = calculate_csm_inati_iter(pcs[ii, ...].transpose( (2, 0, 1)), smoothing=5) ## Now perform gs_recon on each coil combined set # Walsh im_est_walsh_then_recon0 = gs_recon( *[x.squeeze() for x in pcs0[0, idx0, ...]]) im_est_walsh_then_recon1 = gs_recon( *[x.squeeze() for x in pcs0[0, idx1, ...]]) # Inati im_est_inati_then_recon0 = gs_recon( *[x.squeeze() for x in pcs0[1, idx0, ...]]) im_est_inati_then_recon1 = gs_recon( *[x.squeeze() for x in pcs0[1, idx1, ...]]) # view(im_est_walsh_then_recon0) # view(im_est_walsh_then_recon1) view(im_est_inati_then_recon0) view(im_est_inati_then_recon1) rip0 = ripple(im_est_walsh_then_recon0) rip1 = ripple(im_est_walsh_then_recon1) print('walsh then recon: ', np.mean([rip0, rip1])) rip0 = ripple(im_est_inati_then_recon0) rip1 = ripple(im_est_inati_then_recon1) print('inati then recon: ', np.mean([rip0, rip1]))
def test_gs_recon_knee(self): from mr_utils.recon.ssfp import gs_recon I = gs_recon(self.I1, self.I2, self.I3, self.I4) self.assertTrue(np.allclose(I, self.I))
npcs, ncoils, sx, sy = data.shape[:] pcs = np.linspace(0, 2*np.pi, 4, endpoint=False) pcs = np.tile(pcs, (sy, sx, 1)).T # Estimate the sensitivity maps from coil images # Try espirit csm_est = np.load(path + 'csm_P.npy')[ :, :, sl, :].transpose((2, 0, 1)) # csm_est = np.load(path + 'csm_P_S.npy')[ # :, :, sl, :, 0].transpose((2, 0, 1)) # print(csm_est.shape) # view(csm_est*mask) M = np.zeros((ncoils, sx, sy), dtype='complex') for cc in range(ncoils): M[cc, ...] = gs_recon(data[:, cc, ...], pc_axis=0) # M[cc, ...] *= np.exp(1j*pcs[0, ...]/2) # view(M[cc, ...]) thresh = threshold_li(np.abs(M)) mask = np.abs(M) > thresh mask0 = mask[0, ...] # csm_est, _ = calculate_csm_walsh(M) # csm_est, _ = calculate_csm_inati_iter(M) # I think what should actually happen is that coil sensitivity # map phase information should be applied to each phase-cycle # image before GS recon, then # Let's recall the scan parameters TR = 6e-3
dataset = 1 t0 = time() data0 = np.load('data/20190401_GASP_PHANTOM/set%d_tr6_te3.npy' % dataset) data1 = np.load('data/20190401_GASP_PHANTOM/set%d_tr12_te6.npy' % dataset) data2 = np.load('data/20190401_GASP_PHANTOM/set%d_tr24_te12.npy' % dataset) print(time() - t0) print(data0.shape) # [Height, Width, Coil, Avg, PCs] data = np.concatenate( (data0[..., None], data1[..., None], data2[..., None]), axis=-1) data = np.mean(data, axis=3) # [Height, Width, Coil, PCs, TRs] # Create a mask of the phanton band_free = gs_recon(data[:, :, 0, ::4, 0], pc_axis=-1) thresh = threshold_li(np.abs(band_free)) mask = np.abs(band_free) > thresh # Apply mask to data mask0 = np.tile(mask, (data.shape[2:] + ( 1, 1, ))).transpose((3, 4, 0, 1, 2)) data = data * mask0 print(data.shape[:-2]) data = np.reshape(data, data.shape[:-2] + (-1, )) # [Height, Width, Coil, PCs x TRs] data = np.moveaxis(data, 2, 0) # [Coil, Height, Width, PCs x TRs] data = data.transpose((0, 3, 2, 1)) # [Coil, PCs x TRs, Width, Height]
def taylor_method(Is, dphis, alpha, TR, mask=None, chunksize=10, unwrap_fun=None, disp=False): '''Parameter mapping for multiple phase-cycled bSSFP. Parameters ========== Is : array_like Array of phase-cycled images, (dphi, x, y). dphis : array_like Phase-cycles (in radians). alpha : array_like Flip angle map (in rad). TR : float Repetition time (in sec). mask : array_like, optional Locations to compute map estimates. chunksize : int, optional Chunk size to use for parallelized loop. unwrap_fun : callable Function to do 2d phase unwrapping. If None, will use skimage.restoration.unwrap_phase(). disp : bool, optional Show debugging plots. Returns ======= t1map : array_like T1 map estimation (in sec) t2map : array_like T2 map estimation (in sec) offresmap : array_like Off-resonance map estimation (in Hz) m0map : array_like Proton density map estimation Raises ====== AssertionError If number of phase-cycles is not divisible by 4. Notes ===== mask=None computes maps for all points. Note that `Is` must be given as a list. ''' # If mask is None, that means we'll do all the points if mask is None: mask = np.ones(Is[0].shape).astype(bool) # If unwrap is None, then use default: if unwrap_fun is None: from skimage.restoration import unwrap_phase unwrap_fun = lambda x: unwrap_phase(x) # Calculate the banding-removed image using the algorithm in the elliptical # model paper. # My addition: average the GS recon over all sets of 4 we have. This will # have to do while I work on a generalization of the GS recon method to # handle >=4 phase-cycles at a time. assert np.mod(len(Is), 4) == 0, 'We need sets of 4 for GS recon!' num_sets = int(len(Is)/4) Ms = np.zeros((num_sets,) + Is[0].shape, dtype='complex') for ii in range(num_sets): Ms[ii, ...] = gs_recon(Is[ii::num_sets, ...]) M = np.mean(Ms, axis=0) # Display elliptical model image. if disp: plt.imshow(np.abs(M)) plt.title('Eliptical Model - Banding Removed') plt.show() # These plots look fishy -- they changed when I moved from Merry's # SSFPFit function to my ssfp simulation function. Need to look at # MATLAB output to see who's right and/or what's going on. # Choose a row in the mask and show 0, 180 phase cycle lines idx = np.argwhere(mask) idx = idx[np.random.choice(np.arange(idx.shape[0])), :] row, _col = idx[0], idx[1] col = np.argwhere(mask)[row, :] cols = (np.min(col), np.max(col)) Is0 = Is[::num_sets, ...] plt.suptitle('0 PC') plt.subplot(2, 2, 1) plt.plot(Is0[0, row, cols[0]:cols[1]].real) plt.subplot(2, 2, 2) plt.plot(Is0[0, row, cols[0]:cols[1]].imag) plt.subplot(2, 2, 3) plt.plot(np.abs(Is0[0, row, cols[0]:cols[1]])) plt.subplot(2, 2, 4) plt.plot(np.angle(Is0[0, row, cols[0]:cols[1]])) plt.show() plt.suptitle('180 PC') plt.subplot(2, 2, 1) plt.plot(Is[4, row, cols[0]:cols[1]].real) plt.subplot(2, 2, 2) plt.plot(Is[4, row, cols[0]:cols[1]].imag) plt.subplot(2, 2, 3) plt.plot(np.abs(Is[4, row, cols[0]:cols[1]])) plt.subplot(2, 2, 4) plt.plot(np.angle(Is[4, row, cols[0]:cols[1]])) plt.show() # compare to Lauzon paper figure 1 ## Elliptical fit done here offres_est = np.angle(M) m_offres_est = np.ma.array(offres_est, mask=mask & 0) offres_est = unwrap_fun(m_offres_est)*mask offres_est /= -1*np.pi*TR # -1 is for sign of phi in ssfp sim view(offres_est) sh = Is.shape[1:] t1map = np.zeros(sh) t2map = np.zeros(sh) offresmap = np.zeros(sh) m0map = np.zeros(sh) # Since we have to do it for each pixel and all calculations are # independent, we can parallelize this sucker! Use imap_unordered to to # update tqdm progress bar more regularly and use less memory over time. tot = np.sum(mask.flatten()) optim = partial(optim_wrapper, Is=Is, TR=TR, dphis=dphis, offres_est=offres_est, alpha=alpha) with Pool() as pool: res = list(tqdm(pool.imap_unordered( optim, np.argwhere(mask), chunksize=int(chunksize)), total=tot, leave=False, desc='Param mapping')) # The answers are then unpacked (not garanteed to be in the right order) for ii, jj, xopt in res: t1map[ii, jj] = xopt[0] t2map[ii, jj] = xopt[1] offresmap[ii, jj] = xopt[2] m0map[ii, jj] = xopt[3] return(t1map, t2map, offresmap, m0map)
def comparison_numerical_phantom(SNR=None): '''Compare coil by coil, Walsh method, and Inati iterative method.''' true_im = get_true_im_numerical_phantom() csms = get_coil_sensitivity_maps() params = get_numerical_phantom_params(SNR=SNR) pc_vals = params['pc_vals'] dim = params['dim'] noise_std = params['noise_std'] coil_nums = params['coil_nums'] # We want to solve gs_recon for each coil we have in the pc set err = np.zeros((5, len(csms))) rip = err.copy() for ii, csm in enumerate(csms): # I have coil sensitivities, now I need images to apply them to. # coil_ims: (pc,coil,x,y) coil_ims = np.zeros((len(pc_vals), csm.shape[0], dim, dim), dtype='complex') for jj, pc in enumerate(pc_vals): im = bssfp_2d_cylinder(dims=(dim, dim), phase_cyc=pc) im += 1j * im coil_ims[jj, ...] = im * csm coil_ims[jj, ...] += np.random.normal(0, noise_std, coil_ims[ jj, ...].shape) / 2 + 1j * np.random.normal( 0, noise_std, coil_ims[jj, ...].shape) / 2 # Solve the gs_recon coil by coil coil_ims_gs = np.zeros((csm.shape[0], dim, dim), dtype='complex') for kk in range(csm.shape[0]): coil_ims_gs[kk, ...] = gs_recon(*[ x.squeeze() for x in np.split(coil_ims[:, kk, ...], len(pc_vals)) ]) coil_ims_gs[np.isnan(coil_ims_gs)] = 0 # Easy way out: combine all the coils using sos im_est_sos = sos(coil_ims_gs) # view(im_est_sos) # Take coil by coil solution and do Walsh on it to collapse coil dim # walsh csm_walsh, _ = calculate_csm_walsh(coil_ims_gs) im_est_recon_then_walsh = np.sum(csm_walsh * np.conj(coil_ims_gs), axis=0) im_est_recon_then_walsh[np.isnan(im_est_recon_then_walsh)] = 0 # view(im_est_recon_then_walsh) # inati csm_inati, im_est_recon_then_inati = calculate_csm_inati_iter( coil_ims_gs) # Collapse the coil dimension of each phase-cycle using Walsh,Inati pc_est_walsh = np.zeros((len(pc_vals), dim, dim), dtype='complex') pc_est_inati = np.zeros((len(pc_vals), dim, dim), dtype='complex') for jj in range(len(pc_vals)): ## Walsh csm_walsh, _ = calculate_csm_walsh(coil_ims[jj, ...]) pc_est_walsh[jj, ...] = np.sum(csm_walsh * np.conj(coil_ims[jj, ...]), axis=0) # view(csm_walsh) # view(pc_est_walsh) ## Inati csm_inati, pc_est_inati[jj, ...] = calculate_csm_inati_iter( coil_ims[jj, ...], smoothing=1) # pc_est_inati[jj,...] = np.sum(csm_inati*np.conj(coil_ims[jj,...]),axis=0) # view(csm_inati) # Now solve the gs_recon using collapsed coils im_est_walsh = gs_recon( *[x.squeeze() for x in np.split(pc_est_walsh, len(pc_vals))]) im_est_inati = gs_recon( *[x.squeeze() for x in np.split(pc_est_inati, len(pc_vals))]) # view(im_est_walsh) # view(im_est_recon_then_walsh) # Compute error metrics err[0, ii] = compare_nrmse(im_est_sos, true_im) err[1, ii] = compare_nrmse(im_est_recon_then_walsh, true_im) err[2, ii] = compare_nrmse(im_est_recon_then_inati, true_im) err[3, ii] = compare_nrmse(im_est_walsh, true_im) err[4, ii] = compare_nrmse(im_est_inati, true_im) im_est_sos[np.isnan(im_est_sos)] = 0 im_est_recon_then_walsh[np.isnan(im_est_recon_then_walsh)] = 0 im_est_recon_then_inati[np.isnan(im_est_recon_then_inati)] = 0 im_est_walsh[np.isnan(im_est_walsh)] = 0 im_est_inati[np.isnan(im_est_inati)] = 0 rip[0, ii] = ripple_normal(im_est_sos) rip[1, ii] = ripple_normal(im_est_recon_then_walsh) rip[2, ii] = ripple_normal(im_est_recon_then_inati) rip[3, ii] = ripple_normal(im_est_walsh) rip[4, ii] = ripple_normal(im_est_inati) # view(im_est_inati) # # SOS of the gs solution on each individual coil gives us low periodic # # ripple accross the phantom, similar to Walsh method: # plt.plot(np.abs(true_im[int(dim/2),:]),'--',label='True Im') # plt.plot(np.abs(im_est_sos[int(dim/2),:]),'-.',label='SOS') # plt.plot(np.abs(im_est_recon_then_walsh[int(dim/2),:]),label='Recon then Walsh') # plt.plot(np.abs(im_est_walsh[int(dim/2),:]),label='Walsh then Recon') # # plt.plot(np.abs(im_est_inati[int(dim/2),:]),label='Inati') # plt.legend() # plt.show() # # Let's show some stuff # plt.plot(coil_nums,err[0,:],'*-',label='SOS') # plt.plot(coil_nums,err[1,:],label='Recon then Walsh') # plt.plot(coil_nums,err[2,:],label='Walsh then Recon') # # plt.plot(coil_nums,err[3,:],label='Inati') # plt.legend() # plt.show() print('SOS RMSE:', np.mean(err[0, :])) print('recon then walsh RMSE:', np.mean(err[1, :])) print('recon then inati RMSE:', np.mean(err[2, :])) print('walsh then recon RMSE:', np.mean(err[3, :])) print('inati then recon RMSE:', np.mean(err[4, :])) print('SOS ripple:', np.mean(err[0, :])) print('recon then walsh ripple:', np.mean(rip[1, :])) print('recon then inati ripple:', np.mean(rip[2, :])) print('walsh then recon ripple:', np.mean(rip[3, :])) print('inati then recon ripple:', np.mean(rip[4, :])) view(im_est_recon_then_walsh[int(dim / 2), :]) view(im_est_recon_then_inati[int(dim / 2), :]) view(im_est_walsh[int(dim / 2), :]) view(im_est_inati[int(dim / 2), :]) # view(im_est_inati) # view(np.stack((im_est_recon_then_walsh,im_est_recon_then_inati,im_est_walsh,im_est_inati))) return (err)
# Let's do one slice for now sl = 8 im = im[:, :, sl, ...].squeeze() csm = csm[:, :, sl, ..., 0].squeeze() # view(csm) print(im.shape, im.shape) # Adjust for coil sensitivities im0 = np.zeros((im.shape[:3]), dtype='complex') print(im0.shape) for ii in range(4): im0[..., ii] = np.sum(im[..., :, ii]*csm.conj(), axis=-1) # Now do GS recon TR = 6e-3 M = gs_recon(im0, pc_axis=-1) m = int(M.shape[0]/4) M = M[m:-m, :] thresh = threshold_li(np.abs(M)) mask = np.abs(M) > thresh df_est0 = mask*np.angle(M) # df_est = unwrap_phase( # np.ma.array(df_est, mask=np.abs(mask - 1))) # df_est = np.unwrap(df_est, axis=0)*mask # df_est = np.unwrap(df_est, axis=1)*mask # win = np.hamming(M.shape[1]) # win = np.outer(win, win) # df_est = np.fft.fft2(df_est)*win # df_est = np.fft.ifft2(df_est) df_est0 /= np.pi*TR
axes=(1, 2)), axes=(1, 2)), axes=(1, 2)) kspace_coil_ims3 = np.fft.fftshift(np.fft.fft2(np.fft.fftshift(coil_ims3, axes=(1, 2)), 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))
if __name__ == '__main__': # Load in phantom data, shape: (512, 256, 4, 16) path = 'mr_utils/test_data/examples/coils/' imspace, kspace = load_test_data(path, ['imspace', 'kspace']) print(imspace.shape) nx, ny, nc, npcs = imspace.shape[:] trim = int(nx / 4) pcs = np.linspace(0, 2 * np.pi, npcs, endpoint=False) # Get mask ngs = int(npcs / 4) tmp = np.zeros((nx, ny, nc, ngs), dtype='complex') for coil in range(nc): for ii in range(ngs): tmp[..., coil, ii] = gs_recon(imspace[..., coil, ii::4], pc_axis=-1) im_true = np.mean(tmp, axis=-1) im_true = sos(im_true, axes=-1) thresh = threshold_li(im_true) mask = im_true > thresh # view(im_true) # view(mask) # Define coil combination functions ccs, cc_list = get_coil_combine_funs(np.min([nx, ny]), v='') # Do coil combine then ESM res = np.zeros((len(ccs), nx, ny), dtype='complex') err_cc_then_gs = np.zeros(len(ccs)) for fun, cc in enumerate(ccs):
PD, phi_rf=-phi_rf[cc, ...]) # DON'T DO THIS, STUPID! # # phase-cycle correction # Imag = np.abs(I[cc, ...]) # Iphase = np.angle(I[cc, ...]) - np.tile(pcs/2, (N, N, 1)).T # I[cc, ...] = Imag*np.exp(1j*Iphase) I *= csm_mag # view(I.transpose((2, 3, 0, 1))) # Estimate the sensitivity maps from coil images recons = np.zeros((ncoils, N, N), dtype='complex') for cc in range(ncoils): recons[cc, ...] = gs_recon(I[cc, ...], pc_axis=0) thresh = threshold_li(np.abs(recons)) mask = np.abs(recons) > thresh csm_est, _ = calculate_csm_walsh(recons) # This doesn't work as well, still alright, but we knew this about # inati # csm_est, _ = calculate_csm_inati_iter(recons) # # Do the other way: estimate coil sensitivities from phase-cycle # csms = np.zeros((npcs, ncoils, N, N)) # for ii in range(npcs): # csms[ii, ...], _ = calculate_csm_walsh(I[:, ii, ...]) # csm_est = np.mean(csms, axis=0) # # Look at residual phase
phi_rf=phi_rf[cc]) # Do PLANET rotation to get vertical ellipse xr, yr, _C0, phis[cc] = do_planet_rotation(I[cc, :]) I0[cc, :] = xr + 1j*yr # plt.plot(I[cc, :].real, I[cc, :].imag) # plt.plot(I0[cc, :].real, I0[cc, :].imag) # plt.axis('square') # plt.show() # Get coil sensitivity map estimates from ismrmrdtools.coils import calculate_csm_walsh recons = np.zeros(ncoils, dtype='complex') for cc in range(ncoils): view(gs_recon(I[cc, :])) # recons[cc] = gs_recon(I[cc, :][:, None, None], pc_axis=-1) # csm_est = calculate_csm_walsh(recons[:, None, None]) # view(csm_est) # # Can we find the null? # fig, ax1 = plt.subplots() # ax2 = ax1.twinx() # dfs = np.linspace(-1/TR, 1/TR, lpcs) # print(np.rad2deg(phi_rf)) # print(np.rad2deg(phis)) # for cc in range(ncoils): # im0 = np.atleast_2d(I[cc, ::2]) # im1 = np.atleast_2d(I[cc, 1::2]) # recon0 = gs_recon(im0, pc_axis=-1) # recon1 = gs_recon(im1, pc_axis=-1)
right_zeros = 30 slide = 0 box[left_zeros:-right_zeros] = 1 box = np.roll(box, int(slide)) plt.plot(box) plt.show() # Do the thing! I0 = gasp(I, box) print(I.shape, I0.shape) # Find the 0, 90, 180, 270 phase-cycles to do GS recon on idx = [ ii for ii, val in enumerate(np.rad2deg(pcs).astype(int)) if val in [0, 90, 180, 270] ] # Show me da apples! plt.imshow(np.abs(gs_recon(I[idx, ...], pc_axis=0))) plt.show() plt.imshow(np.abs(I0)) plt.show() # Take a slice through the water phantom to compare with desired # spectral profile mid = int(I0.shape[0] / 4) plt.plot(np.abs(I0[mid, :])) p = int((I0.shape[1] - box.size) / 2) plt.plot(np.concatenate((np.zeros(p), box, np.zeros(p))), '--') plt.show()
# view(recon, fft_axes=(1, 2), is_imspace=True, # coil_combine_axis=0) # else: # view(recon) # view(gs_recon(recon, pc_axis=0)) # Get truth image pcs_true = np.linspace(0, 2*np.pi, 16, endpoint=False) ngs = int(pcs_true.size/4) im_true = np.zeros((N, N, ngs), dtype='complex') tmp = np.zeros((N, N, pcs_true.size), dtype='complex') for ii, pc in enumerate(pcs_true): tmp[..., ii] = bssfp_2d_cylinder( dims=(N, N), phase_cyc=pc, radius=radius) for ii in range(ngs): im_true[..., ii] = gs_recon(tmp[..., ii::ngs], pc_axis=-1) im_true = np.mean(im_true, axis=-1) # Trim down to correct size if X < Y: trim = int((Y - X)/2) im_true = im_true[trim:-trim, ...] elif X > Y: trim = int((X - Y)/2) im_true = im_true[:, trim:-trim, ...] # view(im_true) # Get a mask so we only look at phantom for comparisons thresh = threshold_li(np.abs(im_true)) mask = np.abs(im_true) > thresh # view(mask)