def find_center(tomo, theta, ind=None, emission=True, init=None, tol=0.5, mask=True, ratio=1.): """ Find rotation axis location. The function exploits systematic artifacts in reconstructed images due to shifts in the rotation center. It uses image entropy as the error metric and ''Nelder-Mead'' routine (of the scipy optimization module) as the optimizer :cite:`Donath:06`. Parameters ---------- tomo : ndarray 3D tomographic data. theta : array Projection angles in radian. ind : int, optional Index of the slice to be used for reconstruction. emission : bool, optional Determines whether data is emission or transmission type. init : float Initial guess for the center. tol : scalar Desired sub-pixel accuracy. mask : bool, optional If ``True``, apply a circular mask to the reconstructed image to limit the analysis into a circular region. ratio : float, optional The ratio of the radius of the circular mask to the edge of the reconstructed image. Returns ------- float Rotation axis location. """ tomo = dtype.as_float32(tomo) theta = dtype.as_float32(theta) if ind is None: ind = tomo.shape[1] // 2 if init is None: init = tomo.shape[2] // 2 hmin, hmax = _adjust_hist_limits(tomo[:, ind:ind + 1, :], theta, ind, mask, emission) # Magic is ready to happen... res = minimize(_find_center_cost, init, args=(tomo, theta, ind, hmin, hmax, mask, ratio, emission), method='Nelder-Mead', tol=tol) return res.x
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
def find_center( tomo, theta, ind=None, emission=True, init=None, tol=0.5, mask=True, ratio=1.): """ Find rotation axis location. The function exploits systematic artifacts in reconstructed images due to shifts in the rotation center. It uses image entropy as the error metric and ''Nelder-Mead'' routine (of the scipy optimization module) as the optimizer :cite:`Donath:06`. Parameters ---------- tomo : ndarray 3D tomographic data. theta : array Projection angles in radian. ind : int, optional Index of the slice to be used for reconstruction. emission : bool, optional Determines whether data is emission or transmission type. init : float Initial guess for the center. tol : scalar Desired sub-pixel accuracy. mask : bool, optional If ``True``, apply a circular mask to the reconstructed image to limit the analysis into a circular region. ratio : float, optional The ratio of the radius of the circular mask to the edge of the reconstructed image. Returns ------- float Rotation axis location. """ tomo = dtype.as_float32(tomo) theta = dtype.as_float32(theta) if ind is None: ind = tomo.shape[1] // 2 if init is None: init = tomo.shape[2] // 2 print (ind, tomo.shape) hmin, hmax = _adjust_hist_limits( tomo[:, ind:ind + 1, :], theta, ind, mask, emission) # Magic is ready to happen... res = minimize( _find_center_cost, init, args=(tomo, theta, ind, hmin, hmax, mask, ratio, emission), method='Nelder-Mead', tol=tol) return res.x
def remove_outlier(arr, dif, size=3, axis=0, ncore=None, out=None): """ Remove high intensity bright spots from a N-dimensional array by chunking along the specified dimension, and performing (N-1)-dimensional median filtering along the other dimensions. 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 to chunk. 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 Corrected array. """ tmp = np.empty_like(arr) ncore, chnk_slices = mproc.get_ncore_slices(arr.shape[axis], ncore=ncore) filt_size = [size] * arr.ndim filt_size[axis] = 1 with cf.ThreadPoolExecutor(ncore) as e: slc = [slice(None)] * arr.ndim for i in range(ncore): slc[axis] = chnk_slices[i] e.submit(scipy.ndimage.median_filter, arr[tuple(slc)], size=filt_size, output=tmp[tuple(slc)]) arr = dtype.as_float32(arr) tmp = dtype.as_float32(tmp) dif = np.float32(dif) with mproc.set_numexpr_threads(ncore): out = ne.evaluate('where(arr-tmp>=dif,tmp,arr)', out=out) return out
def find_center_vo(tomo, ind=None, smin=-50, smax=50, srad=6, step=0.5, ratio=0.5, drop=20): """ Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. Parameters ---------- tomo : ndarray 3D tomographic data. ind : int, optional Index of the slice to be used for reconstruction. smin, smax : int, optional Coarse search radius. Reference to the horizontal center of the sinogram. srad : float, optional Fine search radius. step : float, optional Step of fine searching. ratio : float, optional The ratio between the FOV of the camera and the size of object. It's used to generate the mask. drop : int, optional Drop lines around vertical center of the mask. Returns ------- float Rotation axis location. """ tomo = dtype.as_float32(tomo) if ind is None: ind = tomo.shape[1] // 2 _tomo = tomo[:, ind, :] # Enable cache for FFTW. pyfftw.interfaces.cache.enable() # Reduce noise by smooth filters. Use different filters for coarse and fine search _tomo_cs = ndimage.filters.gaussian_filter(_tomo, (3, 1)) _tomo_fs = ndimage.filters.median_filter(_tomo, (2, 2)) # Coarse and fine searches for finding the rotation center. if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) _tomo_coarse = downsample(np.expand_dims(_tomo_cs, 1), level=2)[:, 0, :] init_cen = _search_coarse(_tomo_coarse, smin, smax, ratio, drop) fine_cen = _search_fine(_tomo_fs, srad, step, init_cen * 4, ratio, drop) else: init_cen = _search_coarse(_tomo_cs, smin, smax, ratio, drop) fine_cen = _search_fine(_tomo_fs, srad, step, init_cen, ratio, drop) logger.debug('Rotation center search finished: %i', fine_cen) return fine_cen
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
def find_center_vo(tomo, ind=None, smin=-50, smax=50, srad=6, step=0.25, ratio=0.5, drop=20): """ Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. Parameters ---------- tomo : ndarray 3D tomographic data. ind : int, optional Index of the slice to be used for reconstruction. smin, smax : int, optional Coarse search radius. Reference to the horizontal center of the sinogram. srad : float, optional Fine search radius. step : float, optional Step of fine searching. ratio : float, optional The ratio between the FOV of the camera and the size of object. It's used to generate the mask. drop : int, optional Drop lines around vertical center of the mask. Returns ------- float Rotation axis location. """ tomo = dtype.as_float32(tomo) (depth, height, width) = tomo.shape if ind is None: ind = height // 2 if height > 10: # Averaging sinograms to improve SNR _tomo = np.mean(tomo[:, ind - 5:ind + 5, :], axis=1) else: _tomo = tomo[:, ind, :] else: _tomo = tomo[:, ind, :] # Denoising # There's a critical reason to use different window sizes # between coarse and fine search. _tomo_cs = ndimage.filters.gaussian_filter(_tomo, (3, 1)) _tomo_fs = ndimage.filters.gaussian_filter(_tomo, (2, 2)) # Coarse and fine searches for finding the rotation center. if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) _tomo_coarse = downsample( np.expand_dims(_tomo_cs, 1), level=2)[:, 0, :] init_cen = _search_coarse( _tomo_coarse, smin / 4.0, smax / 4.0, ratio, drop) fine_cen = _search_fine(_tomo_fs, srad, step, init_cen * 4, ratio, drop) else: init_cen = _search_coarse(_tomo_cs, smin, smax, ratio, drop) fine_cen = _search_fine(_tomo_fs, srad, step, init_cen, ratio, drop) logger.debug('Rotation center search finished: %i', fine_cen) return fine_cen
def circ_mask(arr, axis, ratio=1, val=0., ncore=None): """ Apply circular mask to a 3D array. Parameters ---------- arr : ndarray Arbitrary 3D array. axis : int Axis along which mask will be performed. ratio : int, optional Ratio of the mask's diameter in pixels to the smallest edge size along given axis. val : int, optional Value for the masked region. Returns ------- ndarray Masked array. """ arr = dtype.as_float32(arr) val = np.float32(val) _arr = arr.swapaxes(0, axis) dx, dy, dz = _arr.shape mask = _get_mask(dy, dz, ratio) with mproc.set_numexpr_threads(ncore): ne.evaluate('where(mask, _arr, val)', out=_arr) return _arr.swapaxes(0, axis)
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
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
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)
def remove_nan(arr, val=0., ncore=None): """ Replace NaN values in array with a given value. Parameters ---------- arr : ndarray Input array. val : float, optional Values to be replaced with NaN values in array. ncore : int, optional Number of cores that will be assigned to jobs. Returns ------- ndarray Corrected array. """ arr = dtype.as_float32(arr) val = np.float32(val) with mproc.set_numexpr_threads(ncore): ne.evaluate('where(arr!=arr, val, arr)', out=arr) return arr
def circ_mask(arr, axis, ratio=1, val=0.): """ Apply circular mask to a 3D array. Parameters ---------- arr : ndarray Arbitrary 3D array. axis : int Axis along which mask will be performed. ratio : int, optional Ratio of the mask's diameter in pixels to the smallest edge size along given axis. val : int, optional Value for the masked region. Returns ------- ndarray Masked array. """ arr = dtype.as_float32(arr) _arr = arr.swapaxes(0, axis) dx, dy, dz = _arr.shape mask = _get_mask(dy, dz, ratio) for m in range(dx): _arr[m, ~mask] = val return _arr.swapaxes(0, axis)
def vector(tomo, theta, center=None, num_iter=1): tomo = dtype.as_float32(tomo) theta = dtype.as_float32(theta) # Initialize tomography data. tomo = init_tomo(tomo, sinogram_order=False, sharedmem=False) recon_shape = (tomo.shape[0], tomo.shape[2], tomo.shape[2]) recon1 = np.zeros(recon_shape, dtype=np.float32) recon2 = np.zeros(recon_shape, dtype=np.float32) center_arr = get_center(tomo.shape, center) extern.c_vector(tomo, center_arr, recon1, recon2, theta, num_gridx=tomo.shape[2], num_gridy=tomo.shape[2], num_iter=num_iter) return recon1, recon2
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) out = np.empty_like(arr) if ncore is None: ncore = mproc.mp.cpu_count() with cf.ThreadPoolExecutor(ncore) as e: slc = [slice(None)]*arr.ndim for i in range(arr.shape[axis]): slc[axis] = i e.submit(filters.median_filter, arr[tuple(slc)], size=(size, size), output=out[tuple(slc)]) return out
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)
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
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)
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
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) out = np.empty_like(arr) if ncore is None: ncore = mproc.mp.cpu_count() with cf.ThreadPoolExecutor(ncore) as e: slc = [slice(None)]*arr.ndim for i in range(arr.shape[axis]): slc[axis] = i e.submit(filters.sobel, arr[slc], output=out[slc]) return out
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
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
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
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
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
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
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
def trim_sinogram(data, center, x, y, diameter): """ Provide sinogram corresponding to a circular region of interest by trimming the complete sinogram of a compact object. Parameters ---------- data : ndarray Input 3D data. center : float Rotation center location. x, y : int, int x and y coordinates in pixels (image center is (0, 0)) diameter : float Diameter of the circle of the region of interest. Returns ------- ndarray Output 3D data. """ data = dtype.as_float32(data.copy()) dx, dy, dz = data.shape rad = np.sqrt(x * x + y * y) alpha = np.arctan2(x, y) l1 = center - diameter / 2 l2 = center - diameter / 2 + rad roidata = np.ones((dx, dy, diameter), dtype='float32') delphi = np.pi / dx for m in range(dx): # Calculate start end coordinates for each row of the sinogram. ind1 = np.ceil(np.cos(alpha - m * delphi) * (l2 - l1) + l1) ind2 = np.floor(np.cos(alpha - m * delphi) * (l2 - l1) + l1 + diameter) # Make sure everythin is inside the frame. if ind1 < 0: ind1 = 0 if ind1 > dz: ind1 = dz if ind2 < 0: ind2 = 0 if ind2 > dz: ind2 = dz roidata[m, :, 0:(ind2 - ind1)] = data[m:m + 1, :, ind1:ind2] return roidata
def _sample(arr, level, axis, mode): arr = dtype.as_float32(arr) dx, dy, dz = arr.shape if mode == 0: dim = arr.shape[axis] / np.power(2, level) if mode == 1: dim = arr.shape[axis] * np.power(2, level) out = _init_out(arr, axis, dim) return extern.c_sample(mode, arr, dx, dy, dz, level, axis, out)
def trim_sinogram(data, center, x, y, diameter): """ Provide sinogram corresponding to a circular region of interest by trimming the complete sinogram of a compact object. Parameters ---------- data : ndarray Input 3D data. center : float Rotation center location. x, y : int, int x and y coordinates in pixels (image center is (0, 0)) diameter : float Diameter of the circle of the region of interest. Returns ------- ndarray Output 3D data. """ data = dtype.as_float32(data.copy()) dx, dy, dz = data.shape rad = np.sqrt(x * x + y * y) alpha = np.arctan2(x, y) l1 = center - diameter / 2 l2 = center - diameter / 2 + rad roidata = np.ones((dx, dy, diameter), dtype='float32') delphi = np.pi / dx for m in range(dx): # Calculate start end coordinates for each row of the sinogram. ind1 = np.ceil(np.cos(alpha - m * delphi) * (l2 - l1) + l1) ind2 = np.floor(np.cos(alpha - m * delphi) * (l2 - l1) + l1 + diameter) # Make sure everythin is inside the frame. if ind1 < 0: ind1 = 0 if ind1 > dz: ind1 = dz if ind2 < 0: ind2 = 0 if ind2 > dz: ind2 = dz roidata[m, :, 0:(ind2 - ind1)] = data[m:m+1, :, ind1:ind2] return roidata
def _sample(arr, level, axis, mode): arr = dtype.as_float32(arr.copy()) dx, dy, dz = arr.shape # Determine the new size, dim, of the down-/up-sampled dimension if mode == 0: dim = int(arr.shape[axis] / np.power(2, level)) if mode == 1: dim = int(arr.shape[axis] * np.power(2, level)) out = _init_out(arr, axis, dim) return extern.c_sample(mode, arr, dx, dy, dz, level, axis, out)
def init_tomo(tomo, sinogram_order, sharedmem=True): tomo = dtype.as_float32(tomo) if not sinogram_order: tomo = np.swapaxes(tomo, 0, 1) # doesn't copy data if sharedmem: # copy data to sharedmem (if not already or not contiguous) tomo = dtype.as_sharedmem(tomo, copy=not dtype.is_contiguous(tomo)) else: # ensure contiguous tomo = np.require(tomo, requirements="AC") return tomo
def remove_outlier1d(arr, dif, size=3, axis=0, ncore=None, out=None): """ Remove high intensity bright spots from an array, using a one-dimensional median filter along the specified axis. 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. out : ndarray, optional Output array for result. If same as arr, process will be done in-place. Returns ------- ndarray Corrected array. """ arr = dtype.as_float32(arr) dif = np.float32(dif) tmp = np.empty_like(arr) other_axes = [i for i in range(arr.ndim) if i != axis] largest = np.argmax([arr.shape[i] for i in other_axes]) lar_axis = other_axes[largest] ncore, chnk_slices = mproc.get_ncore_slices(arr.shape[lar_axis], ncore=ncore) filt_size = [1] * arr.ndim filt_size[axis] = size with cf.ThreadPoolExecutor(ncore) as e: slc = [slice(None)] * arr.ndim for i in range(ncore): slc[lar_axis] = chnk_slices[i] e.submit(filters.median_filter, arr[slc], size=filt_size, output=tmp[slc], mode='mirror') with mproc.set_numexpr_threads(ncore): out = ne.evaluate('where(arr-tmp>=dif,tmp,arr)', out=out) return out
def init_tomo(tomo, sinogram_order, sharedmem=True): tomo = dtype.as_float32(tomo) if not sinogram_order: tomo = np.swapaxes(tomo, 0, 1) #doesn't copy data if sharedmem: # copy data to sharedmem (if not already or not contiguous) tomo = dtype.as_sharedmem(tomo, copy=not dtype.is_contiguous(tomo)) else: # ensure contiguous tomo = np.require(tomo, requirements="AC") return tomo
def vector2(tomo1, tomo2, theta1, theta2, center1=None, center2=None, num_iter=1, axis1=1, axis2=2): tomo1 = dtype.as_float32(tomo1) tomo2 = dtype.as_float32(tomo2) theta1 = dtype.as_float32(theta1) theta2 = dtype.as_float32(theta2) # Initialize tomography data. tomo1 = init_tomo(tomo1, sinogram_order=False, sharedmem=False) tomo2 = init_tomo(tomo2, sinogram_order=False, sharedmem=False) recon_shape = (tomo1.shape[0], tomo1.shape[2], tomo1.shape[2]) recon1 = np.zeros(recon_shape, dtype=np.float32) recon2 = np.zeros(recon_shape, dtype=np.float32) recon3 = np.zeros(recon_shape, dtype=np.float32) center_arr1 = get_center(tomo1.shape, center1) center_arr2 = get_center(tomo2.shape, center2) extern.c_vector2(tomo1, tomo2, center_arr1, center_arr2, recon1, recon2, recon3, theta1, theta2, num_gridx=tomo1.shape[2], num_gridy=tomo1.shape[2], num_iter=num_iter, axis1=axis1, axis2=axis2) return recon1, recon2, recon3
def find_center_vo(tomo, ind=None, smin=-50, smax=50, srad=3, tol=0.5, ratio=2., drop=20): """ Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. Parameters ---------- tomo : ndarray 3D tomographic data. ind : int, optional Index of the slice to be used for reconstruction. smin, smax : int, optional Reference to the horizontal center of the sinogram. srad : float, optional Initial guess for the center. tol : scalar, optional Desired sub-pixel accuracy. ratio : float, optional The ratio between the size of object and FOV of the camera. It's used to generate the mask. drop : int, optional Drop lines around vertical center of the mask. Returns ------- float Rotation axis location. Warning ------- Not tested yet. """ tomo = dtype.as_float32(tomo) if ind is None: ind = tomo.shape[1] // 2 if init is None: init = tomo.shape[2] // 2 # Reduce noise by smooth filtering. tomo = ndimage.filters.gaussian_filter(tomo, sigma=(3, 1)) # Coarse search for finiding the roataion center. init_cen = _search_coarse(tomo, smin, smax, ratio, drop) # Fine search for finiding the roataion center. return _search_fine(tomo, srad, step, init_cen, ratio, drop)
def vector(tomo, theta, center=None, num_iter=1, axis=0): tomo = dtype.as_float32(tomo) theta = dtype.as_float32(theta) # Initialize tomography data. tomo = init_tomo(tomo, sinogram_order=False, sharedmem=False) recon_shape = (tomo.shape[0], tomo.shape[2], tomo.shape[2]) recon = np.zeros(recon_shape, dtype=np.float32) center_arr = get_center(tomo.shape, center) extern.c_vector(tomo, center_arr, recon, theta, num_gridx=tomo.shape[2], num_gridy=tomo.shape[2], num_iter=num_iter, axis=axis) return recon
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
def find_center_vo(tomo, ind=None, smin=-50, smax=50, srad=6, step=0.5, ratio=0.5, drop=20): """ Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. Parameters ---------- tomo : ndarray 3D tomographic data. ind : int, optional Index of the slice to be used for reconstruction. smin, smax : int, optional Coarse search radius. Reference to the horizontal center of the sinogram. srad : float, optional Fine search radius. step : float, optional Step of fine searching. ratio : float, optional The ratio between the FOV of the camera and the size of object. It's used to generate the mask. drop : int, optional Drop lines around vertical center of the mask. Returns ------- float Rotation axis location. """ tomo = dtype.as_float32(tomo) if ind is None: ind = tomo.shape[1] // 2 _tomo = tomo[:, ind, :] # Enable cache for FFTW. pyfftw.interfaces.cache.enable() # Reduce noise by smooth filters. Use different filters for coarse and fine search _tomo_cs = ndimage.filters.gaussian_filter(_tomo, (3, 1)) _tomo_fs = ndimage.filters.median_filter(_tomo, (2, 2)) # Coarse and fine searches for finding the rotation center. if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) _tomo_coarse = downsample(np.expand_dims(_tomo_cs,1), level=2)[:, 0, :] init_cen = _search_coarse(_tomo_coarse, smin / 4.0, smax / 4.0, ratio, drop) fine_cen = _search_fine(_tomo_fs, srad, step, init_cen*4, ratio, drop) else: init_cen = _search_coarse(_tomo_cs, smin, smax, ratio, drop) fine_cen = _search_fine(_tomo_fs, srad, step, init_cen, ratio, drop) logger.debug('Rotation center search finished: %i', fine_cen) return fine_cen
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
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
def remove_outlier1d(arr, dif, size=3, axis=0, ncore=None, out=None): """ Remove high intensity bright spots from an array, using a one-dimensional median filter along the specified axis. 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. out : ndarray, optional Output array for result. If same as arr, process will be done in-place. Returns ------- ndarray Corrected array. """ arr = dtype.as_float32(arr) dif = np.float32(dif) tmp = np.empty_like(arr) other_axes = [i for i in range(arr.ndim) if i != axis] largest = np.argmax([arr.shape[i] for i in other_axes]) lar_axis = other_axes[largest] ncore, chnk_slices = mproc.get_ncore_slices( arr.shape[lar_axis], ncore=ncore) filt_size = [1]*arr.ndim filt_size[axis] = size with cf.ThreadPoolExecutor(ncore) as e: slc = [slice(None)]*arr.ndim for i in range(ncore): slc[lar_axis] = chnk_slices[i] e.submit(filters.median_filter, arr[slc], size=filt_size, output=tmp[slc], mode='mirror') with mproc.set_numexpr_threads(ncore): out = ne.evaluate('where(arr-tmp>=dif,tmp,arr)', out=out) return out
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
def remove_outlier(arr, dif, size=3, axis=0, ncore=None, out=None): """ Remove high intensity bright spots from a N-dimensional array by chunking along the specified dimension, and performing (N-1)-dimensional median filtering along the other dimensions. 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 to chunk. 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 Corrected array. """ arr = dtype.as_float32(arr) dif = np.float32(dif) tmp = np.empty_like(arr) ncore, chnk_slices = mproc.get_ncore_slices(arr.shape[axis], ncore=ncore) filt_size = [size]*arr.ndim filt_size[axis] = 1 with cf.ThreadPoolExecutor(ncore) as e: slc = [slice(None)]*arr.ndim for i in range(ncore): slc[axis] = chnk_slices[i] e.submit(filters.median_filter, arr[tuple(slc)], size=filt_size, output=tmp[tuple(slc)]) with mproc.set_numexpr_threads(ncore): out = ne.evaluate('where(arr-tmp>=dif,tmp,arr)', out=out) return out
def find_center_vo(tomo, ind=None, smin=-40, smax=40, srad=10, step=1, ratio=2., drop=20): """ Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. Parameters ---------- tomo : ndarray 3D tomographic data. ind : int, optional Index of the slice to be used for reconstruction. smin, smax : int, optional Reference to the horizontal center of the sinogram. srad : float, optional Fine search radius. step : float, optional Step of fine searching. ratio : float, optional The ratio between the FOV of the camera and the size of object. It's used to generate the mask. drop : int, optional Drop lines around vertical center of the mask. Returns ------- float Rotation axis location. """ tomo = dtype.as_float32(tomo) if ind is None: ind = tomo.shape[1] // 2 _tomo = tomo[:, ind, :] # Reduce noise by smooth filtering. _tomo = ndimage.filters.gaussian_filter(_tomo, sigma=(3, 1)) # Coarse search for finiding the roataion center. if _tomo.shape[0] * _tomo.shape[1] > 4e6: # If data is large (>2kx2k) _tomo_coarse = downsample(tomo, level=2)[:, ind, :] init_cen = _search_coarse(_tomo_coarse, smin, smax, ratio, drop) else: init_cen = _search_coarse(_tomo, smin, smax, ratio, drop) # Fine search for finiding the roataion center. fine_cen = _search_fine(_tomo, srad, step, init_cen*4, ratio, drop) logger.debug('Rotation center search finished: %i', fine_cen) return fine_cen
def minus_log(arr): """ In-place computation of the minus log of a given array. Parameters ---------- arr : ndarray 3D stack of projections. Returns ------- none """ arr = dtype.as_float32(arr) np.log(arr, arr) # in-place np.negative(arr, arr) # in-place
def remove_outlier(arr, dif, size=3, axis=0, ncore=None, out=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. out : ndarray, optional Output array for result. If same as arr, process will be done in-place. Returns ------- ndarray Corrected array. """ arr = dtype.as_float32(arr) dif = np.float32(dif) tmp = np.empty_like(arr) if ncore is None: ncore = mproc.mp.cpu_count() e = cf.ThreadPoolExecutor(ncore) slc = [slice(None)]*len(arr.shape) for i in range(arr.shape[axis]): slc[axis] = i e.submit(filters.median_filter, arr[slc], size=(size, size), output=tmp[slc]) e.shutdown() with mproc.set_numexpr_threads(ncore): out = ne.evaluate('where(arr-tmp>=dif,tmp,arr)', out=out) return out
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) l = np.float32(1e-6) flat = np.mean(flat, axis=0, dtype=np.float32) dark = np.mean(dark, axis=0, dtype=np.float32) with mproc.set_numexpr_threads(ncore): #denom = ne.evaluate('flat-dark') #ne.evaluate('where(denom<l,l,denom)', out=denom) #out = ne.evaluate('arr-dark', out=out) denom = flat - dark denom[denom < l] = l out = arr - dark out[out < l] = l out[:] /= denom #ne.evaluate('out/denom', out=out, truediv=True) if cutoff is not None: cutoff = np.float32(cutoff) out[out > cutoff] = cutoff #ne.evaluate('where(out>cutoff,cutoff,out)', out=out) return out
def gaussian_filter(arr, sigma=3, order=0, axis=0, ncore=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. Returns ------- ndarray 3D array of same shape as input. """ arr = dtype.as_float32(arr) out = np.empty_like(arr) if ncore is None: ncore = mproc.mp.cpu_count() with cf.ThreadPoolExecutor(ncore) as e: slc = [slice(None)] * arr.ndim for i in range(arr.shape[axis]): slc[axis] = i e.submit(filters.gaussian_filter, arr[tuple(slc)], sigma, order=order, output=out[tuple(slc)]) return out
def remove_nan(arr, val=0.): """ Replace NaN values in array with a given value. Parameters ---------- arr : ndarray Input array. val : float, optional Values to be replaced with NaN values in array. Returns ------- ndarray Corrected array. """ arr = dtype.as_float32(arr) arr[np.isnan(arr)] = val return arr
def remove_neg(arr, val=0.): """ Replace negative values in array with a given value. Parameters ---------- arr : ndarray Input array. val : float, optional Values to be replaced with negative values in array. Returns ------- ndarray Corrected array. """ arr = dtype.as_float32(arr) arr[arr < 0.0] = val return arr