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 ChiIntegratePlugin(ProcessingPlugin): ai = Input(description='A PyFAI.AzimuthalIntegrator object', type=AzimuthalIntegrator) data = Input(description='2d array representing intensity for each pixel', type=np.ndarray) 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='Q bin center positions', type=np.array) Ichi = Output(description='Binned/pixel-split integrated intensity', type=np.array) hints = [PlotHint(chi, Ichi)] def evaluate(self): self.Ichi.value, q, chi = self.ai.value.integrate2d( data=nonesafe_flipud(self.data.value), npt_rad=1, 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.Ichi.value = np.sum(self.Ichi.value, axis=1) self.chi.value = chi
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 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"