Example #1
0
def make_sinogram(mat, angles, pad_rate=0.5, pad_mode="edge"):
    """
    Create a sinogram (series of 1D projections) from a 2D image based on the
    Fourier slice theorem (Ref. [1]).

    Parameters
    ----------
    mat : array_like
        Square array.
    angles : array_like
        1D array. List of angles (in radian) for projecting.
    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.

    References
    ----------
    .. [1] https://doi.org/10.1071/PH560198
    """
    (nrow0, ncol0) = mat.shape
    if nrow0 != ncol0:
        raise ValueError("Width and height of the image are not the same")
    if np.max(np.abs(angles)) > np.pi:
        print("!!! Warning !!!\nMaking sure that the angles are converted to "
              "Radian and in the range of [0; Pi]")
    pad = int(pad_rate * nrow0)
    mat_pad = np.pad(mat, pad, mode=pad_mode)
    if mat_pad.shape[0] % 2 == 0:
        mat_pad = np.pad(mat_pad, ((0, 1), (0, 1)), mode="edge")
    (nrow, ncol) = mat_pad.shape
    xcenter = (ncol - 1.0) * 0.5
    ycenter = (nrow - 1.0) * 0.5
    r_max = np.floor(max(xcenter, ycenter))
    r_list = np.linspace(-r_max, r_max, ncol)
    theta_list = -np.asarray(angles)
    r_mat, theta_mat = np.meshgrid(r_list, theta_list)
    x_mat = np.float32(
        np.clip(xcenter + r_mat * np.cos(theta_mat), 0, ncol - 1))
    y_mat = np.float32(
        np.clip(ycenter + r_mat * np.sin(theta_mat), 0, nrow - 1))
    mat_fft = fft.fftshift(fft.fft2(fft.ifftshift(mat_pad)))
    mat_real = np.real(mat_fft)
    mat_imag = np.imag(mat_fft)
    sino_real = util.mapping(mat_real, x_mat, y_mat, order=5, mode="reflect")
    sino_imag = util.mapping(mat_imag, x_mat, y_mat, order=5, mode="reflect")
    sinogram = np.real(
        fft.fftshift(fft.ifft(fft.ifftshift(sino_real + 1j * sino_imag,
                                            axes=1)),
                     axes=1))
    return sinogram[:, pad:ncol0 + pad]
Example #2
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
Example #3
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
Example #4
0
def generate_tilted_sinogram_chunk(data, start_index, stop_index, angle,
                                   **option):
    """
    Generate a chunk of tilted sinograms of a 3D tomographic dataset or a
    hdf/nxs object.

    Parameters
    ----------
    data : array_like or hdf object
        3D array.
    start_index : int
        Starting index of sinograms.
    stop_index : int
        Stopping index of sinograms.
    angle : float
        Tilted angle in degree.
    option : list or tuple of int
        To extract subset data along axis 0 from a hdf object. E.g option =
        (start, stop, step)

    Returns
    -------
    array_like
        3D array. Chunk of tilted sinograms.
    """
    if data.ndim != 3:
        raise ValueError("Input must be a 3D data !!!")
    (depth, height, width) = data.shape
    if len(option) != 0:
        opt = option[list(option.keys())[0]]
        start, stop, step = opt
    else:
        start, stop, step = 0, depth, 1
    list_idx = range(start, stop, step)
    x_cen = (width - 1.0) / 2
    y_cen = (height - 1.0) / 2
    angle = angle * np.pi / 180.0
    x_list = np.arange(0, width) - x_cen
    y_list = np.arange(start_index, stop_index + 1) - y_cen
    x_mat, y_mat = np.meshgrid(x_list, y_list)
    x_mat1 = x_mat * np.cos(angle) - y_mat * np.sin(angle)
    y_mat1 = x_mat * np.sin(angle) + y_mat * np.cos(angle)
    x_mat1 = np.clip(x_cen + x_mat1, 0, width - 1)
    y_mat1 = np.clip(y_cen + y_mat1, 0, height - 1)
    y_min = np.int16(np.floor(np.amin(y_mat1)))
    y_max = np.int16(np.ceil(np.amax(y_mat1))) + 1
    y_mat1 = y_mat1 - y_min
    sino_chunk = np.asarray([
        util.mapping(data[i, y_min:y_max, :], x_mat1, y_mat1) for i in list_idx
    ])
    return sino_chunk
Example #5
0
def generate_tilted_profile_chunk(mat, start_index, stop_index, angle):
    """
    Generate a chunk of tilted horizontal intensity-profiles of an image.

    Parameters
    ----------
    mat : array_like
        2D array.
    start_index : int
        Starting index of lines.
    stop_index : int
        Stopping index of lines.
    angle : float
        Tilted angle in degree.

    Returns
    -------
    array_like
        2D array.
    """
    if mat.ndim != 2:
        raise ValueError("Input must be a 2D data !!!")
    (height, width) = mat.shape
    x_cen = (width - 1.0) / 2
    y_cen = (height - 1.0) / 2
    angle = angle * np.pi / 180.0
    x_list = np.arange(0, width) - x_cen
    y_list = np.arange(start_index, stop_index + 1) - y_cen
    x_mat, y_mat = np.meshgrid(x_list, y_list)
    x_mat1 = x_mat * np.cos(angle) - y_mat * np.sin(angle)
    y_mat1 = x_mat * np.sin(angle) + y_mat * np.cos(angle)
    x_mat1 = np.clip(x_cen + x_mat1, 0, width - 1)
    y_mat1 = np.clip(y_cen + y_mat1, 0, height - 1)
    y_min = np.int16(np.floor(np.amin(y_mat1)))
    y_max = np.int16(np.ceil(np.amax(y_mat1))) + 1
    y_mat1 = y_mat1 - y_min
    profile_chunk = util.mapping(mat[y_min:y_max, :], x_mat1, y_mat1)
    return profile_chunk
Example #6
0
def unwarp_sinogram_chunk(data, start_index, stop_index, xcenter, ycenter,
                          list_fact, **option):
    """
    Unwarp chunk of sinograms [:, start_index: stop_index, :]
    of a 3D tomographic dataset or a hdf/nxs object.

    Parameters
    ----------
    data : array_like or hdf object
        3D array.
    start_index : int
        Starting index of sinograms.
    stop_index : int
        Stopping index of sinograms.
    xcenter : float
        Center of distortion in x-direction.
    ycenter : float
        Center of distortion in y-direction.
    list_fact : list of float
        Polynomial coefficients of the backward model.
    option : list or tuple of int
        To extract subset data along axis 0 from a hdf object. E.g option =
        [start, stop, step]

    Returns
    -------
    array_like
        3D array. Distortion corrected.
    """
    if data.ndim != 3:
        raise ValueError("Input must be a 3D data !!!")
    (depth, height, width) = data.shape
    if len(option) != 0:
        opt = option[list(option.keys())[0]]
        start, stop, step = opt
    else:
        start, stop, step = 0, depth, 1
    list_idx = range(start, stop, step)
    if stop_index == -1:
        stop_index = height
    xu_list = np.arange(0, width) - xcenter
    yu1 = start_index - ycenter
    ru_list = np.sqrt(xu_list**2 + yu1**2)
    flist = np.sum(np.asarray(
        [factor * ru_list**i for i, factor in enumerate(list_fact)]),
                   axis=0)
    yd_list1 = np.clip(ycenter + flist * yu1, 0, height - 1)
    yu2 = stop_index - ycenter
    ru_list = np.sqrt(xu_list**2 + yu2**2)
    flist = np.sum(np.asarray(
        [factor * ru_list**i for i, factor in enumerate(list_fact)]),
                   axis=0)
    yd_list2 = np.clip(ycenter + flist * yu2, 0, height - 1)
    yd_min = np.int16(np.floor(np.amin(yd_list1)))
    yd_max = np.int16(np.ceil(np.amax(yd_list2)))
    yu_list = np.arange(start_index, stop_index) - ycenter
    xu_mat, yu_mat = np.meshgrid(xu_list, yu_list)
    ru_mat = np.sqrt(xu_mat**2 + yu_mat**2)
    fact_mat = np.sum(np.asarray(
        [factor * ru_mat**i for i, factor in enumerate(list_fact)]),
                      axis=0)
    xd_mat = np.float32(np.clip(xcenter + fact_mat * xu_mat, 0, width - 1))
    yd_mat = np.float32(np.clip(ycenter + fact_mat * yu_mat, 0,
                                height - 1)) - yd_min
    sino_chunk = np.asarray([
        util.mapping(data[i, yd_min:yd_max, :], xd_mat, yd_mat)
        for i in list_idx
    ])
    return sino_chunk
Example #7
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