class ROIProcessingPlugin(ProcessingPlugin): data = Input() image = Input() roi = Output() region = Output() def __init__(self, ROI: ROI): super(ROIProcessingPlugin, self).__init__() self.ROI = ROI self._param = None # type: Parameter self.name = f"ROI #{self.ROI.index}" def evaluate(self): # TODO -- don't need to do scene matching if we are not using ROI.getArrayRegion # Add ROI to same scene as image # (workflow serialization changes the obj refs, they need to be the same for ROI.getArrayRegion) image_scene = self.image.value.scene() image_scene.addItem(self.ROI) self.region.value = self.ROI.getArrayRegion(self.data.value, self.image.value) self.roi.value = self.region.value.astype(np.bool) @property def parameter(self): if not self._param: self._param = self.ROI.parameter() return self._param
class ImageRemap(ProcessingPlugin): name = 'Remesh' data = Input(description='Detector image', type=np.ndarray) geometry = InOut(description='pyFAI Geometry', type=pyFAI.geometry.Geometry) alphai = Input(description='GISAXS angle of incedence', type=float) out_range = Input(description='Coordinates of output image', type=list, default=None) resolution = Input(description='Resolution of output image', type=list, default=None) coord_sys = Input(description='Choice of coordinate system for output image', type=str, default='qp_qz') qImage = Output(description='Remapped image', type=np.ndarray) xcrd = Output(description='X-coordinates output image', type=np.ndarray) ycrd = Output(description='Y-coordinates output image', type=np.ndarray) #hints = [ ] def evaluate(self): I, x, y = hipies.remesh(self.Image.value, self.geometry.value, self.alphai.value, out_range = self.out_range.value, res = self.resolution.value, coord_sys = self.coord_sys.value) self.qImage.value = I dist = self.geometry.value._dist centerX = np.unravel_index(x.abs().argmin(), x.shape)[1] centerY = np.unravel_index(y.abs().argmin(), y.shape)[0] pixel = [self.geometry.value.get_pixel1(), self.geometry.value.get_pixel2()] center = [ centerX * pixel[0], centerY * pixel[1]) geometry.value.setFit2D(self.geometry.value._dist, center[0], center[1])
class XIntegratePlugin(ProcessingPlugin): name = 'X Integrate' ai = Input(description='A PyFAI.AzimuthalIntegrator object', type=AzimuthalIntegrator) data = Input(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) dark = Input(description='Dark noise image', type=np.ndarray) flat = Input(description='Flat field image', type=np.ndarray) normalization_factor = Input(description='Value of a normalization monitor', type=float, default=1.) qx = Output(description='Q_x bin center positions', type=np.array) Ix = Output(description='Binned/pixel-split integrated intensity', type=np.array) hints = [PlotHint(qx, Ix)] def evaluate(self): if self.dark.value is None: self.dark.value = np.zeros_like(self.data.value) if self.flat.value is None: self.flat.value = np.ones_like(self.data.value) if self.mask.value is None: self.mask.value = np.zeros_like(self.data.value) self.Ix.value = np.sum((self.data.value - self.dark.value) * np.average(self.flat.value - self.dark.value) / ( self.flat.value - self.dark.value) * np.logical_not(self.mask.value), axis=0) centerx = self.ai.value.getFit2D()['centerX'] centerz = self.ai.value.getFit2D()['centerY'] self.qx.value = self.ai.value.qFunction(np.array([centerz] * self.data.value.shape[1]), np.arange(0, self.data.value.shape[1])) / 10. self.qx.value[np.arange(0, self.data.value.shape[1]) < centerx] *= -1.
class CakeIntegratePlugin(ProcessingPlugin): ai = Input(description='A PyFAI.AzimuthalIntegrator object', type=AzimuthalIntegrator) data = Input(description='2d array representing intensity for each pixel', type=np.ndarray) npt_rad = Input(description='Number of bins along q', default=1000) npt_azim = Input(description='Number of bins along chi', default=1000) polz_factor = Input(description='Polarization factor for correction', type=float, default=0) unit = Input(description='Output units for q', type=[str, units.Unit], default="q_A^-1") radial_range = Input( description= 'The lower and upper range of the radial unit. If not provided, range is simply ' '(data.min(), data.max()). Values outside the range are ignored.', type=tuple) azimuth_range = Input( description= 'The lower and upper range of the azimuthal angle in degree. If not provided, ' 'range is simply (data.min(), data.max()). Values outside the range are ignored.', type=tuple) mask = Input( description= 'Array (same size as image) with 1 for masked pixels, and 0 for valid pixels', type=np.ndarray) dark = Input(description='Dark noise image', type=np.ndarray) flat = Input(description='Flat field image', type=np.ndarray) method = Input( description= 'Can be "numpy", "cython", "BBox" or "splitpixel", "lut", "csr", "nosplit_csr", ' '"full_csr", "lut_ocl" and "csr_ocl" if you want to go on GPU. To Specify the device: ' '"csr_ocl_1,2"', type=str, default='splitbbox') normalization_factor = Input( description='Value of a normalization monitor', type=float, default=1.) chi = Output(description='Chi bin center positions', type=np.array) cake = Output(description='Binned/pixel-split integrated intensity', type=np.array) q = Output(description='Q bin center positions', type=np.array) def evaluate(self): self.cake.value, q, chi = self.ai.value.integrate2d( data=nonesafe_flipud(self.data.value), npt_rad=self.npt_rad.value, npt_azim=self.npt_azim.value, radial_range=self.radial_range.value, azimuth_range=self.azimuth_range.value, mask=nonesafe_flipud(self.mask.value), polarization_factor=self.polz_factor.value, dark=nonesafe_flipud(self.dark.value), flat=nonesafe_flipud(self.flat.value), method=self.method.value, unit=self.unit.value, normalization_factor=self.normalization_factor.value) self.chi.value = chi self.q.value = q
class LinecutPlugin(ProcessingPlugin): name = 'Linecut' coordinate = Input(description='Coordinate of the cut', type=int, default=0) parallelAxis = Input(description='Axis the cut is parallel to', type=str, default="x") data = Input(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) dark = Input(description='Dark noise image', type=np.ndarray) flat = Input(description='Flat field image', type=np.ndarray) normalization_factor = Input( description='Value of a normalization monitor', type=float, default=1.) px = Output(name='px', description='Bin center positions', type=np.array) I = Output(name='Intensity', description='Binned/pixel-split intensity', type=np.array) hints = [PlotHint(px, I)] def evaluate(self): x = self.parallelAxis.value == "x" lperp = self.data.value.shape[not x] #booleans are ints if self.coordinate.value >= lperp: self.coordinate.value = lperp - 1 if self.coordinate.value < 0: self.coordinate.value = 0 if self.dark.value is None: self.dark.value = np.zeros_like(self.data.value) if self.flat.value is None: self.flat.value = np.ones_like(self.data.value) if self.mask.value is None: self.mask.value = np.zeros_like(self.data.value) h = ((self.data.value - self.dark.value) * np.average(self.flat.value - self.dark.value) / (self.flat.value - self.dark.value) * np.logical_not(self.mask.value)) self.I.value = (h[lperp - 1 - self.coordinate.value] if x else [b[self.coordinate.value] for b in h][::-1]) self.px.value = range(self.data.value.shape[x]) #booleans are ints def getCategory() -> str: return "Cuts"
class RickerWave(ProcessingPlugin): # inputs data = Input(description='Calibrant frame image data', type=np.ndarray) mask = Input( description='Array with 1 for masked pixels, and 0 for valid pixels', type=np.ndarray) domain = Input(description='Search domain in pixels: [r_min, r_max]', type=list) scale = Input(description='Approxmate intensity along the ring', type=float, default=1) width = Input(description='Approximate width of the AgB ring in pixels', type=float, default=5) # output center = Output( description='Approximated position of the direct beam center', type=np.ndarray) def evaluate(self): self.center.value, _ = cwt2d(self.data.value, domain=self.domain.value, scale=self.scale.value, width=self.width.value)
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 chisquared(ProcessingPlugin): dataA = Input(description='Frame A image data', type=np.ndarray) dataB = Input(description='Frame B image data', type=np.ndarray) chisquared = Output( description='Chi-squared difference between consecutive frames') def evaluate(self): self.chisquared.value = (self.dataA.value - self.dataB.value)**2.
class ReadCropped(ProcessingPlugin): """ Return uniformly distributed projection angles in radian. """ paths = Input(description='List of paths', type=List[str]) pxmin = Input(description='start x pixel', type=int) pxmax = Input(description='stop x pixel', type=int) pxstep = Input(description='step of x pixel', type=int) pzmin = Input(description='min z pixel', type=int) pzmax = Input(description='max z pixel', type=int) tomo = Output(description='3D array of tomograms (''projection'' first)') angles = Output(description='angle') def evaluate(self): frames = [] for f in self.paths.value: frames.append(fabio.open(f).data[np.arange(self.pxmin.value, self.pxmax.value, self.pxstep.value),self.pzmin.value:self.pzmax.value]) self.tomo.value = np.array(frames) self.angles.value = np.linspace(0,180, len(self.paths.value))
class QIntegratePlugin(ProcessingPlugin): integrator = Input(description="A PyFAI.AzimuthalIntegrator object", type=AzimuthalIntegrator) data = Input(description="2d array representing intensity for each pixel", type=np.ndarray) npt = Input(description="Number of bins along q") polz_factor = Input(description="Polarization factor for correction", type=float) unit = Input(description="Output units for q", type=[str, units.Unit], default="q_A^-1") radial_range = Input( description="The lower and upper range of the radial unit. If not provided, range is simply " "(data.min(), data.max()). Values outside the range are ignored.", type=tuple, ) azimuth_range = Input( description="The lower and upper range of the azimuthal angle in degree. If not provided, " "range is simply (data.min(), data.max()). Values outside the range are ignored." ) mask = Input(description="Array (same size as image) with 1 for masked pixels, and 0 for valid pixels", type=np.ndarray) dark = Input(description="Dark noise image", type=np.ndarray) flat = Input(description="Flat field image", type=np.ndarray) method = Input( description='Can be "numpy", "cython", "BBox" or "splitpixel", "lut", "csr", "nosplit_csr", ' '"full_csr", "lut_ocl" and "csr_ocl" if you want to go on GPU. To Specify the device: ' '"csr_ocl_1,2"', type=str, ) normalization_factor = Input(description="Value of a normalization monitor", type=float) q = Output(description="Q bin center positions", type=np.array) I = Output(description="Binned/pixel-split integrated intensity", type=np.array) def evaluate(self): self.q.value, self.I.value = self.integrator.value().integrate1d( data=self.data.value, npt=self.npt.value, radial_range=self.radial_range.value, azimuth_range=self.azimuth_range.value, mask=self.mask.value, polarization_factor=self.polz_factor.value, dark=self.dark.value, flat=self.flat.value, method=self.method.value, unit=self.unit.value, normalization_factor=self.normalization_factor.value, )
class QconversionGISAXS(ProcessingPlugin): integrator = Input(description='A PyFAI.AzimuthalIntegrator object', type=AzimuthalIntegrator) data = Input(description='Frame image data', type=np.ndarray) qx = Output(description='qx array with dimension of data', type=np.ndarray) qz = Output(description='qz array with dimension of data', type=np.ndarray) def evaluate(self): self.qx, self.qz = self.qconverion() def qconverion(self): chi = self.integrator.chiArray() twotheta = self.integrator.twoThetaArray() # find alphai alphai = self.integrator.getvalue('Wavelength') # Doble check what is chi = 0 qx = 2 * np.pi / self.integrator.getvalue('Wavelength') * np.sin(twotheta) * np.sin(chi) qz = 2 * np.pi / self.integrator.getvalue('Wavelength') * np.sin(twotheta) * np.cos(chi) return qx, qz
class ROIProcessingPlugin(ProcessingPlugin): data = Input() image = Input() roi = Output() region = Output() def __init__(self, ROI: ROI): super(ROIProcessingPlugin, self).__init__() self.ROI = ROI self._param = None # type: Parameter self.name = f"ROI #{self.ROI.index}" def evaluate(self): self.region.value = self.ROI.getLabelArray(self.data.value, self.image.value) self.roi.value = self.region.value.astype(np.bool) @property def parameter(self): if not self._param: self._param = self.ROI.parameter() return self._param
class Angles(ProcessingPlugin): """ Return uniformly distributed projection angles in radian. """ nang = Input(description='Number of projections', type=int) ang1 = Input(description='First proj. angle in deg', type=float, default=0.) ang2 = Input(description='Last proj. angle in deg', type=int, default=180.) angles = Output(description='Projection angles', type='np.ndarray') def evaluate(self): self.angles.value = tomopy.angles(self.nang.value, ang1=self.ang1.value, ang2=self.ang2.value)
class SimulateCalibrant(ProcessingPlugin): name = 'Simulate Calibrant' ai = Input(description='Azimimuthal integrator', type=AzimuthalIntegrator) Imax = Input(description='Maximum intensity of rings', type=float, default=1) calibrant = Input(description='pyFAI calibrant object to simulate', type=calibrant) data = Output(description='Simulated data for given calibrant', type=np.ndarray) def evaluate(self): self.calibrant.value.set_wavelength(self.ai.value.get_wavelength()) self.data.value = self.calibrant.value.fake_calibration_image( self.ai.value, Imax=self.Imax.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 HorizontalCutPlugin(ProcessingPlugin): data = Input(description='Frame image data', type=np.ndarray) qx = Input(description='qx coordinate corresponding to data', type=np.ndarray) mask = Input(description='Frame image data', type=np.ndarray, default=None) # Make qx range a single parameter, type = tuple qxminimum = Input(description='qx minimum limit', type=int) qxmaximum = Input(description='qx maximum limit', type=int) horizontalcut = Output(description='mask (1 is masked) with dimension of data', type=np.ndarray) def evaluate(self): if self.mask.value is not None: self.horizontalcut.value = np.logical_or(self.mask.value, self.qx < self.qxminimum.value, self.qx > self.qxmaximum.value) else: self.horizontalcut.value = np.logical_or(self.qx < self.qxminimum.value, self.qx > self.qxmaximum.value)
class FindCenter(ProcessingPlugin): """ 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`. """ tomo = Input(description="3D tomographic data", type=np.ndarray) theta = Input(description="Projection angles in radian", type=np.ndarray) ind = Input( description="Index of the slice to be used for reconstruction", default=None, type=int) init = Input( description="Initial guess for the center", default=None, type=float) tol = Input( description="Desired sub-pixel accuracy", default=0.5, type=float) mask = Input( description="If True, apply a circular mask", default=True, type=bool) ratio = Input( description= "The ratio of the radius of the circular mask to the edge of the reconstructed image", default=1., type=float) sinogram_order = Input( type=bool, default=False, description= "Determins whether data is a stack of sinograms (True, y-axis first axis) or a stack of radiographs (False, theta first axis)." ) center = Output(description="Rotation axis location", type=float) def evaluate(self): self.center.value = tomopy.find_center( self.tomo.value, self.theta.value, ind=self.ind.value, init=self.init.value, tol=self.tol.value, mask=self.mask.value, ratio=self.ratio.value, sinogram_order=self.sinogram_order.value)
class AzimuthalCutPlugin(ProcessingPlugin): data = Input(description='Frame image data', type=np.ndarray) center = Input( description= 'Array (same size as image) with 1 for masked pixels, and 0 for valid pixels', type=np.ndarray) detector = 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)') mask = Output(description='Mask array (1 is masked).', type=np.ndarray) def evaluate(self): raise NotImplementedError
class ThresholdMaskPlugin(ProcessingPlugin): data = Input(description="Frame image data", type=np.ndarray) minimum = Input(description="Threshold floor", type=int) maximum = Input(description="Threshold ceiling", type=int) neighborhood = Input( description="Neighborhood size in pixels for morphological opening. Only clusters of this size" " that fail the threshold are masked", type=int, ) mask = Output(description="Thresholded mask (1 is masked)", type=np.ndarray) def evaluate(self): self.mask.value = 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(self.mask.value, kernel, output=self.mask.value) # write-back to mask
class FindCenterPc(ProcessingPlugin): """ 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`. """ proj1 = Input(description="2D projection data", type=np.ndarray) proj2 = Input(description="2D projection data", type=np.ndarray) tol = Input(description="Subpixel accuracy", type=float, default=0.5) center = Output(description="Rotation axis location", type=float) def evaluate(self): self.center.value = tomopy.find_center_pc(self.proj1.value, self.proj2.value, tol=self.tol.value)
class VerticalCutPlugin(ProcessingPlugin): data = Input(description='Frame image data', type=np.ndarray) qz = Input(description='qz coordinate corresponding to data', type=np.ndarray) mask = Input(description='Frame image data', type=np.ndarray, default=None) # Make qz range a single parameter, type = tuple qzminimum = Input(description='qz minimum limit', type=int) qzmaximum = Input(description='qz maximum limit', type=int) cut = Output(description='mask (1 is masked) with dimension of data', type=np.ndarray) hints = [VerticalROI(qzminimum, qzmaximum)] def evaluate(self): if self.mask.value is not None: self.cut.value = np.logical_or(self.mask.value, self.qz < self.qzminimum.value, self.qz > self.qzmaximum.value) else: self.cut.value = np.logical_or(self.qz.value < self.qzminimum.value, self.qz.value > self.qzmaximum.value)
class FindCenterVo(ProcessingPlugin): """ Find rotation axis location using Nghia Vo's method. :cite:`Vo:14`. """ tomo = Input(description="3D tomographic data", type=np.ndarray) ind = Input( description="Index of the slice to be used for reconstruction", type=int, default=None) smin = Input(description="Coarse search radius", type=int, default=-50) smax = Input(description="Coarse search radius", type=int, default=50) srad = Input(description="Fine search radius", type=float, default=6) step = Input(description="Step of fine searching", type=float, default=0.5) ratio = Input( description= "he ratio between the FOV of the camera and the size of object. It's used to generate the mask", type=float, default=0.5) drop = Input( description="Drop lines around vertical center of the mask", type=int, default=20) center = Output(description="Rotation axis location", type=float) def evaluate(self): self.center.value = tomopy.find_center_vo( self.tomo.value, ind=self.ind.value, smin=self.smin.value, smax=self.smax.value, srad=self.srad.value, step=self.step.value, ratio=self.ratio.value, drop=self.drop.value)
class Recon(ProcessingPlugin): """ Reconstruct object from projection data. """ tomo = Input(description="Input array", type=np.ndarray) angles = Input(description="Projection angles in radians", type=List[float]) center = Input(description="Location of rotation axis", type=int, default=None) sinogram_order = Input( description="Determines whether data is a stack of sinograms", type=bool, default=False) algorithm = Input(description="[art, bart, fbp, gridrec, etc.]", type=str, default='gridrec') init_recon = Input(description="Initial guess of the reconstruction", type=np.ndarray, default=None) ncore = Input(description="Number of CPU cores", type=int, default=None) nchunk = Input(description="Chunk size for each core", type=int, default=None) num_gridx = Input( description="Number of pixels along X in Reconstructed data", type=int, default=None) num_gridy = Input( description="Number of pixels along y in Reconstructed data", type=int, default=None) filter_name = Input( description="Name of the filter for analytic reconstruction", type=str, default='none') filter_par = Input(description="Filter parameters", type=np.ndarray, default=None) num_iter = Input(description="Number of algorithm iterations", type=int, default=None) num_block = Input( description="Number of data blocks for intermediate updating", type=int, default=None) ind_block = Input(description="Order of projections to be used", type=np.ndarray, default=None) reg_par = Input(description="Regularization parameter for smoothing", type=float, default=None) recon = Output(description="Reconstructed 3D array", type=np.ndarray) def evaluate(self): if self.algorithm.value == 'gridrec': kwargs = { 'num_gridx': self.num_gridx.value, 'num_gridy': self.num_gridy.value, 'filter_name': self.filter_name.value, 'filter_par': self.filter_par.value, } else: # TODO: Not sure which are applicable to other algorithms yet kwargs = { 'num_iter': self.num_iter.value, 'num_block': self.num_block.value, 'ind_block': self.ind_block.value, 'reg_par': self.reg_par.value } # remove unset kwargs kwargs = {k: v for k, v in kwargs.items() if v is not None} self.recon.value = tomopy.recon( self.tomo.value, self.angles.value, center=self.center.value, sinogram_order=self.sinogram_order.value, algorithm=self.algorithm.value, init_recon=self.init_recon.value, ncore=self.ncore.value, nchunk=self.nchunk.value, **kwargs)
class QBackgroundFit(ProcessingPlugin): name = 'Q Background Fit' q = InOut(description='Q bin center positions', type=np.array) Iq = InOut(description='Q spectra bin intensities', type=np.array) # model = Input(description='Fittable model class in the style of Astropy', type=Enum) domainmin = Input(description='Min bound on the domain of the input data', type=float) domainmax = Input(description='Max bound on the domain of the input data', type=float) degree = Input(name='Polynomial Degree', description='Polynomial degree number', type=int, min=1, default=4) # fitter = Input(description='Fitting algorithm', default=fitting.LevMarLSQFitter(), type=Enum, limits={'Linear LSQ':fitting.LinearLSQFitter(), 'Levenberg-Marquardt LSQ':fitting.LevMarLSQFitter(), 'SLSQP LSQ':fitting.SLSQPLSQFitter(), 'Simplex LSQ':fitting.SimplexLSQFitter()}) domainfilter = Input( description= 'Domain limits where peaks will be fitted; auto-populated by ') backgroundmodel = Output( description= 'A new model with the fitted parameters; behaves as parameterized function', type=Fittable1DModel) backgroundprofile = Output( description='The fitted profile from the evaluation of the ' 'resulting model over the input range.') rawIq = Output(description='The spectra data before subtraction.') hints = [ PlotHint(q, Iq), PlotHint(q, backgroundprofile), PlotHint(q, rawIq) ] modelvars = {} def __init__(self): super(QBackgroundFit, self).__init__() self.peakranges = [] @property def parameter(self): self._workflow.attach(self.find_peak_ranges) # order may be bad... return super(QBackgroundFit, self).parameter def find_peak_ranges(self): from xicam.SAXS.processing.astropyfit import \ AstropyQSpectraFit # must be a late import to avoid being picked up first by plugin manager thisindex = self._workflow.processes.index(self) self.peakranges = [ (process.domainmin.value, process.domainmax.value) for process in self._workflow.processes[thisindex + 1:] if isinstance(process, AstropyQSpectraFit) ] def detach(self): self._workflow.detach(self.find_peak_ranges) def evaluate(self): model = models.Polynomial1D(degree=self.degree.value) norange = self.domainmin.value == self.domainmax.value if self.domainmin.value is None and self.q.value is not None or norange: # truncate the q and I arrays with limits self.domainmin.value = self.q.value.min() if self.domainmax.value is None and self.q.value is not None or norange: # truncate the q and I arrays with limits self.domainmax.value = self.q.value.max() filter = np.logical_and(self.domainmin.value <= self.q.value, self.q.value <= self.domainmax.value) for peakrange in self.peakranges: print('applying peak range:', peakrange) filter &= np.logical_or(peakrange[0] >= self.q.value, self.q.value >= peakrange[1]) q = self.q.value[filter] Iq = self.Iq.value[filter] self.backgroundmodel.value = fitting.LinearLSQFitter()(model, q, Iq) self.backgroundprofile.value = self.backgroundmodel.value(self.q.value) self.rawIq.value = self.Iq.value.copy() self.Iq.value = self.Iq.value - self.backgroundprofile.value def getCategory() -> str: return "Fits"
class AstropyQSpectraFit(ProcessingPlugin): name = 'Q Fit (Astropy)' q = InOut(description='Q bin center positions', type=np.array) Iq = InOut(description='Q spectra bin intensities', type=np.array) model = Input(description='Fittable model class in the style of Astropy', type=Enum) domainmin = Input(description='Min bound on the domain of the input data', type=float) domainmax = Input(description='Max bound on the domain of the input data', type=float) fitter = Input(description='Fitting algorithm', default=fitting.LevMarLSQFitter(), type=Enum, limits={'Linear LSQ': fitting.LinearLSQFitter(), 'Levenberg-Marquardt LSQ': fitting.LevMarLSQFitter(), 'SLSQP LSQ': fitting.SLSQPLSQFitter(), 'Simplex LSQ': fitting.SimplexLSQFitter()}) fittedmodel = Output(description='A new model with the fitted parameters; behaves as parameterized function', type=Fittable1DModel) fittedprofile = Output( description='The fitted profile from the evaluation of the resulting model over the input range.') hints = [PlotHint(q, Iq), PlotHint(q, fittedprofile)] modelvars = {} def __init__(self): super(AstropyQSpectraFit, self).__init__() self.model.limits = {plugin.name: plugin.plugin_object for plugin in pluginmanager.getPluginsOfCategory('Fittable1DModelPlugin')} self.model.value = list(self.model.limits.values())[0] @property 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 def reset_parameter(self): # cache old parameter oldparam = self._param # empty it for child in oldparam.children(): child.remove() # reset attribute so new parameter is generated self._param = None # add new children to old parameter for child in self.parameter.children(): # type: Parameter oldparam.addChild(child) # set old parameter to attribute self._param = oldparam def evaluate(self): if self.model.value is None or self.model.value == '----': return norange = self.domainmin.value == self.domainmax.value if self.domainmin.value is None and self.q.value is not None or norange: # truncate the q and I arrays with limits self.domainmin.value = self.q.value.min() if self.domainmax.value is None and self.q.value is not None or norange: # truncate the q and I arrays with limits self.domainmax.value = self.q.value.max() for name, input in self.modelvars.items(): # propogate user-defined values to the model getattr(self.model.value, name).value = input.value getattr(self.model.value, name).fixed = input.fixed filter = np.logical_and(self.domainmin.value <= self.q.value, self.q.value <= self.domainmax.value) q = self.q.value[filter] Iq = self.Iq.value[filter] self.fittedmodel.value = self.fitter.value(self.model.value, q, Iq) self.fittedprofile.value = self.fittedmodel.value(self.q.value) def getCategory() -> str: return "Fits"