def compute_Q_v2(G, copy_X=True): """Alternative version of compute_Q. experimental: not recommended over compute_Q() """ from mrrt.nufft._nufft import nufft_adj ones = np.ones(G.kspace.shape[0], G.Gnufft._cplx_dtype) sf = np.sqrt(prod(G.Gnufft.Kd)) return sf * fftn(nufft_adj(G.Gnufft, ones, copy_X=True, return_psf=True))
def test_fft_output_dtype(xp): x = xp.random.randn(256) y = fftn(x, axes=None, norm="ortho", xp=xp) assert_equal(y.dtype, xp.complex128) r = ifftn(y, axes=None, norm="ortho", xp=xp) assert_equal(r.dtype, xp.complex128) x = xp.random.randn(256).astype(np.complex128) y = fftn(x, axes=None, norm="ortho", xp=xp) assert_equal(y.dtype, xp.complex128) r = ifftn(y, axes=None, norm="ortho", xp=xp) assert_equal(r.dtype, xp.complex128) for dtype in [xp.float16, xp.float32, xp.complex64]: x = xp.random.randn(256).astype(dtype) y = fftn(x, axes=None, norm="ortho", xp=xp) assert_equal(y.dtype, xp.complex64) r = ifftn(y, axes=None, norm="ortho", xp=xp) assert_equal(r.dtype, xp.complex64)
def norm(self, x): # if not hasattr(self, 'Q') or self.Q is None: # warnings.warn("Toeplitz Q did not exist, creating it...") # self.prep_toeplitz() x = complexify(x) if self.masked: x = embed(x, self.mask, order=self.order) try: if x.size % self.nargin != 0: raise ValueError("wrong size input") Nrepetitions = x.size // self.nargin except IndexError: Nrepetitions = 1 if hasattr(self, "Q"): slices = [slice(None)] * x.ndim for d in range(len(self.Nd)): slices[d] = slice(self.Nd[d]) if Nrepetitions == 1: y = fftn(x, s=self.Q.shape) y *= self.Q y = ifftn(y)[slices] else: if self.order == "C": x_shape = (Nrepetitions,) + tuple(self.Nd) else: x_shape = tuple(self.Nd) + (Nrepetitions,) x = x.reshape(x_shape, order=self.order) fft_axes = tuple(np.arange(len(self.Nd))) y = fftn(x, s=self.Q.shape, axes=fft_axes) y *= self.Q[..., np.newaxis] # add an axis for repetitions y = ifftn(y, axes=fft_axes)[slices] else: y = self.H * (self * x) return y
def test_partial_FFT_with_im_mask(xp, nd_in, order, shift): """ masked FFT with missing samples and masked image domain """ c = get_data(xp) rstate = xp.random.RandomState(1234) sample_mask = rstate.rand(*(128, 127)) > 0.5 x, y = xp.meshgrid( xp.arange(-c.shape[0] // 2, c.shape[0] // 2), xp.arange(-c.shape[1] // 2, c.shape[1] // 2), indexing="ij", sparse=True, ) # make a circular mask im_mask = xp.sqrt(x * x + y * y) < c.shape[0] // 2 nd_out = False FTop = FFT_Operator( c.shape, order=order, im_mask=im_mask, use_fft_shifts=shift, nd_input=nd_in, nd_output=nd_out, sample_mask=sample_mask, gpu_force_reinit=False, mask_kspace_on_gpu=(not shift), **get_loc(xp), ) # create new linear operator for forward followed by inverse transform FtF = FTop.H * FTop assert isinstance(FtF, LinearOperatorMulti) # test forward only forw = embed(FTop * masker(c, im_mask, order=order), sample_mask, order=order) if shift: expected_forw = sample_mask * fftnc(c * im_mask) else: expected_forw = sample_mask * fftn(c * im_mask) xp.testing.assert_allclose(forw, expected_forw, rtol=1e-7, atol=1e-4) # test roundtrip roundtrip = FTop.H * (FTop * masker(c, im_mask, order=order)) if shift: expected_roundtrip = masker(ifftnc(sample_mask * fftnc(c * im_mask)), im_mask, order=order) else: expected_roundtrip = masker(ifftn(sample_mask * fftn(c * im_mask)), im_mask, order=order) xp.testing.assert_allclose(roundtrip, expected_roundtrip, rtol=1e-7, atol=1e-4) # test roundtrip with 2 reps c2 = xp.stack([c] * 2, axis=-1) roundtrip = FTop.H * (FTop * masker(c2, im_mask, order=order)) if shift: expected_roundtrip = masker( ifftnc( sample_mask[..., xp.newaxis] * fftnc(c2 * im_mask[..., xp.newaxis], axes=(0, 1)), axes=(0, 1), ), im_mask, order=order, ) else: expected_roundtrip = masker( ifftn( sample_mask[..., xp.newaxis] * fftn(c2 * im_mask[..., xp.newaxis], axes=(0, 1)), axes=(0, 1), ), im_mask, order=order, ) xp.testing.assert_allclose(roundtrip, expected_roundtrip, rtol=1e-7, atol=1e-4)
def compute_Q(G, wi=None, Nd_os=2, Kd_os=1.35, J=5, **extra_nufft_kwargs): """Compute Q such that IFFT(Q*FFT(x)) = (G.H * G * x). Notes ----- requires that G.Kd ~= 2*G.Nd for good accuracy. can get away with Kd_os < substantially less than 2 References ---------- ..[1] Wajer FTAW, Pruessmann KP. Major Speedup of Reconstruction for Sensitivity Encoding with Arbitrary Trajectories. Proc. Intl. Soc. Mag. Reson. Med. 9 (2001), p.767. ..[2] Eggers H, Boernert P, Boesiger P. Comparison of Gridding- and Convolution-Based Iterative Reconstruction Algorithms For Sensitivity-Encoded Non-Cartesian Acquisitions. Proc. Intl. Soc. Mag. Reson. Med. 10 (2002) ..[3] Liu C, Moseley ME, Bammer R. Fast SENSE Reconstruction Using Linear System Transfer Function. Proc. Intl. Soc. Mag. Reson. Med. 13 (2005), p.689. """ from mrrt.mri.operators import MRI_Operator, NUFFT_Operator if isinstance(G, NUFFT_Operator): Gnufft_op = G elif isinstance(G, MRI_Operator): Gnufft_op = G.Gnufft else: raise ValueError("G must be an NUFFT_Operator or MRI_Operator") # need reasonably accurate gridding onto a 2x oversampled grid Nd = (Nd_os * Gnufft_op.Nd).astype(np.intp) Kd = next_fast_len((Kd_os * Nd).astype(np.intp)) if any(k < 2 * n for (k, n) in zip(G.Kd, G.Nd)): warnings.warn( "Q operator unlikely to be accurate. Recommend using G " "with a grid oversampling factor of 2" ) if Nd_os != 2: warnings.warn("recommend keeping Nd_os=2") G2 = NUFFT_Operator( omega=Gnufft_op.om, Nd=Nd, Kd=Kd, Jd=(J,) * len(Nd), Ld=Gnufft_op.Ld, n_shift=Nd / 2, mode=Gnufft_op.mode, phasing="real", # ONLY WORKS IF THIS IS REAL! **extra_nufft_kwargs, ) if wi is None: wi = np.ones(Gnufft_op.om.shape[0], dtype=Gnufft_op._cplx_dtype) psft = G2.H * wi # TODO: allow DiagonalOperator too for weights psft = np.fft.fftshift(psft.reshape(G2.Nd, order=G2.order)) return fftn(psft)