Beispiel #1
0
    def __init__(self,
                 ksp,
                 calib_width=24,
                 thresh=0.01,
                 kernel_width=6,
                 crop=0.8,
                 max_iter=100,
                 device=sp.cpu_device,
                 output_eigenvalue=False,
                 show_pbar=True):
        self.device = sp.Device(device)
        self.output_eigenvalue = output_eigenvalue
        self.crop = crop

        img_ndim = ksp.ndim - 1
        num_coils = len(ksp)
        with sp.get_device(ksp):
            # Get calibration region
            calib_shape = [num_coils] + [calib_width] * img_ndim
            calib = sp.resize(ksp, calib_shape)
            calib = sp.to_device(calib, device)

        xp = self.device.xp
        with self.device:
            # Get calibration matrix
            kernel_shape = [num_coils] + [kernel_width] * img_ndim
            kernel_strides = [1] * (img_ndim + 1)
            mat = sp.array_to_blocks(calib, kernel_shape, kernel_strides)
            mat = mat.reshape([-1, sp.prod(kernel_shape)])

            # Perform SVD on calibration matrix
            _, S, VH = xp.linalg.svd(mat, full_matrices=False)
            VH = VH[S > thresh * S.max(), :]

            # Get kernels
            num_kernels = len(VH)
            kernels = VH.reshape([num_kernels] + kernel_shape)
            img_shape = ksp.shape[1:]

            # Get covariance matrix in image domain
            AHA = xp.zeros(img_shape[::-1] + (num_coils, num_coils),
                           dtype=ksp.dtype)
            for kernel in kernels:
                img_kernel = sp.ifft(sp.resize(kernel, ksp.shape),
                                     axes=range(-img_ndim, 0))
                aH = xp.expand_dims(img_kernel.T, axis=-1)
                a = xp.conj(aH.swapaxes(-1, -2))
                AHA += aH @ a

            AHA *= (sp.prod(img_shape) / kernel_width**img_ndim)
            self.mps = xp.ones(ksp.shape[::-1] + (1, ), dtype=ksp.dtype)

            alg = sp.alg.PowerMethod(
                lambda x: AHA @ x,
                self.mps,
                norm_func=lambda x: xp.sum(
                    xp.abs(x)**2, axis=-2, keepdims=True)**0.5,
                max_iter=max_iter)

        super().__init__(alg, show_pbar=show_pbar)
def image_undersampled_recon(image,
                             accel_factor=1.5,
                             eps=1e-6,
                             recon_type='l1-wavelet',
                             trajectory=poisson_trajectory,
                             *args):
    """
    This function undersamples the input image in k-space and performs a reconstruction with the undersampled data.

    Inputs:
    :param image: the image to be undersampled and reconstruced (numpy array complex128)
    :param accel_factor: the acceleration factor (float)
    :param eps: precision parameter for constrained reconstruction (float)
    :param recon_type: specify the reconstruction type. Accepts: 'l1-wavelet' for L1-wavelet constrained reconstruction
        (default), 'zero-fill' for a zero-filled image reconstruction
    :param trajectory: the trajectory function to use for generating the kspace mask (function)
    :param args: extraneous arguments to pass to the trajectory function call (extraneous arguments)
    :return: reconstructed_image: (numpy array complex128)
    """
    # Convert image to k-space
    kspace = sigpy.fft(image, center=True, norm='ortho')

    # Generate trajectory mask from kspace size and accel_factor, as well as extraneous arguments.
    trajectory_mask = trajectory(kspace.shape, accel_factor, *args)

    # Generate undersampled k-space data
    undersampled_kspace = kspace * trajectory_mask

    if recon_type.lower() == 'zero-fill'.lower():
        # Reconstruct zero-filled image
        zero_filled_img = sigpy.ifft(undersampled_kspace,
                                     center=True,
                                     norm='ortho')
        return zero_filled_img

    if recon_type.lower() == 'l1-wavelet'.lower():
        # Convert poisson mask into boolean array for indexing
        mask = np.bool8(trajectory_mask)

        # Build coordinates matrix using poisson mask
        x = np.linspace(-image.shape[0] / 2, image.shape[0] / 2 - 1,
                        image.shape[0])
        y = np.linspace(-image.shape[1] / 2, image.shape[1] / 2 - 1,
                        image.shape[1])
        X, Y = np.meshgrid(x, y)
        x_idx = Y[
            mask]  # Dimensions needs to be swapped because of row-major indexing
        y_idx = X[mask]
        coords = np.concatenate([x_idx[:, np.newaxis], y_idx[:, np.newaxis]],
                                axis=1)

        mask = np.bool8(trajectory_mask)
        L1ConstrainedWaveletApp = sigpy.mri.app.L1WaveletConstrainedRecon(
            y=kspace[mask],
            mps=np.ones((1, image.shape[0], image.shape[1])),
            coord=coords,
            eps=eps)
        L1out = L1ConstrainedWaveletApp.run()
        return L1out
Beispiel #3
0
def b2rf(b, cancel_alpha_phs=False):
    a = b2a(b)
    if cancel_alpha_phs:
        b_a_phase = sp.fft(b, center=False, norm=None) * \
            np.exp(-1j * np.angle(sp.fft(a[np.size(a)::-1],
                                         center=False, norm=None)))
        b = sp.ifft(b_a_phase, center=False, norm=None)
    rf = ab2rf(a, b)

    return rf
Beispiel #4
0
def fmp(h):
    l = np.size(h)
    lp = 128 * np.exp(np.ceil(np.log(l) / np.log(2)) * np.log(2))
    padwidths = np.array([np.ceil((lp - l) / 2), np.floor((lp - l) / 2)])
    hp = np.pad(h, padwidths.astype(int), 'constant')
    hpf = sp.fft(hp, norm=None)
    hpfs = hpf - np.min(np.real(hpf)) * 1.000001
    hpfmp = mag2mp(np.sqrt(np.abs(hpfs)))
    hpmp = sp.ifft(np.fft.ifftshift(np.conj(hpfmp)), center=False, norm=None)
    hmp = hpmp[:int((l + 1) / 2)]

    return hmp
Beispiel #5
0
def mag2mp(x):
    n = np.size(x)
    xl = np.log(np.abs(x))  # Log of mag spectrum
    xlf = sp.fft(xl, center=False, norm=None)
    xlfp = xlf
    xlfp[0] = xlf[0]  # Keep DC the same
    xlfp[1:(n // 2):1] = 2 * xlf[1:(n // 2):1]  # Double positive frequencies
    xlfp[n // 2] = xlf[n // 2]  # keep half Nyquist the same
    xlfp[n // 2 + 1:n:1] = 0  # zero negative frequencies
    xlaf = sp.ifft(xlfp, center=False, norm=None)
    a = np.exp(xlaf)  # complex exponentiation

    return a
Beispiel #6
0
        def test_sense_model_with_comm(self):
            img_shape = [16, 16]
            mps_shape = [8, 16, 16]
            comm = sp.Communicator()

            img = sp.randn(img_shape, dtype=np.complex)
            mps = sp.randn(mps_shape, dtype=np.complex)
            comm.allreduce(img)
            comm.allreduce(mps)
            ksp = sp.fft(img * mps, axes=[-1, -2])

            A = linop.Sense(mps[comm.rank::comm.size], comm=comm)

            npt.assert_allclose(A.H(ksp[comm.rank::comm.size]), np.sum(
                sp.ifft(ksp, axes=[-1, -2]) * mps.conjugate(), 0))
Beispiel #7
0
    def _output(self):
        xp = self.device.xp
        # Coil by coil to save memory
        with self.device:
            mps_rss = 0
            mps = np.empty([self.num_coils] + self.img_shape, dtype=self.dtype)
            for c in range(self.num_coils):
                mps_c = sp.ifft(sp.resize(self.mps_ker[c], self.img_shape))
                mps_rss += xp.abs(mps_c)**2
                sp.copyto(mps[c], mps_c)

            mps_rss = sp.to_device(mps_rss**0.5, sp.cpu_device)
            mps /= mps_rss

        return mps
Beispiel #8
0
    def _output(self):
        xp = self.device.xp
        # Normalize by root-sum-of-squares.
        with self.device:
            rss = 0
            mps = np.empty([self.num_coils] + self.img_shape, dtype=self.dtype)
            for c in range(self.num_coils):
                mps_c = sp.ifft(sp.resize(self.mps_ker[c], self.img_shape))
                rss += xp.abs(mps_c)**2
                sp.copyto(mps[c], mps_c)

            rss = sp.to_device(rss)
            if self.comm is not None:
                self.comm.allreduce(rss)

            rss = rss**0.5
            mps /= rss
            return mps
Beispiel #9
0
def dzls(n=64, tb=4, d1=0.01, d2=0.01):
    di = dinf(d1, d2)
    w = di / tb
    f = np.asarray([0, (1 - w) * (tb / 2), (1 + w) * (tb / 2), (n / 2)])
    f = f / (n / 2)
    m = [1, 1, 0, 0]
    w = [1, d1 / d2]

    h = signal.firls(n + 1, f, m, w)
    # shift the filter half a sample to make it symmetric, like in MATLAB
    c = np.exp(
        1j * 2 * np.pi / (2 * (n + 1)) *
        np.concatenate([np.arange(0, n / 2 + 1, 1),
                        np.arange(-n / 2, 0, 1)]))
    h = np.real(sp.ifft(np.multiply(sp.fft(h, center=False), c), center=False))

    # lop off extra sample
    h = h[:n]

    return h
Beispiel #10
0
 def time_ifft(self):
     y = sp.ifft(self.x)
Beispiel #11
0
def prepare_knee_data(ismrmrd_path):
    """Convert ISMRMRD file to slices along readout.

    Args:
        ismrmrd_path (pathlib.Path): file path to ISMRMRD file.

    """

    logging.info('Processing {}'.format(ismrmrd_path.stem))
    dset = ismrmrd.Dataset(str(ismrmrd_path))
    hdr = ismrmrd.xsd.CreateFromDocument(dset.read_xml_header())

    matrix_size_x = hdr.encoding[0].encodedSpace.matrixSize.x
    matrix_size_y = hdr.encoding[0].encodedSpace.matrixSize.y
    number_of_slices = hdr.encoding[0].encodingLimits.slice.maximum + 1
    number_of_channels = hdr.acquisitionSystemInformation.receiverChannels

    ksp = np.zeros(
        [number_of_channels, number_of_slices, matrix_size_y, matrix_size_x],
        dtype=np.complex64)
    for acqnum in range(dset.number_of_acquisitions()):
        acq = dset.read_acquisition(acqnum)

        y = acq.idx.kspace_encode_step_1
        ksp[:, acq.idx.slice, y, :] = acq.data

    ksp = np.fft.fft(np.fft.ifftshift(ksp, axes=-3), axis=-3)
    ksp_lr_bf = sp.resize(ksp, [
        number_of_channels, number_of_slices // 2, matrix_size_y // 2,
        matrix_size_x // 2
    ])
    ksp_lr = sp.resize(ksp_lr_bf, ksp.shape)
    del ksp

    #ksp_lr = sp.resize(sp.resize(ksp, [number_of_channels,
    #                                   number_of_slices // 2,
    #                                   matrix_size_y // 2,
    #                                   matrix_size_x]),
    #                   ksp.shape)

    #img = np.sum(np.abs(sp.ifft(ksp, axes=[-1, -2, -3]))**2, axis=0)**0.5
    img_lr_bf = np.sum(np.abs(sp.ifft(ksp_lr_bf, axes=[-1, -2, -3]))**2,
                       axis=0)**0.5
    #np.save("./stefan_data/img_lr_bf.npy", img_lr_bf)
    #quit()
    img_lr = np.sum(np.abs(sp.ifft(ksp_lr, axes=[-1, -2, -3]))**2, axis=0)**0.5
    smallMatrixX = matrix_size_x // 2
    scale = 1 / img_lr.max()
    scale2 = 1 / img_lr_bf.max()
    for i in range(matrix_size_x):
        logging.info('Processing {}_{:03d}'.format(ismrmrd_path.stem, i))
        #img_i_path = ismrmrd_path.parents[1] / 'img' / '{}_{:03d}'.format(ismrmrd_path.stem, i)
        #img_lr_i_path = ismrmrd_path.parents[1] / 'img_lr' / '{}_{:03d}'.format(ismrmrd_path.stem, i)
        img_lr_i_path = ismrmrd_path.parents[
            1] / 'img_lr2' / '{}_{:03d}'.format(ismrmrd_path.stem, i)
        if i < smallMatrixX:
            img_lr_bf_i_path = ismrmrd_path.parents[
                1] / 'img_lr2_bf' / '{}_{:03d}'.format(ismrmrd_path.stem, i)

        #img_i = img[..., i]
        img_lr_i = img_lr[..., i]
        if i < smallMatrixX:
            img_lr_bf_i = img_lr_bf[..., i]
        #np.save(str(img_i_path), img_i * scale)
        np.save(str(img_lr_i_path), img_lr_i * scale)
        if i < smallMatrixX:
            np.save(str(img_lr_bf_i_path), img_lr_bf_i * scale2)
Beispiel #12
0
def dz_hadamard_b(n=128, g=5, gind=1, tb=4, d1=0.01, d2=0.01, shift=32):
    r"""Design a pulse with hadamard encoding

    Args:
        n (int): number of time points.
        g (int): order of the Hadamard matrix.
        gind (int): index of vector to use from Hadamard matrix for encoding.
        tb (int): time bandwidth product.
        d1 (float): passband ripple level in :math:'M_0^{-1}'.
        d2 (float): stopband ripple level in :math:'M_0^{-1}'.
        shift (int): n time points shift of pulse.

    Returns:
        b (array): SLR beta parameter.

    References:
            Souza, S.P., Szumowski, J., Dumoulin, C.L., Plewes, D.P. &
            Glover, G. 'Sima: Simultaneous multislice acquisition of MR images
            by hadamard - encoded excitation. J.Comput.Assist.Tomogr. 12,
            1026–1030(1988).
    """

    H = linalg.hadamard(g)
    encode = H[gind - 1, :]

    ftw = dinf(d1, d2) / tb  # fractional transition width of the slab profile

    if gind == 1:  # no sub-slices
        b = dzls(n, tb, d1, d2)
    else:
        # left stopband
        f = np.asarray([0, shift - (1 + ftw) * (tb / 2)])
        m = np.asarray([0, 0])
        w = np.asarray([d1 / d2])
        # first sub-band
        ii = 1
        gcent = shift + (ii - g / 2 - 1 / 2) * tb / g  # first band center
        # first band left edge
        f = np.append(f, gcent - (tb / g / 2 - ftw * (tb / 2)))
        m = np.append(m, encode[ii - 1])
        if encode[ii - 1] != encode[ii]:
            # add the first band's right edge and its amplitude, and a weight
            f = np.append(f, gcent + (tb / g / 2 - ftw * (tb / 2)))
            m = np.append(m, encode[ii - 1])
            w = np.append(w, 1)
        # middle sub-bands
        for ii in range(2, g):
            gcent = shift + (ii - g / 2 - 1 / 2) * tb / g  # center of band
            if encode[ii - 1] != encode[ii - 2]:
                # add a left edge and amp for this band
                f = np.append(f, gcent - (tb / g / 2 - ftw * (tb / 2)))
                m = np.append(m, encode[ii - 1])
            if encode[ii - 1] != encode[ii]:
                # add a right edge and its amp, and a weight for this band
                f = np.append(f, gcent + (tb / g / 2 - ftw * (tb / 2)))
                m = np.append(m, encode[ii - 1])
                w = np.append(w, 1)
        # last sub-band
        ii = g
        gcent = shift + (ii - g / 2 - 1 / 2) * tb / g  # center of last band
        if encode[ii - 1] != encode[ii - 2]:
            # add a left edge and amp for the last band
            f = np.append(f, gcent - (tb / g / 2 - ftw * (tb / 2)))
            m = np.append(m, encode[ii - 1])
        # add a right edge and its amp, and a weight for the last band
        f = np.append(f, gcent + (tb / g / 2 - ftw * (tb / 2)))
        m = np.append(m, encode[ii - 1])
        w = np.append(w, 1)
        # right stop-band
        f = np.append(f, (shift + (1 + ftw) * (tb / 2), (n / 2))) / (n / 2)
        m = np.append(m, [0, 0])
        w = np.append(w, d1 / d2)

        # separate the positive and negative bands
        mp = (m > 0).astype(float)
        mn = (m < 0).astype(float)

        # design the positive and negative filters
        c = np.exp(1j * 2 * np.pi / (2 * (n + 1)) * np.concatenate(
            [np.arange(0, n / 2 + 1, 1),
             np.arange(-n / 2, 0, 1)]))
        bp = signal.firls(n + 1, f, mp, w)  # the positive filter
        bn = signal.firls(n + 1, f, mn, w)  # the negative filter

        # combine the filters and demodulate
        b = sp.ifft(np.multiply(sp.fft(bp - bn, center=False), c),
                    center=False)

        b = np.real(b[:n])
        # hilbert transform to suppress negative passband
        b = signal.hilbert(b)
        # demodulate to DC
        c_shift = np.exp(-1j * 2 * np.pi / n * shift * np.arange(0, n, 1)) / 2
        c_shift *= np.exp(-1j * np.pi / n * shift)
        b = np.multiply(b, c_shift)

    return b
Beispiel #13
0
def dz_gslider_b(n=128,
                 g=5,
                 gind=1,
                 tb=4,
                 d1=0.01,
                 d2=0.01,
                 phi=np.pi,
                 shift=32):
    r"""Design a g-slider pulse b

    Args:
        n (int): number of time points.
        g (int): number of sub-slices.
        gind (int): subslice index.
        tb (int): time bandwidth product.
        d1 (float): passband ripple level in :math:'M_0^{-1}'.
        d2 (float): stopband ripple level in :math:'M_0^{-1}'.
        phi (float): subslice phase.
        shift (int): n time points shift of pulse.

    Returns:
        b (array): SLR beta parameter.

    References:
        Setsompop, K. et al. 'High-resolution in vivo diffusion imaging of the
        human brain with generalized slice dithered enhanced resolution:
        Simultaneous multislice (gSlider-SMS). Magn. Reson. Med.79, 141–151
        (2018).
    """
    ftw = dinf(d1, d2) / tb  # fractional transition width of the slab profile

    if np.fmod(g, 2) and gind == int(np.ceil(g / 2)):  # centered sub-slice
        if g == 1:  # no sub-slices, as a sanity check
            b = dzls(n, tb, d1, d2)
        else:
            # Design 2 filters, to allow arbitrary phases on the subslice the
            # first is a wider notch filter with '0's where it the subslice
            # appears, and the second is the subslice. Multiply the subslice by
            # its phase and add the filters.
            f = np.asarray([
                0, (1 / g - ftw) * (tb / 2), (1 / g + ftw) * (tb / 2),
                (1 - ftw) * (tb / 2), (1 + ftw) * (tb / 2), (n / 2)
            ])
            f = f / (n / 2)
            m_notch = [0, 0, 1, 1, 0, 0]
            m_sub = [1, 1, 0, 0, 0, 0]
            w = [1, 1, d1 / d2]

            b_notch = signal.firls(n + 1, f, m_notch, w)  # the notched filter
            b_sub = signal.firls(n + 1, f, m_sub, w)  # the subslice filter
            # add them with the subslice phase
            b = np.add(b_notch, np.multiply(np.exp(1j * phi), b_sub))
            # shift the filter half a sample to make it symmetric,
            # like in MATLAB
            c = np.exp(1j * 2 * np.pi / (2 * (n + 1)) * np.concatenate(
                [np.arange(0, n / 2 + 1, 1),
                 np.arange(-n / 2, 0, 1)]))
            b = sp.ifft(np.multiply(sp.fft(b, center=False), c), center=False)
            # lop off extra sample
            b = b[:n]

    else:
        # design filters for the slab and the subslice, hilbert xform them
        # to suppress their left bands,
        # then demodulate the result back to DC
        gcent = shift + (gind - g / 2 - 1 / 2) * tb / g
        if gind > 1 and gind < g:
            # separate transition bands for slab+slice
            f = np.asarray([
                0, shift - (1 + ftw) * (tb / 2), shift - (1 - ftw) * (tb / 2),
                gcent - (tb / g / 2 + ftw * (tb / 2)),
                gcent - (tb / g / 2 - ftw * (tb / 2)),
                gcent + (tb / g / 2 - ftw * (tb / 2)),
                gcent + (tb / g / 2 + ftw * (tb / 2)),
                shift + (1 - ftw) * (tb / 2), shift + (1 + ftw) * (tb / 2),
                (n / 2)
            ])
            f = f / (n / 2)
            m_notch = [0, 0, 1, 1, 0, 0, 1, 1, 0, 0]
            m_sub = [0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
            w = [d1 / d2, 1, 1, 1, d1 / d2]
        elif gind == 1:
            # the slab and slice share a left transition band
            f = np.asarray([
                0, shift - (1 + ftw) * (tb / 2), shift - (1 - ftw) * (tb / 2),
                gcent + (tb / g / 2 - ftw * (tb / 2)),
                gcent + (tb / g / 2 + ftw * (tb / 2)),
                shift + (1 - ftw) * (tb / 2), shift + (1 + ftw) * (tb / 2),
                (n / 2)
            ])
            f = f / (n / 2)
            m_notch = [0, 0, 0, 0, 1, 1, 0, 0]
            m_sub = [0, 0, 1, 1, 0, 0, 0, 0]
            w = [d1 / d2, 1, 1, d1 / d2]
        elif gind == g:
            # the slab and slice share a right transition band
            f = np.asarray([
                0, shift - (1 + ftw) * (tb / 2), shift - (1 - ftw) * (tb / 2),
                gcent - (tb / g / 2 + ftw * (tb / 2)),
                gcent - (tb / g / 2 - ftw * (tb / 2)),
                shift + (1 - ftw) * (tb / 2), shift + (1 + ftw) * (tb / 2),
                (n / 2)
            ])
            f = f / (n / 2)
            m_notch = [0, 0, 1, 1, 0, 0, 0, 0]
            m_sub = [0, 0, 0, 0, 1, 1, 0, 0]
            w = [d1 / d2, 1, 1, d1 / d2]

        c = np.exp(1j * 2 * np.pi / (2 * (n + 1)) * np.concatenate(
            [np.arange(0, n / 2 + 1, 1),
             np.arange(-n / 2, 0, 1)]))

        b_notch = signal.firls(n + 1, f, m_notch, w)  # the notched filter
        b_notch = sp.ifft(np.multiply(sp.fft(b_notch, center=False), c),
                          center=False)
        b_notch = np.real(b_notch[:n])
        # hilbert transform to suppress negative passband
        b_notch = signal.hilbert(b_notch)

        b_sub = signal.firls(n + 1, f, m_sub, w)  # the sub-band filter
        b_sub = sp.ifft(np.multiply(sp.fft(b_sub, center=False), c),
                        center=False)

        b_sub = np.real(b_sub[:n])
        # hilbert transform to suppress negative passband
        b_sub = signal.hilbert(b_sub)

        # add them with the subslice phase
        b = b_notch + np.exp(1j * phi) * b_sub

        # demodulate to DC
        c_shift = np.exp(-1j * 2 * np.pi / n * shift * np.arange(0, n, 1)) / 2
        c_shift *= np.exp(-1j * np.pi / n * shift)

        b = np.multiply(b, c_shift)

    return b
Beispiel #14
0
def kspace_precond(mps,
                   weights=None,
                   coord=None,
                   lamda=0,
                   device=sp.cpu_device,
                   oversamp=1.25):
    r"""Compute a diagonal preconditioner in k-space.

    Considers the optimization problem:

    .. math::
        \min_P \| P A A^H - I \|_F^2

    where A is the Sense operator.

    Args:
        mps (array): sensitivity maps of shape [num_coils] + image shape.
        weights (array): k-space weights.
        coord (array): k-space coordinates of shape [...] + [ndim].
        lamda (float): regularization.

    Returns:
        array: k-space preconditioner of same shape as k-space.

    """
    dtype = mps.dtype

    if weights is not None:
        weights = sp.to_device(weights, device)

    device = sp.Device(device)
    xp = device.xp

    mps_shape = list(mps.shape)
    img_shape = mps_shape[1:]
    img2_shape = [i * 2 for i in img_shape]
    ndim = len(img_shape)

    scale = sp.prod(img2_shape)**1.5 / sp.prod(img_shape)
    with device:
        if coord is None:
            idx = (slice(None, None, 2), ) * ndim

            ones = xp.zeros(img2_shape, dtype=dtype)
            if weights is None:
                ones[idx] = 1
            else:
                ones[idx] = weights**0.5

            psf = sp.ifft(ones)
        else:
            coord2 = coord * 2
            ones = xp.ones(coord.shape[:-1], dtype=dtype)
            if weights is not None:
                ones *= weights**0.5

            psf = sp.nufft_adjoint(ones, coord2, img2_shape, oversamp=oversamp)

        p_inv = []
        for mps_i in mps:
            mps_i = sp.to_device(mps_i, device)
            mps_i_norm2 = xp.linalg.norm(mps_i)**2
            xcorr_fourier = 0
            for mps_j in mps:
                mps_j = sp.to_device(mps_j, device)
                xcorr_fourier += xp.abs(
                    sp.fft(mps_i * xp.conj(mps_j), img2_shape))**2

            xcorr = sp.ifft(xcorr_fourier)
            xcorr *= psf
            if coord is None:
                p_inv_i = sp.fft(xcorr)[idx]
            else:
                p_inv_i = sp.nufft(xcorr, coord2, oversamp=oversamp)

            if weights is not None:
                p_inv_i *= weights**0.5

            p_inv.append(p_inv_i * scale / mps_i_norm2)

        p_inv = (xp.abs(xp.stack(p_inv, axis=0)) + lamda) / (1 + lamda)
        p_inv[p_inv == 0] = 1
        p = 1 / p_inv

        return p.astype(dtype)
plt.imshow(img_lr_bf[:, 100, :], cmap='gray')
plt.show()
#print (img_lr_bf.shape)
quit()

ksp = np.load('./stefan_data/ksp.npy')
ksp_lr = np.load('./stefan_data/ksp_lr.npy')
ksp_lr_bf = np.load('./stefan_data/ksp_lr_bf.npy')
print(ksp_lr_bf.shape)
#quit()
print(ksp.shape)
print(ksp_lr.shape)
print(ksp_lr_bf.shape)
#quit()
import sigpy as sp
img = np.sum(np.abs(sp.ifft(ksp_lr, axes=[-1, -2, -3]))**2, axis=0)**0.5
#img_lr_bf = np.sum(np.abs(sp.ifft(ksp_lr_bf, axes=[-1, -2, -3]))**2, axis=0)**0.5

plt.imshow(img[:, 100, :], cmap='gray')
plt.show()

#plt.imshow(np.real(ksp_lr[0, :, 100, :]))
#plt.show()
#plt.imshow(np.real(lr[0, :, 100, :]))
#plt.imshow(np.real(img_lr_bf[:, 100, :]), cmap='gray')
#plt.show()
quit()
'''
#R = np.load('filter.raisr')
#R = np.load(open(r'./filter.raisr', 'rb'), allow_pickle=True)
#quit()
Beispiel #16
0
def dzrf(n=64,
         tb=4,
         ptype='st',
         ftype='ls',
         d1=0.01,
         d2=0.01,
         cancel_alpha_phs=False,
         custom_profile=None):
    r"""Primary function for design of pulses using the SLR algorithm.

    Args:
        n (int): number of time points.
        tb (int): pulse time bandwidth product.
        ptype (string): pulse type, 'st' (small-tip excitation), 'ex' (pi/2
            excitation pulse), 'se' (spin-echo pulse), 'inv' (inversion), or
            'sat' (pi/2 saturation pulse).
        ftype (string): type of filter to use: 'ms' (sinc), 'pm'
            (Parks-McClellan equal-ripple), 'min' (minphase using factored pm),
            'max' (maxphase using factored pm), 'ls' (least squares), or 'cp'
            (custom excitation profile).
        d1 (float): passband ripple level in :math:'M_0^{-1}'.
        d2 (float): stopband ripple level in :math:'M_0^{-1}'.
        cancel_alpha_phs (bool): For 'ex' pulses, absorb the alpha phase
            profile from beta's profile, so they cancel for a flatter
            total phase
        custom_profile (array): if provided, pulse will be designed to excite
            an arbitrary profile rather than a rectangular one, following [2].

    Returns:
        rf (array): designed RF pulse.

    References:
        [1] Pauly, J., Le Roux, Patrick., Nishimura, D., and Macovski, A.
        (1991). Parameter Relations for the Shinnar-LeRoux Selective Excitation
        Pulse Design Algorithm.
        IEEE Transactions on Medical Imaging, Vol 10, No 1, 53-65.

        [2] Barral, J., Pauly, J., and Nishimura, D. (2008). SLR RF Pulse
        Design for Arbitrarily-Shaped Excitation Profiles.
        Proc. Intl. Soc. Mag. Reson. Med. 16, 1323.
    """

    [bsf, d1, d2] = calc_ripples(ptype, d1, d2)

    if ftype == 'ms':  # sinc
        b = msinc(n, tb / 4)
    elif ftype == 'pm':  # linphase
        b = dzlp(n, tb, d1, d2)
    elif ftype == 'min':  # minphase
        b = dzmp(n, tb, d1, d2)
        b = b[::-1]
    elif ftype == 'max':  # maxphase
        b = dzmp(n, tb, d1, d2)
    elif ftype == 'ls':  # least squares
        b = dzls(n, tb, d1, d2)
    elif ftype == 'cp':  # custom profile, [2]
        if custom_profile is None:
            raise Exception('cp filter selected but custom_profile not passed')
        b = np.sin(np.arcsin(custom_profile) / 2)
    else:
        raise Exception('Filter type ("{}") is not recognized.'.format(ftype))

    if ftype == 'cp':  # custom profile rf design, following [2]
        b_hat = bsf * sp.ifft(b, center=True, norm=None)
        rf = b2rf(b_hat, cancel_alpha_phs=True)
    else:
        if ptype == 'st':
            rf = b
        elif ptype == 'ex':
            b = bsf * b
            rf = b2rf(b, cancel_alpha_phs)
        else:
            b = bsf * b
            rf = b2rf(b)

    return rf
Beispiel #17
0
def dz_recursive_rf(n_seg,
                    tb,
                    n,
                    se_seq=False,
                    tb_ref=8,
                    z_pad_fact=4,
                    win_fact=1.75,
                    cancel_alpha_phs=True,
                    t1=np.inf,
                    tr_seg=60,
                    use_mz=True,
                    d1=0.01,
                    d2=0.01,
                    d1se=0.01,
                    d2se=0.01):
    r"""Recursive SLR pulse design.

    Args:
        n_seg (int): number of segments designed by recursion.
        tb (int): time bandwidth product.
        n (int): pulse length.
        se_seq (bool): spin echo sequence.
        tb_ref (int): time bandwidth product of refocusing pulse.
        z_pad_fact (float): zero padding factor.
        win_fact (float): applied window factor.
        cancel_alpha_phs (bool): absorb the alpha phase
            profile from beta's profile, so they cancel for a flatter
            total phase
        t1 (float): t1
        tr_seg (int): length of tr
        use_mz (bool): design the pulses accounting for the actual Mz profile
        d1 (float): passband ripple level in :math:'M_0^{-1}'.
        d2 (float): stopband ripple level in :math:'M_0^{-1}'.
        d1se (float): passband ripple level for se
        d2se (float): stopband ripple level for se

    Returns:
        If se_seq=True, 2-element tuple containing

        - **rf** (*array*): rf pulse out.
        - **rf_ref** (*array*): rf refocusing pulse out.
    """

    # get refocusing pulse and its rotation parameters
    if se_seq is True:
        [bsf, d1se, d2se] = calc_ripples('se', d1se, d2se)
        b_ref = bsf * dzls(n, tb_ref, d1se, d2se)
        b_ref = np.concatenate(
            (np.zeros(int(z_pad_fact * n / 2 - n / 2)), b_ref,
             np.zeros(int(z_pad_fact * n / 2 - n / 2))))
        rf_ref = b2rf(b_ref)
        bref = sp.fft(b_ref, norm=None)
        bref /= np.max(np.abs(bref))
        bref_mag = np.abs(bref)
        aref_mag = np.abs(np.sqrt(1 - bref_mag**2))
        flip_ref = 2 * np.arcsin(bref_mag[int(z_pad_fact * n / 2)]) \
            * 180 / np.pi

    # get flip angles
    flip = np.zeros(n_seg)
    flip[n_seg - 1] = 90
    for jj in range(n_seg - 2, -1, -1):
        if se_seq is False:
            flip[jj] = np.arctan(np.sin(flip[jj + 1] * np.pi / 180))
            flip[jj] = flip[jj] * 180 / np.pi  # deg
        else:
            flip[jj] = np.arctan(
                np.cos(flip_ref * np.pi / 180) *
                np.sin(flip[jj + 1] * np.pi / 180))
            flip[jj] = flip[jj] * 180 / np.pi  # deg

    # design first RF pulse
    b = np.zeros((int(z_pad_fact * n), n_seg), dtype=complex)
    b[int(z_pad_fact * n / 2 - n / 2):int(z_pad_fact * n / 2 + n / 2), 0] = \
        dzls(n, tb, d1, d2)
    # b = np.concatenate((np.zeros(int(zPadFact*N/2-N/2)), b,
    #    np.zeros(int(zPadFact*N/2-N/2))))
    B = sp.fft(b[:, 0], norm=None)

    c = np.exp(-1j * 2 * np.pi / (n * z_pad_fact) / 2 *
               np.arange(-n * z_pad_fact / 2, n * z_pad_fact / 2, 1))
    B = np.multiply(B, c)
    b[:, 0] = sp.ifft(B / np.max(np.abs(B)), norm=None)
    b[:, 0] *= np.sin(flip[0] * (np.pi / 180) / 2)
    rf = np.zeros((z_pad_fact * n, n_seg), dtype=complex)
    a = b2a(b[:, 0])
    if cancel_alpha_phs:
        # cancel a phase by absorbing into b
        # Note that this is the only time we need to do it
        b_a_phase = sp.fft(b[:, 0], center=False, norm=None) * \
            np.exp(-1j * np.angle(sp.fft(a[np.size(a)::-1],
                                         center=False, norm=None)))
        b[:, 0] = sp.ifft(b_a_phase, center=False, norm=None)
    rf[:, 0] = b2rf(b[:, 0])

    # get min-phase alpha and its response
    # a = b2a(b[:, 0])
    A = sp.fft(a)

    # calculate beta filter response
    B = sp.fft(b[:, 0], norm=None)

    if win_fact < z_pad_fact:
        win_len = (win_fact - 1) * n
        npad = n * z_pad_fact - win_fact * n
        # blackman window?
        window = signal.blackman(int((win_fact - 1) * n))
        # split in half; stick N ones in the middle
        window = np.concatenate((window[0:int(win_len / 2)], np.ones(n),
                                 window[int(win_len / 2):]))
        window = np.concatenate(
            (np.zeros(int(npad / 2)), window, np.zeros(int(npad / 2))))
        # apply windowing to first pulse for consistency
        b[:, 0] = np.multiply(b[:, 0], window)
        rf[:, 0] = b2rf(b[:, 0])
        # recalculate B and A
        B = sp.fft(b[:, 0], norm=None)
        A = sp.fft(b2a(b[:, 0]), norm=None)

    # use A and B to get Mxy
    # Mxy = np.zeros((zPadFact*N, Nseg), dtype = complex)
    if se_seq is False:
        mxy0 = 2 * np.conj(A) * B
    else:
        mxy0 = 2 * A * np.conj(B) * bref**2

    # Amplitude of next pulse's Mxy profile will be
    #               |Mz*2*a*b| = |Mz*2*sqrt(1-abs(B).^2)*B|.
    # If we set this = |Mxy_1|, we can solve for |B| via solving quadratic
    # equation 4*Mz^2*(1-B^2)*B^2 = |Mxy_1|^2.
    # Subsequently solve for |A|, and get phase of A via min-phase, and
    # then get phase of B by dividing phase of A from first pulse's Mxy phase.
    mz = np.ones((z_pad_fact * n), dtype=complex)
    for jj in range(1, n_seg):

        # calculate Mz profile after previous pulse
        if se_seq is False:
            mz = mz * (1 - 2 * np.abs(B) ** 2) * np.exp(-tr_seg / t1) + \
                 (1 - np.exp(-tr_seg / t1))
        else:
            mz = mz * (1 - 2 *
                       (np.abs(A * bref_mag)**2 + np.abs(aref_mag * B)**2))
            # (second term is about 1%)

        if use_mz is True:  # design the pulses accounting for the
            # actual Mz profile (the full method)
            # set up quadratic equation to get |B|
            cq = -np.abs(mxy0)**2
            if se_seq is False:
                bq = 4 * mz**2
                aq = -4 * mz**2
            else:
                bq = 4 * (bref_mag**4) * mz**2
                aq = -4 * (bref_mag**4) * mz**2
            bmag = np.sqrt(
                (-bq + np.real(np.sqrt(bq**2 - 4 * aq * cq))) / (2 * aq))
            bmag[np.isnan(bmag)] = 0
            # get A - easier to get complex A than complex B since |A| is
            # determined by |B|, and phase is gotten by min-phase relationship
            # Phase of B doesn't matter here since only profile mag is used by
            # b2a
            A = sp.fft(b2a(sp.ifft(bmag, norm=None)), norm=None)
            # trick: now we can get complex B from ratio of Mxy and A
            B = mxy0 / (2 * np.conj(A) * mz)

        else:  # design assuming ideal Mz (conventional VFA)

            B *= np.sin(np.pi / 180 * flip[jj] / 2) \
                 / np.sin(np.pi / 180 * flip[jj - 1] / 2)
            A = sp.fft(b2a(sp.ifft(B, norm=None)), norm=None)

        # get polynomial
        b[:, jj] = sp.ifft(B, norm=None)

        if win_fact < z_pad_fact:
            b[:, jj] *= window
            # recalculate B and A
            B = sp.fft(b[:, jj], norm=None)
            A = sp.fft(b2a(b[:, jj]), norm=None)

        rf[:, jj] = b2rf(b[:, jj])

    # truncate the RF
    if win_fact < z_pad_fact:
        pulse_len = int(win_fact * n)
        rf = rf[int(npad / 2):int(npad / 2 + pulse_len), :]

    if se_seq is False:
        return rf
    else:
        return rf, rf_ref
Beispiel #18
0
def circulant_precond(mps,
                      weights=None,
                      coord=None,
                      lamda=0,
                      device=sp.cpu_device):
    r"""Compute circulant preconditioner.

    Considers the optimization problem:

    .. math::
        \min_P \| A^H A - F P F^H  \|_2^2

    where A is the Sense operator,
    and F is a unitary Fourier transform operator.

    Args:
        mps (array): sensitivity maps of shape [num_coils] + image shape.
        weights (array): k-space weights.
        coord (array): k-space coordinates of shape [...] + [ndim].
        lamda (float): regularization.

    Returns:
        array: circulant preconditioner of image shape.

    """
    if coord is not None:
        coord = sp.to_device(coord, device)

    if weights is not None:
        weights = sp.to_device(weights, device)

    dtype = mps.dtype
    device = sp.Device(device)
    xp = device.xp

    mps_shape = list(mps.shape)
    img_shape = mps_shape[1:]
    img2_shape = [i * 2 for i in img_shape]
    ndim = len(img_shape)

    scale = sp.prod(img2_shape)**1.5 / sp.prod(img_shape)**2
    with device:
        idx = (slice(None, None, 2), ) * ndim
        if coord is None:
            ones = xp.zeros(img2_shape, dtype=dtype)
            if weights is None:
                ones[idx] = 1
            else:
                ones[idx] = weights**0.5

            psf = sp.ifft(ones)
        else:
            coord2 = coord * 2
            ones = xp.ones(coord.shape[:-1], dtype=dtype)
            if weights is not None:
                ones *= weights**0.5

            psf = sp.nufft_adjoint(ones, coord2, img2_shape)

        p_inv = 0
        for mps_i in mps:
            mps_i = sp.to_device(mps_i, device)
            xcorr_fourier = xp.abs(sp.fft(xp.conj(mps_i), img2_shape))**2
            xcorr = sp.ifft(xcorr_fourier)
            xcorr *= psf
            p_inv_i = sp.fft(xcorr)
            p_inv_i = p_inv_i[idx]
            p_inv_i *= scale
            if weights is not None:
                p_inv_i *= weights**0.5

            p_inv += p_inv_i

        p_inv += lamda
        p_inv[p_inv == 0] = 1
        p = 1 / p_inv

        return p.astype(dtype)
Beispiel #19
0
 def time_ifft_non_centered(self):
     y = sp.ifft(self.x, center=False)
Beispiel #20
0
def espirit_maps(ksp,
                 calib_width=24,
                 thresh=0.001,
                 kernel_width=6,
                 crop=0.8,
                 max_power_iter=30,
                 device=sp.cpu_device,
                 output_eigenvalue=False):
    """Generate ESPIRiT maps from k-space.

    Currently only supports outputting one set of maps.

    Args:
        ksp (array): k-space array of shape [num_coils, n_ndim, ..., n_1]
        calib (tuple of ints): length-2 image shape.
        thresh (float): threshold for the calibration matrix.
        kernel_width (int): kernel width for the calibration matrix.
        max_power_iter (int): maximum number of power iterations.
        device (Device): computing device.
        crop (int): cropping threshold.

    Returns:
        array: ESPIRiT maps of the same shape as ksp.

    References:
        Martin Uecker, Peng Lai, Mark J. Murphy, Patrick Virtue, Michael Elad,
        John M. Pauly, Shreyas S. Vasanawala, and Michael Lustig
        ESPIRIT - An Eigenvalue Approach to Autocalibrating Parallel MRI:
        Where SENSE meets GRAPPA.
        Magnetic Resonance in Medicine, 71:990-1001 (2014)

    """
    img_ndim = ksp.ndim - 1
    num_coils = len(ksp)
    with sp.get_device(ksp):
        # Get calibration region
        calib_shape = [num_coils] + [calib_width] * img_ndim
        calib = sp.resize(ksp, calib_shape)
        calib = sp.to_device(calib, device)

    device = sp.Device(device)
    xp = device.xp
    with device:
        # Get calibration matrix
        kernel_shape = [num_coils] + [kernel_width] * img_ndim
        kernel_strides = [1] * (img_ndim + 1)
        mat = sp.array_to_blocks(calib, kernel_shape, kernel_strides)
        mat = mat.reshape([-1, sp.prod(kernel_shape)])

        # Perform SVD on calibration matrix
        _, S, VH = xp.linalg.svd(mat, full_matrices=False)
        VH = VH[S > thresh * S.max(), :]

        # Get kernels
        num_kernels = len(VH)
        kernels = VH.reshape([num_kernels] + kernel_shape)
        img_shape = ksp.shape[1:]

        # Get covariance matrix in image domain
        AHA = xp.zeros(img_shape[::-1] + (num_coils, num_coils),
                       dtype=ksp.dtype)
        for kernel in kernels:
            img_kernel = sp.ifft(sp.resize(kernel, ksp.shape),
                                 axes=range(-img_ndim, 0))
            aH = xp.expand_dims(img_kernel.T, axis=-1)
            a = xp.conj(aH.swapaxes(-1, -2))
            AHA += aH @ a

        AHA *= (sp.prod(img_shape) / kernel_width**img_ndim)

        # Power Iteration to compute top eigenvector
        mps = xp.ones(ksp.shape[::-1] + (1, ), dtype=ksp.dtype)
        for _ in range(max_power_iter):
            sp.copyto(mps, AHA @ mps)
            eig_value = xp.sum(xp.abs(mps)**2, axis=-2, keepdims=True)**0.5
            mps /= eig_value

        # Normalize phase with respect to first channel
        mps = mps.T[0]
        mps *= xp.conj(mps[0] / xp.abs(mps[0]))

        # Crop maps by thresholding eigenvalue
        eig_value = eig_value.T[0]
        mps *= eig_value > crop

        if output_eigenvalue:
            return mps, eig_value
        else:
            return mps