Beispiel #1
0
def normalize_bg(tomo, air=1, ncore=None, nchunk=None):
    """
    Normalize 3D tomgraphy data based on background intensity.

    Weight sinogram such that the left and right image boundaries
    (i.e., typically the air region around the object) are set to one
    and all intermediate values are scaled linearly.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    air : int, optional
        Number of pixels at each boundary to calculate the scaling factor.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    tomo = dtype.as_float32(tomo)
    air = dtype.as_int32(air)
    dx, dy, dz = tomo.shape

    arr = mproc.distribute_jobs(tomo,
                                func=extern.c_normalize_bg,
                                args=(dx, dy, dz, air),
                                axis=0,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr
Beispiel #2
0
def remove_stripe_based_fitting(
        tomo, order=3, sigma=(5, 20), ncore=None, nchunk=None):
    """
    Remove horizontal stripes from sinogram using Nghia Vo's
    approach :cite:'Vo:18'
    Algorithm 1 in the paper. Remove stripes using the fitting technique.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    order : int
        Polynomial fit order.
    sigma : tuple of 2 floats
        Sigmas of a 2D Gaussian window in x and y direction.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_based_fitting,
        args=(order, sigma),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #3
0
def remove_all_stripe(tomo, snr=3, la_size=61, sm_size=21, ncore=None, nchunk=None):
    """
    Remove stripe artifacts from sinogram using Nghia Vo's
    approach :cite:`Vo:18`
    Combine algorithms 6,5,4,3 to remove all types of stripes.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    snr  : float
        Ratio used to locate large stripes.
        Greater is less sensitive. 
    la_size : int
        Window size of the median filter to remove large stripes.
    sm_size : int
        Window size of the median filter to remove small-to-medium stripes.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_all_stripe,
        args=(snr, la_size, sm_size),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #4
0
def remove_dead_stripe(tomo, snr=3, size=51, ncore=None, nchunk=None):
    """
    Remove stripe artifacts from sinogram using Nghia Vo's
    approach :cite:`Vo:18`
    Algorithm 6 in the paper. Remove unresponsive and fluctuating stripes.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    snr  : float
        Ratio used to detect locations of large stripes.
        Greater is less sensitive. 
    size : int
        Window size of the median filter.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_dead_stripe,
        args=(snr, size),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #5
0
def remove_stripe_based_fitting(
        tomo, order=3, sigma=(5, 20), ncore=None, nchunk=None):
    """
    Remove horizontal stripes from sinogram using Nghia Vo's
    approach :cite:`Vo:18`
    Algorithm 1 in the paper. Remove stripes using the fitting technique.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    order : int
        Polynomial fit order.
    sigma : tuple of 2 floats
        Sigmas of a 2D Gaussian window in x and y direction.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_based_fitting,
        args=(order, sigma),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #6
0
def remove_stripe_based_filtering(
        tomo, sigma=3, size=None, ncore=None, nchunk=None):
    """
    Remove stripe artifacts from sinogram using Nghia Vo's
    approach :cite:`Vo:18`
    Algorithm 2 in the paper. Remove stripes using the filtering technique.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    sigma : float
        Sigma of the Gaussian window which is used to separate 
        the low-pass and high-pass components of the intensity
        profiles of each column.
    size : int
        Window size of the median filter.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_based_filtering,
        args=(sigma, size),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #7
0
def remove_stripe_based_filtering(
        tomo, sigma=3, size=None, ncore=None, nchunk=None):
    """
    Remove stripe artifacts from sinogram using Nghia Vo's
    approach :cite:'Vo:18'
    Algorithm 2 in the paper. Remove stripes using the filtering technique.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    sigma : float
        Sigma of the Gaussian window which is used to separate 
        the low-pass and high-pass components of the intensity
        profiles of each column.
    size : int
        Window size of the median filter.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_based_filtering,
        args=(sigma, size),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #8
0
def remove_all_stripe(tomo, snr=3, la_size=61, sm_size=21, ncore=None, nchunk=None):
    """
    Remove stripe artifacts from sinogram using Nghia Vo's
    approach :cite:'Vo:18'
    Combine algorithms 6,5,4,3 to remove all types of stripes.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    snr  : float
        Ratio used to locate large stripes.
        Greater is less sensitive. 
    la_size : int
        Window size of the median filter to remove large stripes.
    sm_size : int
        Window size of the median filter to remove small-to-medium stripes.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_all_stripe,
        args=(snr, la_size, sm_size),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #9
0
def remove_stripe_sf(tomo, size=5, ncore=None, nchunk=None):
    """
    Normalize raw projection data using a smoothing filter approach.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    size : int, optional
        Size of the smoothing filter.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    tomo = dtype.as_float32(tomo)
    arr = mproc.distribute_jobs(
        tomo,
        func=extern.c_remove_stripe_sf,
        args=(size,),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #10
0
def remove_stripe_ti(tomo, nblock=0, alpha=1.5, ncore=None, nchunk=None):
    """
    Remove horizontal stripes from sinogram using Titarenko's
    approach :cite:`Miqueles:14`.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    nblock : int, optional
        Number of blocks.
    alpha : int, optional
        Damping factor.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_ti,
        args=(nblock, alpha),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #11
0
def normalize_roi(arr, roi=[0, 0, 10, 10], ncore=None):
    """
    Normalize raw projection data using an average of a selected window
    on projection images.

    Parameters
    ----------
    arr : ndarray
        3D tomographic data.
    roi: list of int, optional
        [top-left, top-right, bottom-left, bottom-right] pixel coordinates.
    ncore : int, optional
        Number of cores that will be assigned to jobs.

    Returns
    -------
    ndarray
        Normalized 3D tomographic data.
    """
    arr = dtype.as_float32(arr)

    arr = mproc.distribute_jobs(arr,
                                func=_normalize_roi,
                                args=(roi, ),
                                axis=0,
                                ncore=ncore,
                                nchunk=0)
    return arr
Beispiel #12
0
def remove_outlier(arr, dif, size=3, axis=0, ncore=None, nchunk=None):
    """
    Remove high intensity bright spots from a 3D array along specified
    dimension.

    Parameters
    ----------
    arr : ndarray
        Input array.
    dif : float
        Expected difference value between outlier value and
        the median value of the array.
    size : int
        Size of the median filter.
    axis : int, optional
        Axis along which median filtering is performed.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
       Corrected array.
    """
    arr = dtype.as_float32(arr)
    arr = mproc.distribute_jobs(arr.swapaxes(0, axis),
                                func=_remove_outlier,
                                args=(dif, size),
                                axis=0,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr.swapaxes(0, axis)
Beispiel #13
0
def remove_stripe_based_sorting(tomo,
                                size=None,
                                dim=1,
                                ncore=None,
                                nchunk=None):
    """
    Remove full and partial stripe artifacts from sinogram using Nghia Vo's
    approach :cite:`Vo:18` (algorithm 3).
    Suitable for removing partial stripes.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    size : int
        Window size of the median filter.
    dim : {1, 2}, optional
        Dimension of the window.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(tomo,
                                func=_remove_stripe_based_sorting,
                                args=(size, dim),
                                axis=1,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr
Beispiel #14
0
def remove_stripe_sf(tomo, size=5, ncore=None, nchunk=None):
    """
    Normalize raw projection data using a smoothing filter approach.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    size : int, optional
        Size of the smoothing filter.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    tomo = dtype.as_float32(tomo)
    dx, dy, dz = tomo.shape
    arr = mproc.distribute_jobs(
        tomo,
        func=extern.c_remove_stripe_sf,
        args=(dx, dy, dz, size),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #15
0
def _search_coarse(sino, smin, smax, ratio, drop, ncore=None):
    """
    Coarse search for finding the rotation center.
    """
    (nrow, ncol) = sino.shape
    cen_fliplr = (ncol - 1.0) / 2.0
    smin = np.int16(np.clip(smin + cen_fliplr, 0, ncol - 1) - cen_fliplr)
    smax = np.int16(np.clip(smax + cen_fliplr, 0, ncol - 1) - cen_fliplr)
    start_cor = ncol // 2 + smin
    stop_cor = ncol // 2 + smax
    flip_sino = np.fliplr(sino)
    comp_sino = np.flipud(sino)  # Used to avoid local minima
    list_cor = np.arange(start_cor, stop_cor + 0.5, 0.5)
    list_metric = np.zeros(len(list_cor), dtype=np.float32)
    mask = _create_mask(2 * nrow, ncol, 0.5 * ratio * ncol, drop)
    list_shift = 2.0 * (list_cor - cen_fliplr)
    list_metric = distribute_jobs(np.float32(list_shift),
                                  _calculate_metric,
                                  axis=0,
                                  args=(sino, flip_sino, comp_sino, mask),
                                  ncore=ncore,
                                  nchunk=1)
    minpos = np.argmin(list_metric)
    if minpos == 0:
        logger.debug('WARNING!!!Global minimum is out of searching range')
        logger.debug('Please extend smin: %i', smin)
    if minpos == len(list_metric) - 1:
        logger.debug('WARNING!!!Global minimum is out of searching range')
        logger.debug('Please extend smax: %i', smax)
    cor = list_cor[minpos]
    return cor
Beispiel #16
0
def remove_stripe_based_sorting(tomo, size=None, ncore=None, nchunk=None):
    """
    Remove stripe artifacts from sinogram using Nghia Vo's
    approach :cite:`Vo:18`
    Algorithm 3 in the paper. Remove stripes using the sorting technique.
    Work particularly well for removing partial stripes.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    size : int
        Window size of the median filter.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_based_sorting,
        args=(size,),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #17
0
def distortion_correction_proj(tomo, xcenter, ycenter, list_fact,
                                ncore=None, nchunk=None):
    """
    Apply distortion correction to projections using the polynomial model.
    Coefficients are calculated using Vounwarp package :cite:`Vo:15`.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    xcenter : float
        Center of distortion in x-direction. From the left of the image.
    ycenter : float
        Center of distortion in y-direction. From the top of the image. 
    list_fact : list of floats
        Polynomial coefficients of the backward model.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_distortion_correction_proj,
        args=(xcenter, ycenter, list_fact),
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #18
0
def normalize_roi(tomo, roi=[0, 0, 10, 10], ncore=None, nchunk=None):
    """
    Normalize raw projection data using an average of a selected window
    on projection images.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    roi: list of int, optional
        [top-left, top-right, bottom-left, bottom-right] pixel coordinates.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Normalized 3D tomographic data.
    """
    tomo = dtype.as_float32(tomo)

    arr = mproc.distribute_jobs(
        tomo,
        func=_normalize_roi,
        args=(roi, ),
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #19
0
def median_filter(arr, size=3, axis=0, ncore=None):
    """
    Apply median filter to 3D array along specified axis.

    Parameters
    ----------
    arr : ndarray
        Input array.
    size : int, optional
        The size of the filter.
    axis : int, optional
        Axis along which median filtering is performed.
    ncore : int, optional
        Number of cores that will be assigned to jobs.

    Returns
    -------
    ndarray
        Median filtered 3D array.
    """
    arr = dtype.as_float32(arr)
    arr = mproc.distribute_jobs(
        arr,
        func=filters.median_filter,
        args=((size, size),),
        axis=axis,
        ncore=ncore,
        nchunk=0)
    return arr
Beispiel #20
0
def normalize_bg(tomo, air=1, ncore=None, nchunk=None):
    """
    Normalize 3D tomgraphy data based on background intensity.

    Weight sinogram such that the left and right image boundaries
    (i.e., typically the air region around the object) are set to one
    and all intermediate values are scaled linearly.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    air : int, optional
        Number of pixels at each boundary to calculate the scaling factor.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    tomo = dtype.as_float32(tomo)
    air = dtype.as_int32(air)

    arr = mproc.distribute_jobs(
        tomo,
        func=extern.c_normalize_bg,
        args=(air,),
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #21
0
def remove_stripe_based_sorting(tomo, size=None, ncore=None, nchunk=None):
    """
    Remove stripe artifacts from sinogram using Nghia Vo's
    approach :cite:`Vo:18`
    Algorithm 3 in the paper. Remove stripes using the sorting technique.
    Work particularly well for removing partial stripes.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    size : int
        Window size of the median filter.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_based_sorting,
        args=(size,),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #22
0
def remove_stripe_ti(tomo, nblock=0, alpha=1.5, ncore=None, nchunk=None):
    """
    Remove horizontal stripes from sinogram using Titarenko's
    approach :cite:`Miqueles:14`.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    nblock : int, optional
        Number of blocks.
    alpha : int, optional
        Damping factor.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_ti,
        args=(nblock, alpha),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #23
0
def sobel_filter(arr, axis=0, ncore=None):
    """
    Apply Sobel filter to 3D array along specified axis.

    Parameters
    ----------
    arr : ndarray
        Input array.
    axis : int, optional
        Axis along which sobel filtering is performed.
    ncore : int, optional
        Number of cores that will be assigned to jobs.

    Returns
    -------
    ndarray
        3D array of same shape as input.
    """
    arr = dtype.as_float32(arr)
    arr = mproc.distribute_jobs(
        arr,
        func=filters.sobel,
        axis=axis,
        ncore=ncore,
        nchunk=0)
    return arr
Beispiel #24
0
def remove_outlier(arr, dif, size=3, axis=0, ncore=None):
    """
    Remove high intensity bright spots from a 3D array along specified
    dimension.

    Parameters
    ----------
    arr : ndarray
        Input array.
    dif : float
        Expected difference value between outlier value and
        the median value of the array.
    size : int
        Size of the median filter.
    axis : int, optional
        Axis along which median filtering is performed.
    ncore : int, optional
        Number of cores that will be assigned to jobs.

    Returns
    -------
    ndarray
       Corrected array.
    """
    arr = dtype.as_float32(arr)
    arr = mproc.distribute_jobs(
        arr,
        func=_remove_outlier_from_img,
        args=(dif, size),
        axis=axis,
        ncore=ncore,
        nchunk=0)
    return arr
Beispiel #25
0
def remove_dead_stripe(tomo, snr=3, size=51, ncore=None, nchunk=None):
    """
    Remove stripe artifacts from sinogram using Nghia Vo's
    approach :cite:'Vo:18'
    Algorithm 6 in the paper. Remove unresponsive and fluctuating stripes.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    snr  : float
        Ratio used to detect locations of large stripes.
        Greater is less sensitive. 
    size : int
        Window size of the median filter.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_dead_stripe,
        args=(snr, size),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #26
0
def sobel_filter(arr, axis=0, ncore=None, nchunk=None):
    """
    Apply Sobel filter to 3D array along specified axis.

    Parameters
    ----------
    arr : ndarray
        Input array.
    axis : int, optional
        Axis along which sobel filtering is performed.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        3D array of same shape as input.
    """
    arr = dtype.as_float32(arr)
    arr = mproc.distribute_jobs(arr.swapaxes(0, axis),
                                func=_sobel_filter,
                                axis=axis,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr.swapaxes(0, axis)
Beispiel #27
0
def median_filter(arr, size=3, axis=0, ncore=None, nchunk=None):
    """
    Apply median filter to 3D array along specified axis.

    Parameters
    ----------
    arr : ndarray
        Input array.
    size : int, optional
        The size of the filter.
    axis : int, optional
        Axis along which median filtering is performed.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Median filtered 3D array.
    """
    arr = dtype.as_float32(arr)
    arr = mproc.distribute_jobs(arr.swapaxes(0, axis),
                                func=_median_filter,
                                args=(size, ),
                                axis=axis,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr.swapaxes(0, axis)
Beispiel #28
0
def test_distribute_jobs():
    assert_allclose(
        mproc.distribute_jobs(
            np.zeros((8, 8, 8)),
            func=_synthetic_func,
            args=(1.,),
            axis=0),
        np.ones((8, 8, 8)))
Beispiel #29
0
 def _test_shape(self, a, expected_shape, axis=0, ncore=None, nchunk=None):
     ret = mproc.distribute_jobs(a,
                                 func=_test_shape,
                                 args=(expected_shape, ),
                                 axis=axis,
                                 ncore=ncore,
                                 nchunk=nchunk)
     assert_array_equal(a, ret)
Beispiel #30
0
 def test_distribute_jobs(self):
     assert_allclose(
         mproc.distribute_jobs(
             np.zeros((8, 8, 8)),
             func=_synthetic_func,
             args=(1.,),
             axis=0),
         np.ones((8, 8, 8)))
Beispiel #31
0
def project(obj, theta, center=None, emission=True, sinogram_order=False, ncore=None, nchunk=None):
    """
    Project x-rays through a given 3D object.

    Parameters
    ----------
    obj : ndarray
        Voxelized 3D object.
    theta : array
        Projection angles in radian.
    center: array, optional
        Location of rotation axis.
    emission : bool, optional
        Determines whether output data is emission or transmission type.
    sinogram_order: bool, optional
        Determins whether output data is a stack of sinograms (True, y-axis first axis) 
        or a stack of radiographs (False, theta first axis).
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        3D tomographic data.
    """
    obj = dtype.as_float32(obj)
    theta = dtype.as_float32(theta)

    # Estimate data dimensions.
    oy, ox, oz = obj.shape
    dt = theta.size
    dy = oy
    dx = _round_to_even(np.sqrt(ox * ox + oz * oz) + 2)
    shape = dy, dt, dx
    tomo = dtype.empty_shared_array(shape)
    tomo[:] = 0.0
    center = get_center(shape, center)

    tomo = mproc.distribute_jobs(
        (obj, center, tomo),
        func=extern.c_project,
        args=(theta,),
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
    # NOTE: returns sinogram order with emmission=True
    if not emission:
        # convert data to be transmission type
        np.exp(-tomo, tomo)
    if not sinogram_order:
        # rotate to radiograph order
        tomo = np.swapaxes(tomo, 0, 1) #doesn't copy data
        # copy data to sharedmem
        tomo = dtype.as_sharedmem(tomo, copy=True)
        
    return tomo
Beispiel #32
0
def retrieve_phase(tomo,
                   pixel_size=1e-4,
                   dist=50,
                   energy=20,
                   alpha=1e-3,
                   pad=True,
                   ncore=None,
                   nchunk=None):
    """
    Perform single-step phase retrieval from phase-contrast measurements
    :cite:`Paganin:02`.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    pixel_size : float, optional
        Detector pixel size in cm.
    dist : float, optional
        Propagation distance of the wavefront in cm.
    energy : float, optional
        Energy of incident wave in keV.
    alpha : float, optional
        Regularization parameter.
    pad : bool, optional
        If True, extend the size of the projections by padding with zeros.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Approximated 3D tomographic phase data.
    """
    # New dimensions and pad value after padding.
    py, pz, val = _calc_pad(tomo, pixel_size, dist, energy, pad)

    # Compute the reciprocal grid.
    dx, dy, dz = tomo.shape
    w2 = _reciprocal_grid(pixel_size, dy + 2 * py, dz + 2 * pz)

    # Filter in Fourier space.
    phase_filter = np.fft.fftshift(
        _paganin_filter_factor(energy, dist, alpha, w2))

    # Enable cache for FFTW.
    pyfftw.interfaces.cache.enable()

    prj = val * np.ones((dy + 2 * py, dz + 2 * pz), dtype='float32')
    arr = mproc.distribute_jobs(tomo,
                                func=_retrieve_phase,
                                args=(phase_filter, py, pz, prj, pad),
                                axis=0,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr
Beispiel #33
0
def _dist_recon(tomo, recon, algorithm, args, kwargs, ncore, nchunk):
    mproc.init_tomo(tomo)
    return mproc.distribute_jobs(recon,
                                 func=algorithm,
                                 args=args,
                                 kwargs=kwargs,
                                 axis=0,
                                 ncore=ncore,
                                 nchunk=nchunk)
Beispiel #34
0
 def _test_shape(self, a, expected_shape, axis=0, ncore=None, nchunk=None):
     ret = mproc.distribute_jobs(
         a,
         func=_test_shape,
         args=(expected_shape,),
         axis=axis,
         ncore=ncore,
         nchunk=nchunk)
     assert_array_equal(a, ret)
Beispiel #35
0
def _dist_recon(tomo, center, recon, algorithm, args, kwargs, ncore, nchunk):
    #assert tomo.flags.aligned
    return mproc.distribute_jobs((tomo, center, recon),
                                 func=algorithm,
                                 args=args,
                                 kwargs=kwargs,
                                 axis=0,
                                 ncore=ncore,
                                 nchunk=nchunk)
Beispiel #36
0
def remove_ring(rec, center_x=None, center_y=None, thresh=300.0,
                thresh_max=300.0, thresh_min=-100.0, theta_min=30,
                rwidth=30, ncore=None, nchunk=None):
    """
    Remove ring artifacts from images in the reconstructed domain.
    Descriptions of parameters need to be more clear for sure.

    Parameters
    ----------
    arr : ndarray
        Array of reconstruction data
    center_x : float, optional
        abscissa location of center of rotation
    center_y : float, optional
        ordinate location of center of rotation
    thresh : float, optional
        maximum value of an offset due to a ring artifact
    thresh_max : float, optional
        max value for portion of image to filter
    thresh_min : float, optional
        min value for portion of image to filer
    theta_min : int, optional
        minimum angle in degrees (int) to be considered ring artifact
    rwidth : int, optional
        Maximum width of the rings to be filtered in pixels
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected reconstruction data
    """

    rec = dtype.as_float32(rec)

    dz, dy, dx = rec.shape

    if center_x is None:
        center_x = (dx - 1.0)/2.0
    if center_y is None:
        center_y = (dy - 1.0)/2.0

    args = (center_x, center_y, dx, dy, dz, thresh_max, thresh_min,
            thresh, theta_min, rwidth)

    rec = mproc.distribute_jobs(
        rec,
        func=extern.c_remove_ring,
        args=args,
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
    return rec
Beispiel #37
0
def _dist_recon(tomo, center, recon, algorithm, args, kwargs, ncore, nchunk):
    #assert tomo.flags.aligned
    return mproc.distribute_jobs(
        (tomo, center, recon),
        func=algorithm,
        args=args,
        kwargs=kwargs,
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
Beispiel #38
0
def _dist_recon(tomo, recon, algorithm, args, kwargs, ncore, nchunk):
    mproc.init_tomo(tomo)
    return mproc.distribute_jobs(
        recon,
        func=algorithm,
        args=args,
        kwargs=kwargs,
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
Beispiel #39
0
def retrieve_phase(
        tomo, pixel_size=1e-4, dist=50, energy=20,
        alpha=1e-3, pad=True, ncore=None, nchunk=None):
    """
    Perform single-step phase retrieval from phase-contrast measurements
    :cite:`Paganin:02`.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    pixel_size : float, optional
        Detector pixel size in cm.
    dist : float, optional
        Propagation distance of the wavefront in cm.
    energy : float, optional
        Energy of incident wave in keV.
    alpha : float, optional
        Regularization parameter.
    pad : bool, optional
        If True, extend the size of the projections by padding with zeros.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Approximated 3D tomographic phase data.
    """
    # New dimensions and pad value after padding.
    py, pz, val = _calc_pad(tomo, pixel_size, dist, energy, pad)

    # Compute the reciprocal grid.
    dx, dy, dz = tomo.shape
    w2 = _reciprocal_grid(pixel_size, dy + 2 * py, dz + 2 * pz)

    # Filter in Fourier space.
    phase_filter = np.fft.fftshift(
        _paganin_filter_factor(energy, dist, alpha, w2))

    # Enable cache for FFTW.
    pyfftw.interfaces.cache.enable()
    pyfftw.interfaces.cache.set_keepalive_time(5)

    prj = val * np.ones((dy + 2 * py, dz + 2 * pz), dtype='float32')
    arr = mproc.distribute_jobs(
        tomo,
        func=_retrieve_phase,
        args=(phase_filter, py, pz, prj, pad),
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
    return arr
def fcorrect_as_pathlength_centerline(input_trans):
    """Corrects for the beam hardening, assuming we are in the ring plane.
    Input: transmission
    Output: sample pathlength in microns.
    """
    data_dtype = input_trans.dtype
    return_data = mproc.distribute_jobs(input_trans,
                                        centerline_spline,
                                        args=(),
                                        axis=1)
    return return_data
Beispiel #41
0
def remove_stripe_fw(tomo,
                     level=None,
                     wname='db5',
                     sigma=2,
                     pad=True,
                     ncore=None,
                     nchunk=None):
    """
    Remove horizontal stripes from sinogram using the Fourier-Wavelet (FW)
    based method :cite:`Munch:09`.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    level : int, optional
        Number of discrete wavelet transform levels.
    wname : str, optional
        Type of the wavelet filter. 'haar', 'db5', sym5', etc.
    sigma : float, optional
        Damping parameter in Fourier space.
    pad : bool, optional
        If True, extend the size of the sinogram by padding with zeros.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    if level is None:
        size = np.max(tomo.shape)
        level = int(np.ceil(np.log2(size)))

    # Enable cache for FFTW.
    pyfftw.interfaces.cache.enable()

    arr = mproc.distribute_jobs(tomo,
                                func=_remove_stripe_fw,
                                args=(level, wname, sigma, pad),
                                axis=1,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr
Beispiel #42
0
    def fcorrect_as_pathlength_centerline(self, input_trans):
        """Corrects for the beam hardening, assuming we are in the ring plane.

        Parameters
        ==========
        input_trans : np.ndarray
          transmission

        Returns
        =======
        pathlength : np.ndarray
          sample pathlength in microns.

        """
        data_dtype = input_trans.dtype
        pathlength = mproc.distribute_jobs(input_trans, self.centerline_spline, args=(), axis=1)
        return pathlength
Beispiel #43
0
def remove_stripe_fw(
        tomo, level=None, wname='db5', sigma=2,
        pad=True, ncore=None, nchunk=None):
    """
    Remove horizontal stripes from sinogram using the Fourier-Wavelet (FW)
    based method :cite:`Munch:09`.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    level : int, optional
        Number of discrete wavelet transform levels.
    wname : str, optional
        Type of the wavelet filter. 'haar', 'db5', sym5', etc.
    sigma : float, optional
        Damping parameter in Fourier space.
    pad : bool, optional
        If True, extend the size of the sinogram by padding with zeros.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    if level is None:
        size = np.max(tomo.shape)
        level = int(np.ceil(np.log2(size)))

    # Enable cache for FFTW.
    pyfftw.interfaces.cache.enable()

    arr = mproc.distribute_jobs(
        tomo,
        func=_remove_stripe_fw,
        args=(level, wname, sigma, pad),
        axis=1,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #44
0
def remove_stripe_based_interpolation(tomo,
                                      snr=3,
                                      size=31,
                                      drop_ratio=0.1,
                                      norm=True,
                                      ncore=None,
                                      nchunk=None):
    """
    Remove most types of stripe artifacts from sinograms based on
    interpolation. Derived from algorithm 4, 5, and 6 in :cite:`Vo:18`.

    .. versionadded:: 1.9

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    snr : float
        Ratio used to segment between useful information and noise.
    size : int
        Window size of the median filter used to detect stripes.
    drop_ratio : float, optional
        Ratio of pixels to be dropped, which is used to to reduce
        the possibility of the false detection of stripes.
    norm : bool, optional
        Apply normalization if True.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(tomo,
                                func=_remove_stripe_based_interpolation,
                                args=(snr, size, drop_ratio, norm),
                                axis=1,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr
Beispiel #45
0
def project(obj, theta, center=None, ncore=None, nchunk=None):
    """
    Project x-rays through a given 3D object.

    Parameters
    ----------
    obj : ndarray
        Voxelized 3D object.
    theta : array
        Projection angles in radian.
    center: array, optional
        Location of rotation axis.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        3D tomographic data.
    """
    obj = dtype.as_float32(obj)
    theta = dtype.as_float32(theta)

    # Estimate data dimensions.
    ox, oy, oz = obj.shape
    dx = theta.size
    dy = ox
    dz = np.ceil(np.sqrt(oy * oy + oz * oz)).astype('int')
    shape = dx, dy, dz
    tomo = np.zeros(shape, dtype='float32')
    center = get_center(shape, center)

    mproc.init_obj(obj)
    arr = mproc.distribute_jobs(
        tomo,
        func=extern.c_project,
        args=(ox, oy, oz, theta, center, dx, dy, dz),
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #46
0
def remove_large_stripe(tomo,
                        snr=3,
                        size=51,
                        drop_ratio=0.1,
                        norm=True,
                        ncore=None,
                        nchunk=None):
    """
    Remove large stripe artifacts from sinogram using Nghia Vo's
    approach :cite:`Vo:18` (algorithm 5).

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    snr  : float
        Ratio used to locate of large stripes.
        Greater is less sensitive. 
    size : int
        Window size of the median filter.
    drop_ratio : float, optional
        Ratio of pixels to be dropped, which is used to reduce the false
        detection of stripes.
    norm : bool, optional
        Apply normalization if True.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Corrected 3D tomographic data.
    """
    arr = mproc.distribute_jobs(tomo,
                                func=_remove_large_stripe,
                                args=(snr, size, drop_ratio, norm),
                                axis=1,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr
Beispiel #47
0
def project(obj, theta, center=None, ncore=None, nchunk=None):
    """
    Project x-rays through a given 3D object.

    Parameters
    ----------
    obj : ndarray
        Voxelized 3D object.
    theta : array
        Projection angles in radian.
    center: array, optional
        Location of rotation axis.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        3D tomographic data.
    """
    obj = dtype.as_float32(obj)
    theta = dtype.as_float32(theta)

    # Estimate data dimensions.
    ox, oy, oz = obj.shape
    dx = theta.size
    dy = ox
    dz = np.ceil(np.sqrt(oy * oy + oz * oz)).astype('int')
    shape = dx, dy, dz
    tomo = np.zeros(shape, dtype='float32')
    center = get_center(shape, center)

    mproc.init_obj(obj)
    arr = mproc.distribute_jobs(tomo,
                                func=extern.c_project,
                                args=(ox, oy, oz, theta, center, dx, dy, dz),
                                axis=0,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr
Beispiel #48
0
def normalize(arr, flat, dark, cutoff=None, ncore=None, out=None):
    """
    Normalize raw projection data using the flat and dark field projections.

    Parameters
    ----------
    arr : ndarray
        3D stack of projections.
    flat : ndarray
        3D flat field data.
    dark : ndarray
        3D dark field data.
    cutoff : float, optional
        Permitted maximum vaue for the normalized data.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    out : ndarray, optional
        Output array for result.  If same as arr, process will be done in-place.

    Returns
    -------
    ndarray
        Normalized 3D tomographic data.
    """
    arr = dtype.as_float32(arr)
    flat = dtype.as_float32(flat)
    dark = dtype.as_float32(dark)

    flat = flat.mean(axis=0)
    dark = dark.mean(axis=0)

    arr = mproc.distribute_jobs(
        arr,
        func=_normalize,
        args=(flat, dark, cutoff),
        axis=0,
        ncore=ncore,
        nchunk=0,
        out=out)
    return arr
Beispiel #49
0
def gaussian_filter(arr, sigma=3, order=0, axis=0, ncore=None, nchunk=None):
    """
    Apply Gaussian filter to 3D array along specified axis.

    Parameters
    ----------
    arr : ndarray
        Input array.
    sigma : scalar or sequence of scalars
        Standard deviation for Gaussian kernel. The standard deviations
        of the Gaussian filter are given for each axis as a sequence, or
        as a single number, in which case it is equal for all axes.
    order : {0, 1, 2, 3} or sequence from same set, optional
        Order of the filter along each axis is given as a sequence
        of integers, or as a single number. An order of 0 corresponds
        to convolution with a Gaussian kernel. An order of 1, 2, or 3
        corresponds to convolution with the first, second or third
        derivatives of a Gaussian. Higher order derivatives are not
        implemented
    axis : int, optional
        Axis along which median filtering is performed.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        3D array of same shape as input.
    """
    arr = dtype.as_float32(arr)
    arr = mproc.distribute_jobs(
        arr.swapaxes(0, axis),
        func=_gaussian_filter,
        args=(sigma, order),
        axis=axis,
        ncore=ncore,
        nchunk=nchunk)
    return arr.swapaxes(0, axis)
Beispiel #50
0
def _search_fine(sino, srad, step, init_cen, ratio, drop, ncore=None):
    """
    Fine search for finding the rotation center.
    """
    (nrow, ncol) = sino.shape
    cen_fliplr = (ncol - 1.0) / 2.0
    srad = np.clip(np.abs(srad), 1.0, ncol / 4.0)
    step = np.clip(np.abs(step), 0.1, srad)
    init_cen = np.clip(init_cen, srad, ncol - srad - 1)
    list_cor = init_cen + np.arange(-srad, srad + step, step)
    flip_sino = np.fliplr(sino)
    comp_sino = np.flipud(sino)
    mask = _create_mask(2 * nrow, ncol, 0.5 * ratio * ncol, drop)
    list_shift = 2.0 * (list_cor - cen_fliplr)
    list_metric = distribute_jobs(np.float32(list_shift),
                                  _calculate_metric,
                                  axis=0,
                                  args=(sino, flip_sino, comp_sino, mask),
                                  ncore=ncore,
                                  nchunk=1)
    cor = list_cor[np.argmin(list_metric)]
    return cor
Beispiel #51
0
def normalize(tomo, flat, dark, cutoff=None, ncore=None, nchunk=None):
    """
    Normalize raw projection data using the flat and dark field projections.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    flat : ndarray
        3D flat field data.
    dark : ndarray
        3D dark field data.
    cutoff : float, optional
        Permitted maximum vaue for the normalized data.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Normalized 3D tomographic data.
    """
    tomo = dtype.as_float32(tomo)
    flat = dtype.as_float32(flat)
    dark = dtype.as_float32(dark)

    flat = flat.mean(axis=0)
    dark = dark.mean(axis=0)

    arr = mproc.distribute_jobs(
        tomo,
        func=_normalize,
        args=(flat, dark, cutoff),
        axis=0,
        ncore=ncore,
        nchunk=nchunk)
    return arr
Beispiel #52
0
def normalize(arr, flat, dark, cutoff=None, ncore=None, out=None):
    """
    Normalize raw projection data using the flat and dark field projections.

    Parameters
    ----------
    arr : ndarray
        3D stack of projections.
    flat : ndarray
        3D flat field data.
    dark : ndarray
        3D dark field data.
    cutoff : float, optional
        Permitted maximum vaue for the normalized data.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    out : ndarray, optional
        Output array for result.  If same as arr, process will be done in-place.

    Returns
    -------
    ndarray
        Normalized 3D tomographic data.
    """
    arr = dtype.as_float32(arr)
    flat = dtype.as_float32(flat)
    dark = dtype.as_float32(dark)

    flat = flat.mean(axis=0)
    dark = dark.mean(axis=0)

    arr = mproc.distribute_jobs(arr,
                                func=_normalize,
                                args=(flat, dark, cutoff),
                                axis=0,
                                ncore=ncore,
                                nchunk=0,
                                out=out)
    return arr
Beispiel #53
0
def gaussian_filter(arr, sigma=3, order=0, axis=0, ncore=None, nchunk=None):
    """
    Apply Gaussian filter to 3D array along specified axis.

    Parameters
    ----------
    arr : ndarray
        Input array.
    sigma : scalar or sequence of scalars
        Standard deviation for Gaussian kernel. The standard deviations
        of the Gaussian filter are given for each axis as a sequence, or
        as a single number, in which case it is equal for all axes.
    order : {0, 1, 2, 3} or sequence from same set, optional
        Order of the filter along each axis is given as a sequence
        of integers, or as a single number. An order of 0 corresponds
        to convolution with a Gaussian kernel. An order of 1, 2, or 3
        corresponds to convolution with the first, second or third
        derivatives of a Gaussian. Higher order derivatives are not
        implemented
    axis : int, optional
        Axis along which median filtering is performed.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        3D array of same shape as input.
    """
    arr = dtype.as_float32(arr)
    arr = mproc.distribute_jobs(arr.swapaxes(0, axis),
                                func=_gaussian_filter,
                                args=(sigma, order),
                                axis=axis,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr.swapaxes(0, axis)
Beispiel #54
0
def normalize(tomo, flat, dark, cutoff=None, ncore=None, nchunk=None):
    """
    Normalize raw projection data using the flat and dark field projections.

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    flat : ndarray
        3D flat field data.
    dark : ndarray
        3D dark field data.
    cutoff : float, optional
        Permitted maximum vaue for the normalized data.
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Normalized 3D tomographic data.
    """
    tomo = dtype.as_float32(tomo)
    flat = dtype.as_float32(flat)
    dark = dtype.as_float32(dark)

    flat = flat.mean(axis=0)
    dark = dark.mean(axis=0)

    arr = mproc.distribute_jobs(tomo,
                                func=_normalize,
                                args=(flat, dark, cutoff),
                                axis=0,
                                ncore=ncore,
                                nchunk=nchunk)
    return arr
Beispiel #55
0
def normalize_nf(tomo, flats, dark, flat_loc,
                 cutoff=None, ncore=None, nchunk=None):
    """
    Normalize raw 3D projection data with flats taken more than once during
    tomography. Normalization for each projection is done with the mean of the
    nearest set of flat fields (nearest flat fields).

    Parameters
    ----------
    tomo : ndarray
        3D tomographic data.
    flats : ndarray
        3D flat field data.
    dark : ndarray
        3D dark field data.
    flat_loc : list of int
        Indices of flat field data within tomography
    ncore : int, optional
        Number of cores that will be assigned to jobs.
    nchunk : int, optional
        Chunk size for each core.

    Returns
    -------
    ndarray
        Normalized 3D tomographic data.
    """

    tomo = dtype.as_float32(tomo)
    flats = dtype.as_float32(flats)
    dark = dtype.as_float32(dark)

    arr = np.zeros_like(tomo)

    dark = np.median(dark, axis=0)

    num_flats = len(flat_loc)
    total_flats = flats.shape[0]
    total_tomo = tomo.shape[0]

    num_per_flat = total_flats//num_flats
    tend = 0

    for m, loc in enumerate(flat_loc):
        fstart = m*num_per_flat
        fend = (m + 1)*num_per_flat
        flat = np.median(flats[fstart:fend], axis=0)

        # Normalization can be parallelized much more efficiently outside this
        # foor loop accounting for the nested parallelism arising from
        # chunking the total normalization and each chunked normalization
        tstart = 0 if m == 0 else tend
        tend = total_tomo if m >= num_flats-1 else (flat_loc[m+1]-loc)//2 + loc

        _arr = mproc.distribute_jobs(tomo[tstart:tend],
                                     func=_normalize,
                                     args=(flat, dark, cutoff),
                                     axis=0,
                                     ncore=ncore,
                                     nchunk=nchunk)

        arr[tstart:tend] = _arr

    return arr