コード例 #1
0
def seggrappa(kspace, calibs, *args, **kwargs):
    '''Segmented GRAPPA.

    See pygrappa.grappa() for full list of arguments.

    Parameters
    ----------
    calibs : list of array_like
        List of calibration regions.

    Notes
    -----
    A generalized implementation of the method described in [1]_.
    Multiple ACS regions can be supplied to function.  GRAPPA is run
    for each ACS region and then averaged to produce the final
    reconstruction.

    References
    ----------
    .. [1] Park, Jaeseok, et al. "Artifact and noise suppression in
           GRAPPA imaging using improved k‐space coil calibration and
           variable density sampling." Magnetic Resonance in
           Medicine: An Official Journal of the International Society
           for Magnetic Resonance in Medicine 53.1 (2005): 186-193.
    '''

    # Do the reconstruction for each of the calibration regions
    recons = [cgrappa(kspace, c, *args, **kwargs) for c in calibs]

    # Average all the reconstructions
    return np.mean(recons, axis=0)
コード例 #2
0
ファイル: basic_cgrappa.py プロジェクト: nadrx/pygrappa
        np.fft.ifftshift(imspace, axes=ax), axes=ax), axes=ax)

    # crop 20x20 window from the center of k-space for calibration
    pd = 10
    ctr = int(N/2)
    calib = kspace[ctr-pd:ctr+pd, ctr-pd:ctr+pd, :].copy()

    # calibrate a kernel
    kernel_size = (4, 4)

    # undersample by a factor of 2 in both x and y
    kspace[::2, 1::2, :] = 0
    kspace[1::2, ::2, :] = 0

    # reconstruct:
    res = cgrappa(
        kspace, calib, kernel_size, coil_axis=-1, lamda=0.01)

    # Take a look
    res = np.abs(np.sqrt(N**2)*np.fft.fftshift(np.fft.ifft2(
        np.fft.ifftshift(res, axes=ax), axes=ax), axes=ax))
    M, N = res.shape[:2]
    res0 = np.zeros((2*M, 2*N))
    kk = 0
    for idx in np.ndindex((2, 2)):
        ii, jj = idx[:]
        res0[ii*M:(ii+1)*M, jj*N:(jj+1)*N] = res[..., kk]
        kk += 1
    plt.imshow(res0, cmap='gray')
    plt.show()
コード例 #3
0
def nlgrappa(kspace,
             calib,
             kernel_size=(5, 5),
             ml_kernel='polynomial',
             ml_kernel_args=None,
             coil_axis=-1):
    '''NL-GRAPPA.

    Parameters
    ----------
    kspace : array_like
    calib : array_like
    kernel_size : tuple of int, optional
    ml_kernel : {
            'linear', 'polynomial', 'sigmoid', 'rbf',
            'laplacian', 'chi2'}, optional
        Kernel functions modeled on scikit-learn metrics.pairwise
        module but which can handle complex-valued inputs.
    ml_kernel_args : dict or None, optional
        Arguments to pass to kernel functions.
    coil_axis : int, optional
        Axis holding the coil data.

    Returns
    -------
    res : array_like
        Reconstructed k-space.

    Notes
    -----
    Implements the algorithm described in [1]_.

    Bias term is removed from polynomial kernel as it adds a PSF-like
    overlay onto the reconstruction.

    Currently only `polynomial` method is implemented.

    References
    ----------
    .. [1] Chang, Yuchou, Dong Liang, and Leslie Ying. "Nonlinear
           GRAPPA: A kernel approach to parallel MRI reconstruction."
           Magnetic resonance in medicine 68.3 (2012): 730-740.
    '''

    raise NotImplementedError("NL-GRAPPA is not currently working!")

    # Coils to the back
    kspace = np.moveaxis(kspace, coil_axis, -1)
    calib = np.moveaxis(calib, coil_axis, -1)
    _kx, _ky, nc = kspace.shape[:]

    # Get the correct kernel:
    _phi = {
        # 'linear': linear_kernel,
        'polynomial': polynomial_kernel,
        # 'sigmoid': sigmoid_kernel,
        # 'rbf': rbf_kernel,
        # 'laplacian': laplacian_kernel,
        # 'chi2': chi2_kernel,
    }[ml_kernel]

    # Get default args if none were passed in
    if ml_kernel_args is None:
        ml_kernel_args = {
            'cross_term_neighbors': 1,
        }

    # Pass arguments to kernel function
    phi = partial(_phi, **ml_kernel_args)

    # Get the extra "virtual" channels using kernel function, phi
    vkspace = phi(kspace)
    vcalib = phi(calib)

    # Pass onto cgrappa for the heavy lifting
    return np.moveaxis(
        cgrappa(vkspace,
                vcalib,
                kernel_size=kernel_size,
                coil_axis=-1,
                nc_desired=nc,
                lamda=0), -1, coil_axis)
コード例 #4
0
ファイル: hpgrappa.py プロジェクト: zongjg/pygrappa
def hpgrappa(
        kspace, calib, fov, kernel_size=(5, 5), w=None, c=None,
        ret_filter=False, coil_axis=-1, lamda=0.01, silent=True):
    '''High-pass GRAPPA.

    Parameters
    ----------
    fov : tuple, (FOV_x, FOV_y)
        Field of view (in m).
    w : float, optional
        Filter parameter: determines the smoothness of the filter
        boundary.
    c : float, optional
        Filter parameter: sets the cutoff frequency.
    ret_filter : bool, optional
        Returns the high pass filter determined by (w, c).

    Notes
    -----
    If w and/or c are None, then the closest values listed in
    Table 1 from [1]_ will be used.

    F2 described by Equation [2] in [1]_ is used to generate the
    high pass filter.

    References
    ----------
    .. [1] Huang, Feng, et al. "High‐pass GRAPPA: An image support
           reduction technique for improved partially parallel
           imaging." Magnetic Resonance in Medicine: An Official
           Journal of the International Society for Magnetic
           Resonance in Medicine 59.3 (2008): 642-649.
    '''

    # Pass GRAPPA arguments forward
    grappa_args = {
        'kernel_size': kernel_size,
        'coil_axis': -1,
        'lamda': lamda,
        'silent': silent
    }

    # Put the coil dim in the back
    kspace = np.moveaxis(kspace, coil_axis, -1)
    calib = np.moveaxis(calib, coil_axis, -1)
    kx, ky, nc = kspace.shape[:]
    cx, cy, nc = calib.shape[:]
    kx2, ky2 = int(kx/2), int(ky/2)
    cx2, cy2 = int(cx/2), int(cy/2)

    # Get filter parameters if None provided
    if w is None or c is None:
        _w, _c = _filter_parameters(nc, np.min([cx, cy]))
        if w is None:
            w = _w
        if c is None:
            c = _c

    # We'll need the filter, seeing as this is high-pass GRAPPA
    fov_x, fov_y = fov[:]
    kxx, kyy = np.meshgrid(
        kx*np.linspace(-1, 1, kx)/(fov_x*2), # I think this gives
        ky*np.linspace(-1, 1, ky)/(fov_y*2)) # kspace FOV?
    F2 = (1 - 1/(1 + np.exp((np.sqrt(kxx**2 + kyy**2) - c)/w)) +
          1/(1 + np.exp((np.sqrt(kxx**2 + kyy**2) + c)/w)))

    # Apply the filter to both kspace and calibration data
    kspace_fil = kspace*F2[..., None]
    calib_fil = calib*F2[kx2-cx2:kx2+cx2, ky2-cy2:ky2+cy2, None]

    # Do regular old GRAPPA on filtered data
    res = cgrappa(kspace_fil, calib_fil, **grappa_args)

    # Inverse filter
    res = res/F2[..., None]

    # Restore measured data
    mask = np.abs(kspace[..., 0]) > 0
    res[mask, :] = kspace[mask, :]
    res[kx2-cx2:kx2+cx2, ky2-cy2:ky2+cy2, :] = calib
    res = np.moveaxis(res, -1, coil_axis)

    # Return the filter if user asked for it
    if ret_filter:
        return(res, F2)
    return res
コード例 #5
0
ファイル: basic_nlgrappa.py プロジェクト: nadrx/pygrappa
    ax = (0, 1)
    kspace = np.fft.ifftshift(np.fft.fft2(np.fft.fftshift(
        ph, axes=ax), axes=ax), axes=ax)

    # 20x20 calibration region
    ctr = int(N/2)
    pad = 20
    calib = kspace[ctr-pad:ctr+pad, ctr-pad:ctr+pad, :].copy()

    # Undersample: R=3
    kspace3x1 = kspace.copy()
    kspace3x1[1::3, ...] = 0
    kspace3x1[2::3, ...] = 0

    # Reconstruct using both GRAPPA and VC-GRAPPA
    res_grappa = cgrappa(kspace3x1.copy(), calib)
    res_nlgrappa = nlgrappa(
        kspace3x1.copy(), calib, ml_kernel='polynomial',
        ml_kernel_args={'cross_term_neighbors': 0})

    # Bring back to image space
    imspace_nlgrappa = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(
        res_nlgrappa, axes=ax), axes=ax), axes=ax)
    imspace_grappa = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(
        res_grappa, axes=ax), axes=ax), axes=ax)

    # Coil combine (sum-of-squares)
    cc_nlgrappa = np.sqrt(
        np.sum(np.abs(imspace_nlgrappa)**2, axis=-1))
    cc_grappa = np.sqrt(np.sum(np.abs(imspace_grappa)**2, axis=-1))
    ph = shepp_logan(N)
コード例 #6
0
ファイル: basic_seggrappa.py プロジェクト: zongjg/pygrappa
    ctr = int(N / 2)
    calib_upper = kspace[ctr - pad + offset:ctr + pad + offset, ...].copy()
    calib_lower = kspace[ctr - pad - offset:ctr + pad - offset, ...].copy()

    # A single calibration region at the center for comparison
    pad_single = 2 * pad
    calib = kspace[ctr - pad_single:ctr + pad_single, ...].copy()

    # Undersample kspace
    kspace[:, ::2, :] = 0

    # Reconstruct using segmented GRAPPA with separate ACS regions
    res_seg = seggrappa(kspace, [calib_lower, calib_upper])

    # Reconstruct using single calibration region at the center
    res_grappa = cgrappa(kspace, calib)

    # Into image space
    imspace_seg = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(res_seg,
                                                                axes=ax),
                                               axes=ax),
                                  axes=ax)
    imspace_grappa = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(res_grappa,
                                                                   axes=ax),
                                                  axes=ax),
                                     axes=ax)

    # Coil combine (sum-of-squares)
    cc_seg = np.sqrt(np.sum(np.abs(imspace_seg)**2, axis=-1))
    cc_grappa = np.sqrt(np.sum(np.abs(imspace_grappa)**2, axis=-1))
    ph = shepp_logan(N)
コード例 #7
0
                                          axes=ax),
                              axes=ax)

    # 20x20 calibration region
    ctr = int(N / 2)
    pad = 10
    calib = kspace[ctr - pad:ctr + pad, ctr - pad:ctr + pad, :].copy()

    # Undersample: R=4
    kspace4x1 = kspace.copy()
    kspace4x1[1::4, ...] = 0
    kspace4x1[2::4, ...] = 0
    kspace4x1[3::4, ...] = 0

    # Compare to regular ol' GRAPPA
    grecon4x1 = cgrappa(kspace4x1, calib, kernel_size=(4, 5))

    # Get a GRAPPA operator and do the recon
    Gx, Gy = grappaop(calib)
    recon4x1 = kspace4x1.copy()
    recon4x1[1::4, ...] = recon4x1[0::4, ...] @ Gx
    recon4x1[2::4, ...] = recon4x1[1::4, ...] @ Gx
    recon4x1[3::4, ...] = recon4x1[2::4, ...] @ Gx

    # Try different undersampling factors: Rx=2, Ry=2.  Same Gx, Gy
    # will work since we're using the same calibration region!
    kspace2x2 = kspace.copy()
    kspace2x2[1::2, ...] = 0
    kspace2x2[:, 1::2, :] = 0
    grecon2x2 = cgrappa(kspace2x2, calib, kernel_size=(4, 5))
    recon2x2 = kspace2x2.copy()
コード例 #8
0
        shepp_logan(N))[..., None]*gaussian_csm(N, N, nc)

    # Trim down to make nonsquare
    # 1st > 2nd
    trim = int((N - M)/2)
    pad = int(calib_lines/2)
    imspace1 = imspace[:, trim:-trim, :]
    kspace1 = fft(imspace1)
    calib1 = kspace1[N2-pad:N2+pad, ...].copy()
    kspace1[::2, ...] = 0 # Undersample: R=2

    # 2nd > 1st
    imspace2 = imspace[trim:-trim, ...]
    kspace2 = fft(imspace2)
    calib2 = kspace2[:, N2-pad:N2+pad, :].copy()
    kspace2[:, ::2, :] = 0

    # Do the thing
    res1 = cgrappa(kspace1, calib1, kernel_size=(5, 5), coil_axis=-1)
    res2 = cgrappa(kspace2, calib2, kernel_size=(5, 5), coil_axis=-1)

    # Show sum-of-squares results
    sos1 = np.sqrt(np.sum(np.abs(ifft(res1))**2, axis=-1))
    sos2 = np.sqrt(np.sum(np.abs(ifft(res2))**2, axis=-1))

    plt.subplot(1, 2, 1)
    plt.imshow(sos1)
    plt.subplot(1, 2, 2)
    plt.imshow(sos2)
    plt.show()
コード例 #9
0
    # generate 4 coil phantom
    ph = shepp_logan(N)
    imspace = ph[..., None] * mps
    imspace = imspace.astype('complex')
    ax = (0, 1)
    kspace = 1 / np.sqrt(N**2) * np.fft.fftshift(
        np.fft.fft2(np.fft.ifftshift(imspace, axes=ax), axes=ax), axes=ax)

    # crop 20x20 window from the center of k-space for calibration
    pd = 10
    ctr = int(N / 2)
    calib = kspace[ctr - pd:ctr + pd, ctr - pd:ctr + pd, :].copy()

    # calibrate a kernel
    kernel_size = (5, 5)

    # undersample by a factor of 2 in both x and y
    kspace[::2, 1::2, :] = 0
    kspace[1::2, ::2, :] = 0

    # Time both implementations
    t0 = time()
    recon0 = grappa(kspace, calib, (5, 5))
    print(' GRAPPA: %g' % (time() - t0))

    t0 = time()
    recon1 = cgrappa(kspace, calib, (5, 5))
    print('CGRAPPA: %g' % (time() - t0))

    assert np.allclose(recon0, recon1)
コード例 #10
0
def igrappa(kspace,
            calib,
            kernel_size=(5, 5),
            k=0.3,
            coil_axis=-1,
            lamda=0.01,
            ref=None,
            niter=10,
            silent=True):
    '''Iterative GRAPPA.

    Parameters
    ----------
    kspace : array_like
        2D multi-coil k-space data to reconstruct from.  Make sure
        that the missing entries have exact zeros in them.
    calib : array_like
        Calibration data (fully sampled k-space).
    kernel_size : tuple, optional
        Size of the 2D GRAPPA kernel (kx, ky).
    k : float, optional
        Regularization parameter for iterative reconstruction.  Must
        be in the interval (0, 1).
    coil_axis : int, optional
        Dimension holding coil data.  The other two dimensions should
        be image size: (sx, sy).
    lamda : float, optional
        Tikhonov regularization for the kernel calibration.
    ref : array_like or None, optional
        Reference k-space data.  This is the true data that we are
        attempting to reconstruct.  If provided, MSE at each
        iteration will be returned.  If None, only reconstructed
        kspace is returned.
    niter : int, optional
        Number of iterations.
    silent : bool, optional
        Suppress messages to user.

    Returns
    -------
    res : array_like
        k-space data where missing entries have been filled in.
    mse : array_like, optional
        MSE at each iteration.  Returned if ref not None.

    Raises
    ------
    AssertionError
        If regularization parameter k is not in the interval (0, 1).

    Notes
    -----
    More or less implements the iterative algorithm described in [1].

    References
    ----------
    .. [1] Zhao, Tiejun, and Xiaoping Hu. "Iterative GRAPPA (iGRAPPA)
           for improved parallel imaging reconstruction." Magnetic
           Resonance in Medicine: An Official Journal of the
           International Society for Magnetic Resonance in Medicine
           59.4 (2008): 903-907.
    '''

    # Make sure k has a reasonable value
    assert 0 < k < 1, 'Parameter k should be in (0, 1)!'

    # Collect arguments to pass to cgrappa:
    grappa_args = {
        'kernel_size': kernel_size,
        'coil_axis': -1,
        'lamda': lamda,
        'silent': silent
    }

    # Put the coil dimension at the end
    kspace = np.moveaxis(kspace, coil_axis, -1)
    calib = np.moveaxis(calib, coil_axis, -1)
    kx, ky, _nc = kspace.shape[:]
    cx, cy, _nc = calib.shape[:]
    kx2, ky2 = int(kx / 2), int(ky / 2)
    cx2, cy2 = int(cx / 2), int(cy / 2)

    # Initial conditions
    kIm, W = cgrappa(kspace, calib, ret_weights=True, **grappa_args)
    ax = (0, 1)
    Im = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(kIm, axes=ax), axes=ax),
                         axes=ax)
    Fp = 1e6  # some large number to begin with

    # If user has provided reference, let's track the MSE
    if ref is not None:
        mse = np.zeros(niter)
        aref = np.abs(ref)

    # Fixed number of iterations
    for ii in trange(niter, leave=False, desc='iGRAPPA'):

        # Update calibration region -- now includes all estimated
        # lines plus unchanged calibration region
        calib0 = kIm.copy()
        calib0[kx2 - cx2:kx2 + cx2, ky2 - cy2:ky2 + cy2, :] = calib.copy()

        kTm, Wn = cgrappa(kspace, calib0, ret_weights=True, **grappa_args)
        Tm = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(kTm, axes=ax),
                                          axes=ax),
                             axes=ax)

        # Estimate relative image intensity change
        l1_Tm = np.linalg.norm(Tm.flatten(), ord=1)
        l1_Im = np.linalg.norm(Im.flatten(), ord=1)
        Tp = np.abs(l1_Tm - l1_Im) / l1_Im

        # Update weights
        p = Tp / (k * Fp)
        if p < 1:
            # Take this reconstruction and new weights
            Im = Tm
            kIm = kTm
            W = Wn
        else:
            # Modify weights to get new reconstruction
            p = 1 / p
            W = [(1 - p) * Wn0 + p * W0 for Wn0, W0 in zip(Wn, W)]

            # Need to be able to supply grappa with weights to use!
            kIm = cgrappa(kspace, calib0, Wsupp=W, **grappa_args)
            Im = np.fft.fftshift(np.fft.ifft2(np.fft.ifftshift(kIm, axes=ax),
                                              axes=ax),
                                 axes=ax)

        # Update Fp
        Fp = Tp

        # Track MSE
        if ref is not None:
            mse[ii] = compare_mse(aref, np.abs(kIm))

    # Return the reconstructed kspace and MSE if ref kspace provided,
    # otherwise, just return reconstruction
    if ref is not None:
        return (kIm, mse)
    return kIm