Beispiel #1
0
def remove_ring_based_wavelet_fft(mat,
                                  level=5,
                                  size=1,
                                  wavelet_name="db9",
                                  sort=False):
    """
    Remove ring artifacts in a reconstructed image by combining the polar
    transform and the wavelet-fft-based method (Ref. [1]).

    Parameters
    ----------
    mat : array_like
        Square array. Reconstructed image
    level : int
        Wavelet decomposition level.
    size : int
        Damping parameter. Larger is stronger.
    wavelet_name : str
        Name of a wavelet. Search pywavelets API for a full list.
    sort : bool, optional
        Apply sorting (Ref. [2]) if True.

    Returns
    -------
    array_like
        Ring-removed image.

    References
    ----------
    .. [1] https://doi.org/10.1364/OE.17.008567
    .. [2] https://doi.org/10.1364/OE.26.028396
    """
    (nrow, ncol) = mat.shape
    if nrow != ncol:
        raise ValueError(
            "Width and height of the reconstructed image are not the same")
    mask = util.make_circle_mask(ncol, 1.0)
    (x_mat, y_mat) = util.rectangular_from_polar(ncol, ncol, ncol, ncol)
    (r_mat, theta_mat) = util.polar_from_rectangular(ncol, ncol, ncol, ncol)
    polar_mat = util.mapping(mat, x_mat, y_mat)
    polar_mat = remo.remove_stripe_based_wavelet_fft(polar_mat,
                                                     level,
                                                     size,
                                                     wavelet_name,
                                                     sort=sort)
    mat_rec = util.mapping(polar_mat, r_mat, theta_mat)
    return mat_rec * mask
Beispiel #2
0
def remove_ring_based_fft(mat, u=20, n=8, v=1, sort=False):
    """
    Remove ring artifacts in the reconstructed image by combining the polar
    transform and the fft-based method.

    Parameters
    ----------
    mat : array_like
        Square array. Reconstructed image
    u : int
        Cutoff frequency.
    n : int
        Filter order.
    v : int
        Number of rows (* 2) to be applied the filter.
    sort : bool, optional
        Apply sorting (Ref. [2]) if True.

    Returns
    -------
    array_like
        Ring-removed image.

    References
    ----------
    .. [1] https://doi.org/10.1063/1.1149043
    .. [2] https://doi.org/10.1364/OE.26.028396
    """
    (nrow, ncol) = mat.shape
    if nrow != ncol:
        raise ValueError(
            "Width and height of the reconstructed image are not the same")
    mask = util.make_circle_mask(ncol, 1.0)
    (x_mat, y_mat) = util.rectangular_from_polar(ncol, ncol, ncol, ncol)
    (r_mat, theta_mat) = util.polar_from_rectangular(ncol, ncol, ncol, ncol)
    polar_mat = util.mapping(mat, x_mat, y_mat)
    polar_mat = remo.remove_stripe_based_fft(polar_mat, u, n, v, sort=sort)
    mat_rec = util.mapping(polar_mat, r_mat, theta_mat)
    return mat_rec * mask
Beispiel #3
0
def astra_reconstruction(sinogram,
                         center,
                         angles=None,
                         ratio=1.0,
                         method="FBP_CUDA",
                         num_iter=1,
                         filter_name="hann",
                         pad=None,
                         apply_log=True):
    """
    Wrapper of reconstruction methods implemented in the astra toolbox package.
    https://www.astra-toolbox.com/docs/algs/index.html
    Users must install Astra Toolbox before using this function.

    Parameters
    ----------
    sinogram : array_like
        2D array. Sinogram image.
    center : float
        Center of rotation.
    angles : array_like
        1D array. List of angles (radian) corresponding to the sinogram.
    ratio : float
        To apply a circle mask to the reconstructed image.
    method : str
        Reconstruction algorithms. for CPU: 'FBP', 'SIRT', 'SART', 'ART',
        'CGLS'. for GPU: 'FBP_CUDA', 'SIRT_CUDA', 'SART_CUDA', 'CGLS_CUDA'.
    num_iter : int
        Number of iterations if using iteration methods.
    filter_name : str
        Apply filter if using FBP method. Options: 'hamming', 'hann',
        'lanczos', 'kaiser', 'parzen',...
    pad : int
        Padding to reduce the side effect of FFT.
    apply_log : bool
        Apply the logarithm function to the sinogram before reconstruction.

    Returns
    -------
    array_like
        Square array.
    """
    try:
        import astra
    except ImportError:
        print("!!!!!! Error !!!!!!!")
        print("You must install Astra Toolbox before using this function!")
        raise
    if apply_log is True:
        sinogram = -np.log(sinogram)
    if pad is None:
        pad = int(0.1 * sinogram.shape[1])
    sinogram = np.pad(sinogram, ((0, 0), (pad, pad)), mode='edge')
    (nrow, ncol) = sinogram.shape
    if angles is None:
        angles = np.linspace(0.0, 180.0, nrow) * np.pi / 180.0
    proj_geom = astra.create_proj_geom('parallel', 1, ncol, angles)
    vol_geom = astra.create_vol_geom(ncol, ncol)
    cen_col = (ncol - 1.0) / 2.0
    sinogram = shift(sinogram, (0, cen_col - (center + pad)), mode='nearest')
    sino_id = astra.data2d.create('-sino', proj_geom, sinogram)
    rec_id = astra.data2d.create('-vol', vol_geom)
    if "CUDA" not in method:
        proj_id = astra.create_projector('line', proj_geom, vol_geom)
    cfg = astra.astra_dict(method)
    cfg['ProjectionDataId'] = sino_id
    cfg['ReconstructionDataId'] = rec_id
    if "CUDA" not in method:
        cfg['ProjectorId'] = proj_id
    if (method == "FBP_CUDA") or (method == "FBP"):
        cfg["FilterType"] = filter_name
    alg_id = astra.algorithm.create(cfg)
    astra.algorithm.run(alg_id, num_iter)
    recon = astra.data2d.get(rec_id)
    astra.algorithm.delete(alg_id)
    astra.data2d.delete(sino_id)
    astra.data2d.delete(rec_id)
    recon = recon[pad:ncol - pad, pad:ncol - pad]
    if ratio is not None:
        ncol0 = ncol - 2 * pad
        if ratio == 0.0:
            ratio = min(center, ncol0 - center) / (0.5 * ncol0)
        mask = util.make_circle_mask(ncol0, ratio)
        recon = recon * mask
    return recon
Beispiel #4
0
def gridrec_reconstruction(sinogram,
                           center,
                           angles=None,
                           ratio=1.0,
                           filter_name="shepp",
                           apply_log=True,
                           pad=True,
                           ncore=1):
    """
    Wrapper of the gridrec method implemented in the tomopy package:
    https://tomopy.readthedocs.io/en/latest/api/tomopy.recon.algorithm.html
    Users must install Tomopy before using this function.

    Parameters
    ----------
    sinogram : array_like
        2D array. Sinogram image.
    center : float
        Center of rotation.
    angles : array_like
        1D array. List of angles (radian) corresponding to the sinogram.
    ratio : float
        To apply a circle mask to the reconstructed image.
    filter_name : str
        Apply a smoothing filter. Full list is at:
        https://github.com/tomopy/tomopy/blob/master/source/tomopy/recon/algorithm.py
    apply_log : bool
        Apply the logarithm function to the sinogram before reconstruction.
    pad : bool
        Apply edge padding to the nearest power of 2.

    Returns
    -------
    array_like
        Square array.
    """
    try:
        import tomopy
    except ImportError:
        print("!!!!!! Error !!!!!!!")
        print("You must install Tomopy before using this function!")
        raise
    pad_left = 0
    ncol = sinogram.shape[-1]
    if isinstance(pad, bool):
        if pad is True:
            ncol_pad = int(2**np.ceil(np.log2(1.0 * ncol)))
            pad_left = (ncol_pad - ncol) // 2
            pad_right = ncol_pad - ncol - pad_left
            sinogram = np.pad(sinogram, ((0, 0), (pad_left, pad_right)),
                              mode='edge')
    else:
        pad_left = pad
        sinogram = np.pad(sinogram, ((0, 0), (pad, pad)), mode='edge')
    if apply_log is True:
        sinogram = -np.log(sinogram)
    if filter_name is None:
        filter_name = "shepp"
    if angles is None:
        angles = np.linspace(0.0, 180.0, sinogram.shape[0]) * np.pi / 180.0
    recon = tomopy.recon(np.expand_dims(sinogram, 1),
                         angles,
                         center=center + pad_left,
                         algorithm='gridrec',
                         filter_name=filter_name,
                         ncore=ncore)[0]
    recon = recon[pad_left:pad_left + ncol, pad_left:pad_left + ncol]
    if ratio is not None:
        if ratio == 0.0:
            ratio = min(center, ncol - center) / (0.5 * ncol)
        mask = util.make_circle_mask(ncol, ratio)
        recon = recon * mask
    return recon
Beispiel #5
0
def dfi_reconstruction(sinogram,
                       center,
                       angles=None,
                       ratio=1.0,
                       filter_name="hann",
                       pad_rate=0.25,
                       pad_mode="edge",
                       apply_log=True):
    """
    Apply the DFI (direct Fourier inversion) reconstruction method to a
    sinogram image (Ref. [1]_). The method is a practical and direct
    implementation of the Fourier slice theorem (Ref. [2]_).

    Parameters
    ----------
    sinogram : array_like
        2D array. Sinogram image.
    center : float
        Center of rotation.
    angles : array_like
        1D array. List of angles (in radian) corresponding to the sinogram.
    ratio : float
        To apply a circle mask to the reconstructed image.
    filter_name : {None, "hann", "bartlett", "blackman", "hamming", "nuttall",\\
                  "parzen", "triang"}
        Apply a smoothing filter.
    pad_rate : float
        To apply padding before the FFT. The padding width equals to
        (pad_rate * image_width).
    pad_mode : str
        Padding method. Full list can be found at numpy.pad documentation.
    apply_log : bool
        Apply the logarithm function to the sinogram before reconstruction.

    Returns
    -------
    array_like
        Square array. Reconstructed image.

    References
    ----------
    .. [1] https://doi.org/10.1364/OE.418448
    .. [2] https://doi.org/10.1071/PH560198
    """
    if apply_log is True:
        sinogram = -np.log(sinogram)
    (nrow, ncol) = sinogram.shape
    if ncol % 2 == 0:
        sinogram = np.pad(sinogram, ((0, 0), (0, 1)), mode="edge")
    ncol1 = sinogram.shape[1]
    xshift = (ncol1 - 1) / 2.0 - center
    sinogram = shift(sinogram, (0, xshift), mode='nearest')
    if angles is not None:
        t_ang = np.sum(np.abs(np.diff(angles * 180.0 / np.pi)))
        if abs(t_ang - 360) < 10:
            nrow = nrow // 2 + 1
            sinogram = (sinogram[:nrow] + np.fliplr(sinogram[-nrow:])) / 2
        step = np.mean(np.abs(np.diff(angles)))
        b_ang = angles[0] - (angles[0] // (2 * np.pi)) * (2 * np.pi)
        sino_360 = np.vstack((sinogram[:nrow - 1], np.fliplr(sinogram)))
        sinogram = shift(sino_360, (b_ang / step, 0), mode='wrap')[:nrow]
        if angles[-1] < angles[0]:
            sinogram = np.flipud(np.fliplr(sinogram))
    num_pad = int(pad_rate * ncol1)
    sinogram = np.pad(sinogram, ((0, 0), (num_pad, num_pad)), mode=pad_mode)
    ncol2 = sinogram.shape[1]
    mask = util.make_circle_mask(ncol2, 1.0)
    (r_mat, theta_mat) = generate_mapping_coordinate(ncol2, nrow, ncol2, ncol2)
    sino_fft = fft.fftshift(fft.fft(fft.ifftshift(sinogram, axes=1)), axes=1)
    if filter_name is not None:
        window = make_smoothing_window(filter_name, ncol2)
        sino_fft = sino_fft * np.tile(window, (nrow, 1))
    mat_real = np.real(sino_fft)
    mat_imag = np.imag(sino_fft)
    reg_real = util.mapping(
        mat_real, r_mat, theta_mat, order=5, mode="reflect") * mask
    reg_imag = util.mapping(
        mat_imag, r_mat, theta_mat, order=5, mode="reflect") * mask
    recon = np.real(
        fft.fftshift(fft.ifft2(
            fft.ifftshift(reg_real + 1j * reg_imag))))[num_pad:ncol + num_pad,
                                                       num_pad:ncol + num_pad]
    if ratio is not None:
        if ratio == 0.0:
            ratio = min(center, ncol - center) / (0.5 * ncol)
        mask = util.make_circle_mask(ncol, ratio)
        recon = recon * mask
    return recon
Beispiel #6
0
def fbp_reconstruction(sinogram,
                       center,
                       angles=None,
                       ratio=1.0,
                       ramp_win=None,
                       filter_name="hann",
                       pad=None,
                       pad_mode="edge",
                       apply_log=True,
                       gpu=True):
    """
    Apply the FBP (filtered back-projection) reconstruction method to a
    sinogram image.

    Parameters
    ----------
    sinogram : array_like
        2D array. Sinogram image.
    center : float
        Center of rotation.
    angles : array_like, optional
        1D array. List of angles (in radian) corresponding to the sinogram.
    ratio : float, optional
        To apply a circle mask to the reconstructed image.
    ramp_win : complex ndarray, optional
        Ramp window in the Fourier space. It will be generated if None is given.
    filter_name : {None, "hann", "bartlett", "blackman", "hamming", "nuttall",\\
                  "parzen", "triang"}
        Apply a smoothing filter.
    pad : int, optional
        To apply padding before the FFT. The value is set to 10% of the image
        width if None is given.
    pad_mode : str, optional
        Padding method. Full list can be found at numpy.pad documentation.
    apply_log : bool, optional
        Apply the logarithm function to the sinogram before reconstruction.
    gpu : bool, optional
        Use GPU for computing if True.

    Returns
    -------
    array_like
        Square array. Reconstructed image.
    """
    if gpu is True:
        if cuda.is_available() is False:
            print("!!! No Nvidia GPU found !!! Run with CPU instead !!!")
            gpu = False
    if apply_log is True:
        sinogram = -np.log(sinogram)
    (nrow, ncol) = sinogram.shape
    if angles is None:
        angles = np.linspace(0.0, 180.0, nrow) * np.pi / 180.0
    else:
        num_pro = len(angles)
        if num_pro != nrow:
            msg = "!!!Number of angles is not the same as the row number of " \
                  "the sinogram!!!"
            raise ValueError(msg)
    xlist = np.float32(np.arange(0.0, ncol) - center)
    sino_filtered = apply_ramp_filter(sinogram, ramp_win, filter_name, pad,
                                      pad_mode)
    recon = np.zeros((ncol, ncol), dtype=np.float32)
    if gpu is True:
        fbp_block = (16, 16)
        fbp_grid = (int(np.ceil(1.0 * ncol / fbp_block[0])),
                    int(np.ceil(1.0 * ncol / fbp_block[1])))
        back_projection_gpu[fbp_grid, fbp_block](recon,
                                                 np.float32(sino_filtered),
                                                 np.float32(angles), xlist,
                                                 np.float32(center),
                                                 np.int32(nrow),
                                                 np.int32(ncol))
    else:
        recon = back_projection_cpu(np.float32(sino_filtered),
                                    np.float32(angles), np.float32(xlist),
                                    np.float32(center))
    if ratio is not None:
        if ratio == 0.0:
            ratio = min(center, ncol - center) / (0.5 * ncol)
        mask = util.make_circle_mask(ncol, ratio)
        recon = recon * mask
    return recon * np.pi / (nrow - 1)