class CoM(ProcessingPlugin): data = Input(description='Array of two or more dimensions.', type=np.ndarray) mask = Input( description= 'Array (same size as image) with 1 for masked pixels, and 0 for valid pixels', type=np.ndarray) x_min = Input(description='X pixel index, bottom left.', type=int, default=1) y_min = Input(description='Y pixel index, bottom left.', type=int, default=1) x_max = Input(description='X pixel index, top right.', type=int, default=1000) y_max = Input(description='Y pixel index, top right.', type=int, default=1000) x_cen = InOut(description='X pixel index, center of mass', type=float) y_cen = InOut(description='Y pixel index, center of mass', type=float) def evaluate(self): data = np.flipud(self.data.value) mask = np.flipud(self.mask.value) data = data * np.logical_not(mask) (self.y_cen.value, self.x_cen.value) = ndimage.measurements.center_of_mass( data[self.y_min.value:self.y_max.value, self.x_min.value:self.x_max.value]) self.x_cen.value = self.x_cen.value + self.x_min.value self.y_cen.value = self.y_cen.value + self.y_min.value
def parameter(self): # clear cache in for input in self.modelvars: del self.__dict__[input] varcache = self.modelvars.copy() self.modelvars = {} self._inputs = None self._inverted_vars = None if hasattr(self, '_inverted_vars'): del self._inverted_vars for name in self.model.value.param_names: param = getattr(self.model.value, name) # TODO: CHECK NAMESPACE if name in varcache: input = varcache[name] else: input = InOut(name=name, default=param.default, limits=param.bounds, type=float, fixed=False, fixable=True) setattr(self, name, input) self.modelvars[name] = input parameter = super(AstropyQSpectraFit, self).parameter parameter.child('model').sigValueChanged.connect(self.reset_parameter) return parameter
class NormalizeBg(ProcessingPlugin): """ 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. """ tomo = InOut(description="3D tomographic data", type=np.ndarray) air = Input( description= "Number of pixels at each boundary to calculate the scaling factor", type=int, default=1) ncore = Input( description="Number of cores that will be assigned to jobs", type=int, default=None) nchunk = Input( description="Chunk size for each core", type=int, default=None) def evaluate(self): self.tomo.value = tomopy.normalize_bg( self.tomo.value, air=self.air.value, ncore=self.ncore.value, nchunk=self.nchunk.value)
class NormalizeNf(ProcessingPlugin): """ 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). """ tomo = InOut(description="3D tomographic data", type=np.ndarray) flats = Input(description="3D flat field data", type=np.ndarray) darks = Input(description="3D dark field data", type=np.ndarray) flat_loc = Input( description="Indices of flat field data within tomography", type=list) cutoff = Input(description="Cut-off value", type=float, default=None) ncore = Input( description="Number of cores that will be assigned to jobs", type=int, default=None) out = Input( description= "Output array for result. If same as arr, process will be done in-place.", type=np.ndarray, default=None) def evaluate(self): self.tomo.value = tomopy.normalize_nf( self.tomo.value, self.flats.value, self.darks.value, self.flat_loc.value, cutoff=self.cutoff.value, ncore=self.ncore.value, out=self.out.value)
class ThresholdMaskPlugin(ProcessingPlugin): name = 'Threshold Mask' data = Input(description='Frame image data', type=np.ndarray) minimum = Input(description='Threshold floor', type=int, default=3) maximum = Input(description='Threshold ceiling', type=int, default=1e10) neighborhood = Input( description= 'Neighborhood size in pixels for morphological opening. Only clusters of this size' ' that fail the threshold are masked', type=int, default=2) mask = InOut(description='Thresholded mask (1 is masked)', type=np.ndarray) def evaluate(self): mask = np.logical_or(self.data.value < self.minimum.value, self.data.value > self.maximum.value) y, x = np.ogrid[-self.neighborhood.value:self.neighborhood.value + 1, -self.neighborhood.value:self.neighborhood.value + 1] kernel = x**2 + y**2 <= self.neighborhood.value**2 morphology.binary_opening(mask, kernel, output=mask) # write-back to mask if self.mask.value is not None: mask = np.logical_or( mask, self.mask.value) # .astype(np.int, copy=False) self.mask.value = mask
class GaussianFilter(ProcessingPlugin): """ Apply Gaussian filter to 3D array along specified axis. TODO: Make simga to an optional sequence """ tomo = InOut(description="Input array", type=np.ndarray) sigma = Input(description="Standard deviation for Gaussian kernel", type=float, default=3) order = Input(description="Order of the filter along each axis", type=tuple, default=0) axis = Input(description="Axis along which median filtering is performed", type=int, default=0) ncore = Input(description="Number of cores that will be assigned to jobs", type=int, default=None) def evaluate(self): self.tomo.value = tomopy.gaussian_filter(self.tomo.value, sigma=self.sigma.value, order=self.order.value, axis=self.axis.value, ncore=self.ncore.value)
class FileMask(ProcessingPlugin): name = 'File Mask' mask = InOut(description='Mask array (1 is masked).', type=np.ndarray) ai = Input( description= 'PyFAI azimuthal integrator instance; the geometry of the detector' 's inactive area will be masked.', type=AzimuthalIntegrator) path = Input(description='File path to image mask file.', type=str, default='') def evaluate(self): if not self.path.value: return mask = fabio.open(self.path.value).data.astype(np.bool_) if not mask.shape == self.ai.value.detector.shape: raise IndexError('Mask file does not match detector shape.') if self.mask.value is None: self.mask.value = np.zeros(self.ai.value.detector.shape).astype( np.bool_) self.mask.value = np.logical_or(self.mask.value, mask) def getCategory() -> str: return "Masks"
class fourierAutocorrelation(ProcessingPlugin): name = 'Fourier Autocorrelation' data = Input(description='Calibrant frame image data', type=np.ndarray) center = Output( description='Approximated position of the direct beam center') ai = InOut( description='Azimuthal integrator; center will be modified in place', type=AzimuthalIntegrator) def evaluate(self): mask = self.ai.value.detector.mask data = np.array(self.data.value) if isinstance(mask, np.ndarray) and mask.shape == self.data.value.shape: data = data * (1 - mask) con = signal.fftconvolve(data, data) / np.sqrt( signal.fftconvolve(np.ones_like(self.data.value), np.ones_like(self.data.value))) self.center.value = np.array(np.unravel_index(con.argmax(), con.shape)) / 2. self.center.value[0] = len(data) - self.center.value[0] fit2dparams = self.ai.value.getFit2D() fit2dparams['centerX'] = self.center.value[1] fit2dparams['centerY'] = self.center.value[0] self.ai.value.setFit2D(**fit2dparams)
class ZingerMaskPlugin(ProcessingPlugin): data = Input(description='Frame image data', type=np.ndarray) mask = InOut(description='Mask array (1 is masked).', type=np.ndarray) def evaluate(self): self.mask.value = np.logical_or( self.mask.value, astroscrappy.detect_cosmics(self.data.value, self.mask.value)[0])
class ArrayDivide(ProcessingPlugin): """ Divide array by a scalar """ recon = InOut(description="Input array", type=np.ndarray) div = Input(description="Divisor", type=float) def evaluate(self): self.recon.value = np.divide(self.recon.value, self.div.value)
class ArrayTranspose(ProcessingPlugin): data = InOut(description='Input array.', type=np.ndarray) axes = Input( description= 'By default, reverse the dimensions, otherwise permute the axes according to the values given.', type=np.array) def evaluate(self): self.data.value = np.transpose(self.data.value, self.axes.value)
class RemoveRing(ProcessingPlugin): """ Remove ring artifacts from images in the reconstructed domain. Descriptions of parameters need to be more clear for sure. """ recon = InOut(description="Array of reconstruction data", type=np.ndarray) center_x = Input( description="abscissa location of center of rotation", type=float, default=None) center_y = Input( description="ordinate location of center of rotation", type=float, default=None) thresh = Input( description="maximum value of an offset due to a ring artifact", type=float, default=300.) thresh_max = Input( description="max value for portion of image to filter", type=float, default=300.) thresh_min = Input( description="min value for portion of image to filer", type=float, default=-100.) theta_min = Input( description= "minimum angle in degrees (int) to be considered ring artifact", type=int, default=30) rwidth = Input( description="Maximum width of the rings to be filtered in pixels", type=int, default=30) int_mode = Input(description="WARP or REFLECT", type=str, default='WARP') ncore = Input(description="Number or CPU cores", type=int, default=None) nchunk = Input( description="Chunk size for each core", type=int, default=None) def evaluate(self): self.recon.value = tomopy.remove_ring( self.recon.value, center_x=self.center_x.value, center_y=self.center_y.value, thresh=self.thresh.value, thresh_max=self.thresh_max.value, thresh_min=self.thresh_min.value, theta_min=self.theta_min.value, rwidth=self.rwidth.value, int_mode=self.int_mode.value, ncore=self.ncore.value, nchunk=self.nchunk.value, out=self.recon.value)
class ArrayMax(ProcessingPlugin): """ Maximum value of the tomoay """ tomo = InOut(description="Input tomogram", type=np.ndarray) floor = Input(description="Floor value for comparison", type=float, default=0) def evaluate(self): self.tomo.value = np.maximum(self.tomo.value, self.floor.value)
class Normalize(ProcessingPlugin): """ Normalize raw 3D projection data with flats and darks """ tomo = InOut(description="3D tomographic data", type=np.ndarray) flats = Input(description="3D flat field data", type=np.ndarray) darks = Input(description="3D dark field data", type=np.ndarray) def evaluate(self): self.tomo.value = tomopy.normalize(self.tomo.value, self.flats.value, self.darks.value)
class GrowMask(ProcessingPlugin): mask = InOut(description='Mask array (1 is masked).', type=np.ndarray) size = Input( description='Distance to grow the mask (px); used as kernel size') def evaluate(self): y, x = np.ogrid[-self.size.value:self.size.value + 1, -self.size.value:self.size.value + 1] kernel = x**2 + y**2 <= self.size.value**2 morphology.binary_dilation( self.mask.value, kernel, output=self.mask.value) # write-back to mask
class SobelFilter(ProcessingPlugin): """ Apply Sobel filter to 3D array along specified axis """ tomo = InOut(description="Input array", type=np.ndarray) axis = Input(description="Axis along which sobel filtering is performed", type=int) ncore = Input(description="Number of CPU cores", type=int, default=None) def evaluate(self): self.tomo.value = tomopy.sobel_filter(self.tomo.value, axis=self.axis.value, ncore=self.ncore.value)
class RemoveNan(ProcessingPlugin): """ Replace NaN values in array with a given value """ tomo = InOut(description="Auto Genrated", type=np.ndarray) val = Input(description="Values to be replaced with NaN", type=float, default=0.) ncore = Input(description="Numner of CPU cores", type=int, default=None) def evaluate(self): self.tomo.value = tomopy.remove_nan(self.tomo.value, val=self.val.value, ncore=self.ncore.value)
class DetectorMaskPlugin(ProcessingPlugin): name = 'Detector Mask' ai = Input( description= 'PyFAI azimuthal integrator instance; the geometry of the detector' 's inactive area will be masked.', type=AzimuthalIntegrator) mask = InOut(description='Mask array (1 is masked).', type=np.ndarray) def evaluate(self): if self.ai.value and self.ai.value.detector: mask = self.ai.value.detector.calc_mask() if mask is None: mask = np.zeros(self.ai.value.detector.shape) self.mask.value = np.logical_or(self.mask.value, mask)
class PolygonMask(ProcessingPlugin): name = 'Polygon Mask' ai = Input( description='PyFAI detector instance; the geometry of the detector' 's inactive area will be masked.', type=Detector) polygon = Input(description='Polygon shape to mask (interior is masked)', type=List[Tuple[float, float]]) mask = InOut(description='Mask array (1 is masked).', type=np.ndarray) def evaluate(self): if self.polygon.value is not None: # create path path = Path( np.vstack([self.polygon.value, self.polygon.value[-1]]), [Path.MOVETO] + [Path.LINETO] * (len(self.polygon.value) - 1) + [Path.CLOSEPOLY]) # create a grid ny, nx = self.ai.value.detector.shape x = np.linspace(0, nx, nx) y = np.linspace(0, ny, ny) xgrid, ygrid = np.meshgrid(x, y) pixel_coordinates = np.c_[xgrid.ravel(), ygrid.ravel()] # find points within path self.mask.value = np.logical_or( self.mask.value, np.flipud( path.contains_points(pixel_coordinates).reshape(ny, nx))) @property def parameter(self): if not (hasattr(self, '_param') and self._param): instructions = parameterTypes.TextParameter( name='Instructions', value= 'Use the mouse to draw the mask. Click [Finish Mask] below when complete, or [Clear Selection] to start over.', readonly=True) clearmask = parameterTypes.ActionParameter(name='Clear Selection') finishmask = parameterTypes.ActionParameter(name='Finish Mask') children = [instructions, clearmask, finishmask] self._param = parameterTypes.GroupParameter(name='Polygon Mask', children=children) return self._param def getCategory() -> str: return "Masks"
class Downsample(ProcessingPlugin): """ Downsample along specified axis of a 3D array. """ tomo = InOut(description="3D input array", type=np.ndarray) # recon = tomo level = Input(description="Downsampling level in powers of two", type=int, default=1) axis = Input( description="Axis along which downsampling will be performed", default=2, type=int) def evaluate(self): self.tomo.value = tomopy.downsample( self.tomo.value, level=int(self.level.value), axis=int(self.axis.value))
class Upsample(ProcessingPlugin): """ Upsample along specified axis of a 3D array """ tomo = InOut(description="3D input array", type=np.ndarray) level = Input( description="Upsampling level in powers of two", type=int, default=1) axis = Input( description="Axis along which upsampling will be performed", type=int, default=2) def evaluate(self): self.tomo.value = tomopy.upsample( self.tomo.value, level=self.level.value, axis=self.axis.value)
class ArrayRotate(ProcessingPlugin): data = InOut(description='Array of two or more dimensions.', type=np.ndarray) k = Input( description='Number of times the array is rotated by 90 degrees.', type=int, default=1) axes = Input( description= 'The array is rotated in the plane defined by the axes. Axes must be different.', type=np.ndarray, default=(0, 1)) def evaluate(self): self.data.value = np.rot90(self.data.value, self.k.value, self.axes.value)
class InPaint(ProcessingPlugin): name = 'Inpaint (pyFAI)' data = InOut(description='2d array representing intensity for each pixel', type=np.ndarray) mask = Input( description= 'Array (same size as image) with 1 for masked pixels, and 0 for valid pixels', type=np.ndarray) inpaint = Output(description='2d array with masking pixels ' 'reconstructed' '', type=np.ndarray) def evaluate(self): self.inpaint.value = reconstruct(self.data.value, self.mask.value)
class Crop(ProcessingPlugin): recon = InOut(description='Array to crop along given access (slicing)', type=np.ndarray) p11 = Input(description='First point along first axis', type=int) p12 = Input(description='Second point along first axis', type=int) p21 = Input(description='First point along second axis', type=int) p22 = Input(description='Second point along second axis', type=int) axis = Input(description='Axis to crop along', type=int, default=0) def evaluate(self): slc = [] pts = [self.p11.value, self.p12.value, self.p21.value, self.p22.value] for n in range(len(self.recon.value.shape)): if n == self.axis.value: slc.append(slice(None)) else: slc.append(slice(int(pts.pop(0)), -int(pts.pop(0)))) self.recon.value = self.recon.value[slc]
class Pad(ProcessingPlugin): tomo = InOut(description="Input array", type=np.ndarray) axis = Input(description="Axis along which padding will be performed", type=int) npad = Input(description="New dimension after padding", type=int, default=None) mode = Input(description="constant or edge", type=str, default='constant') ncore = Input(description="Number of cores that will be assigned to jobs", type=int, default=None) def evaluate(self): self.tomo.value = tomopy.pad(self.tomo.value, self.axis.value, npad=self.npad.value, mode=self.mode.value, ncore=self.ncore.value)
class RemoveStripeTi(ProcessingPlugin): """ Remove horizontal stripes from sinogram using Titarenko's approach :cite:`Miqueles:14`. """ tomo = InOut(description="3D tomographic data", type=np.ndarray) nblock = Input(description="Number of blocks", type=int, default=0) alpha = Input(description="Damping factor", type=float, default=1.5) ncore = Input(description="Number of CPU cores", type=int, default=None) nchunk = Input(description="Chunk size for each core", type=int, default=None) def evaluate(self): self.tomo.value = tomopy.remove_stripe_ti(self.tomo.value, nblock=self.nblock.value, alpha=self.alpha.value, ncore=self.ncore.value, nchunk=self.nchunk.value)
class RemoveStripeSf(ProcessingPlugin): """ Normalize raw projection data using a smoothing filter approach """ tomo = InOut(description="3D tomographic data", type=np.ndarray) size = Input(description="Size of the smoothing filter", type=int, default=5) ncore = Input(description="Number of CPU cores", type=int, default=None) nchunk = Input(description="Chunk size for each core", type=int, default=None) def evaluate(self): self.tomo.value = tomopy.remove_stripe_sf(self.tomo.value, size=self.size.value, ncore=self.ncore.value, nchunk=self.nchunk.value)
class MinusLog(ProcessingPlugin): """ Computation of the minus log of a given array """ tomo = InOut(description="3D stack of projections", type=np.ndarray) ncore = Input(description="Number of cores that will be assigned to jobs.", type=int, default=None) out = Input( description= "Output array for result. If same as arr, process will be done in-place.", type=np.ndarray, default=None) def evaluate(self): self.tomo.value = tomopy.minus_log(self.tomo.value, ncore=self.ncore.value, out=self.out.value)
class NormalizeRoi(ProcessingPlugin): """ Normalize raw projection data using an average of a selected window on projection images. """ tomo = InOut(description="3D tomographic data", type=np.ndarray) roi = Input( description= "[top-left, top-right, bottom-left, bottom-right] pixel coordinates", type=list, default=[0, 0, 10, 10]) ncore = Input(description="Number of cores that will be assigned to jobs", type=int, default=None) def evaluate(self): self.tomo.value = tomopy.normalize_roi(self.tomo.value, roi=self.roi.value, ncore=self.ncore.value)
class RetrievePhase(ProcessingPlugin): """ Perform single-step phase retrieval from phase-contrast measurements :cite:`Paganin:02` """ tomo = InOut(description="3D tomographic data", type=np.ndarray) pixel_size = Input(description="Detector pixel size in cm", type=float, default=0.0001) dist = Input(description="Propagation distance of the wavefront in cm", type=float, default=50) energy = Input(description="Energy of incident wave in keV", type=float, default=20) alpha = Input(description="Regularization parameter", type=float, default=0.001) pad = Input(description="Self evident", type=bool, default=True) ncore = Input(description="Number of CPU cores", type=int, default=None) nchunk = Input(description="Chunk size for each core", type=int, default=None) def evaluate(self): # New dimensions and pad value after padding. py, pz, val = _calc_pad(self.tomo.value, self.pixel_size.value, self.dist.value, self.energy.value, self.pad.value) # Compute the reciprocal grid. dx, dy, dz = self.tomo.value.shape w2 = _reciprocal_grid(self.pixel_size.value, dy + 2 * py, dz + 2 * pz) # Filter in Fourier space. phase_filter = np.fft.fftshift( _paganin_filter_factor(self.energy.value, self.dist.value, self.alpha.value, w2)) prj = np.full((dy + 2 * py, dz + 2 * pz), val, dtype='float32') _retrieve_phase(self.tomo.value, phase_filter, py, pz, prj, self.pad.value)