diff options
author | Picca Frédéric-Emmanuel <picca@debian.org> | 2024-02-05 16:30:07 +0100 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@debian.org> | 2024-02-05 16:30:07 +0100 |
commit | 04095a69f18767d222b16fae5b40f2b712cd6f7e (patch) | |
tree | d20abd3ee2f237319443e9dfd7500ad55d29a33d /src/silx/gui/fit | |
parent | 3427caf0e96690e56aac6231a91df8f0f7a64fc2 (diff) |
New upstream version 2.0.0+dfsg
Diffstat (limited to 'src/silx/gui/fit')
-rw-r--r-- | src/silx/gui/fit/BackgroundWidget.py | 149 | ||||
-rw-r--r-- | src/silx/gui/fit/FitConfig.py | 185 | ||||
-rw-r--r-- | src/silx/gui/fit/FitWidget.py | 286 | ||||
-rw-r--r-- | src/silx/gui/fit/FitWidgets.py | 101 | ||||
-rw-r--r-- | src/silx/gui/fit/Parameters.py | 475 | ||||
-rw-r--r-- | src/silx/gui/fit/test/testBackgroundWidget.py | 19 | ||||
-rw-r--r-- | src/silx/gui/fit/test/testFitConfig.py | 36 | ||||
-rw-r--r-- | src/silx/gui/fit/test/testFitWidget.py | 18 |
8 files changed, 688 insertions, 581 deletions
diff --git a/src/silx/gui/fit/BackgroundWidget.py b/src/silx/gui/fit/BackgroundWidget.py index 9ab63e4..d9cfcc8 100644 --- a/src/silx/gui/fit/BackgroundWidget.py +++ b/src/silx/gui/fit/BackgroundWidget.py @@ -1,4 +1,4 @@ -#/*########################################################################## +# /*########################################################################## # Copyright (C) 2004-2021 V.A. Sole, European Synchrotron Radiation Facility # # This file is part of the PyMca X-ray Fluorescence Toolkit developed at @@ -44,8 +44,9 @@ __date__ = "28/06/2017" class HorizontalSpacer(qt.QWidget): def __init__(self, *args): qt.QWidget.__init__(self, *args) - self.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Expanding, - qt.QSizePolicy.Fixed)) + self.setSizePolicy( + qt.QSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Fixed) + ) class BackgroundParamWidget(qt.QWidget): @@ -56,6 +57,7 @@ class BackgroundParamWidget(qt.QWidget): Updating the widgets causes :attr:`sigBackgroundParamWidgetSignal` to be emitted. """ + sigBackgroundParamWidgetSignal = qt.pyqtSignal(object) def __init__(self, parent=None): @@ -70,8 +72,7 @@ class BackgroundParamWidget(qt.QWidget): self.algorithmCombo = qt.QComboBox(self) self.algorithmCombo.addItem("Strip") self.algorithmCombo.addItem("Snip") - self.algorithmCombo.activated[int].connect( - self._algorithmComboActivated) + self.algorithmCombo.activated[int].connect(self._algorithmComboActivated) # Strip parameters --------------------------------------------------- self.stripWidthLabel = qt.QLabel(self) @@ -90,9 +91,10 @@ class BackgroundParamWidget(qt.QWidget): self.stripIterValue.setText("0") self.stripIterValue.editingFinished[()].connect(self._emitSignal) self.stripIterValue.setToolTip( - "Number of iterations for strip algorithm.\n" + - "If greater than 999, an 2nd pass of strip filter is " + - "applied to remove artifacts created by first pass.") + "Number of iterations for strip algorithm.\n" + + "If greater than 999, an 2nd pass of strip filter is " + + "applied to remove artifacts created by first pass." + ) # Snip parameters ---------------------------------------------------- self.snipWidthLabel = qt.QLabel(self) @@ -103,7 +105,6 @@ class BackgroundParamWidget(qt.QWidget): self.snipWidthSpin.setMinimum(0) self.snipWidthSpin.valueChanged[int].connect(self._emitSignal) - # Smoothing parameters ----------------------------------------------- self.smoothingFlagCheck = qt.QCheckBox(self) self.smoothingFlagCheck.setText("Smoothing Width (Savitsky-Golay)") @@ -111,7 +112,7 @@ class BackgroundParamWidget(qt.QWidget): self.smoothingSpin = qt.QSpinBox(self) self.smoothingSpin.setMinimum(3) - #self.smoothingSpin.setMaximum(40) + # self.smoothingSpin.setMaximum(40) self.smoothingSpin.setSingleStep(2) self.smoothingSpin.valueChanged[int].connect(self._emitSignal) @@ -125,12 +126,12 @@ class BackgroundParamWidget(qt.QWidget): self.anchorsFlagCheck = qt.QCheckBox(self.anchorsGroup) self.anchorsFlagCheck.setText("Use anchors") self.anchorsFlagCheck.setToolTip( - "Define X coordinates of points that must remain fixed") - self.anchorsFlagCheck.stateChanged[int].connect( - self._anchorsToggled) + "Define X coordinates of points that must remain fixed" + ) + self.anchorsFlagCheck.stateChanged[int].connect(self._anchorsToggled) anchorsLayout.addWidget(self.anchorsFlagCheck) - maxnchannel = 16384 * 4 # Fixme ? + maxnchannel = 16384 * 4 # Fixme ? self.anchorsList = [] num_anchors = 4 for i in range(num_anchors): @@ -170,8 +171,7 @@ class BackgroundParamWidget(qt.QWidget): :param algorithm: "snip" or "strip" """ if algorithm not in ["strip", "snip"]: - raise ValueError( - "Unknown background filter algorithm %s" % algorithm) + raise ValueError("Unknown background filter algorithm %s" % algorithm) self.algorithm = algorithm self.stripWidthSpin.setEnabled(algorithm == "strip") @@ -220,7 +220,7 @@ class BackgroundParamWidget(qt.QWidget): if "AnchorsList" in ddict: anchorslist = ddict["AnchorsList"] - if anchorslist in [None, 'None']: + if anchorslist in [None, "None"]: anchorslist = [] for spin in self.anchorsList: spin.setValue(0) @@ -248,20 +248,22 @@ class BackgroundParamWidget(qt.QWidget): stripitertext = self.stripIterValue.text() stripiter = int(stripitertext) if len(stripitertext) else 0 - return {"algorithm": self.algorithm, - "StripThreshold": 1.0, - "SnipWidth": self.snipWidthSpin.value(), - "StripIterations": stripiter, - "StripWidth": self.stripWidthSpin.value(), - "SmoothingFlag": self.smoothingFlagCheck.isChecked(), - "SmoothingWidth": self.smoothingSpin.value(), - "AnchorsFlag": self.anchorsFlagCheck.isChecked(), - "AnchorsList": [spin.value() for spin in self.anchorsList]} + return { + "algorithm": self.algorithm, + "StripThreshold": 1.0, + "SnipWidth": self.snipWidthSpin.value(), + "StripIterations": stripiter, + "StripWidth": self.stripWidthSpin.value(), + "SmoothingFlag": self.smoothingFlagCheck.isChecked(), + "SmoothingWidth": self.smoothingSpin.value(), + "AnchorsFlag": self.anchorsFlagCheck.isChecked(), + "AnchorsList": [spin.value() for spin in self.anchorsList], + } def _emitSignal(self, dummy=None): self.sigBackgroundParamWidgetSignal.emit( - {'event': 'ParametersChanged', - 'parameters': self.getParameters()}) + {"event": "ParametersChanged", "parameters": self.getParameters()} + ) class BackgroundWidget(qt.QWidget): @@ -270,6 +272,7 @@ class BackgroundWidget(qt.QWidget): Strip and snip filters parameters can be adjusted using input widgets, and the computed backgrounds are plotted next to the original data to show the result.""" + def __init__(self, parent=None): qt.QWidget.__init__(self, parent) self.setWindowTitle("Strip and SNIP Configuration Window") @@ -328,8 +331,7 @@ class BackgroundWidget(qt.QWidget): self._update() def _update(self, resetzoom=False): - """Compute strip and snip backgrounds, update the curves - """ + """Compute strip and snip backgrounds, update the curves""" if self._y is None: return @@ -338,7 +340,7 @@ class BackgroundWidget(qt.QWidget): # smoothed data y = numpy.ravel(numpy.array(self._y)).astype(numpy.float64) if pars["SmoothingFlag"]: - ysmooth = filters.savitsky_golay(y, pars['SmoothingWidth']) + ysmooth = filters.savitsky_golay(y, pars["SmoothingWidth"]) f = [0.25, 0.5, 0.25] ysmooth[1:-1] = numpy.convolve(ysmooth, f, mode=0) ysmooth[0] = 0.5 * (ysmooth[0] + ysmooth[1]) @@ -346,14 +348,13 @@ class BackgroundWidget(qt.QWidget): else: ysmooth = y - # loop for anchors x = self._x - niter = pars['StripIterations'] + niter = pars["StripIterations"] anchors_indices = [] - if pars['AnchorsFlag'] and pars['AnchorsList'] is not None: + if pars["AnchorsFlag"] and pars["AnchorsList"] is not None: ravelled = x - for channel in pars['AnchorsList']: + for channel in pars["AnchorsList"]: if channel <= ravelled[0]: continue index = numpy.nonzero(ravelled >= channel)[0] @@ -362,52 +363,56 @@ class BackgroundWidget(qt.QWidget): if index > 0: anchors_indices.append(index) - stripBackground = filters.strip(ysmooth, - w=pars['StripWidth'], - niterations=niter, - factor=pars['StripThreshold'], - anchors=anchors_indices) + stripBackground = filters.strip( + ysmooth, + w=pars["StripWidth"], + niterations=niter, + factor=pars["StripThreshold"], + anchors=anchors_indices, + ) if niter >= 1000: # final smoothing - stripBackground = filters.strip(stripBackground, - w=1, - niterations=50*pars['StripWidth'], - factor=pars['StripThreshold'], - anchors=anchors_indices) + stripBackground = filters.strip( + stripBackground, + w=1, + niterations=50 * pars["StripWidth"], + factor=pars["StripThreshold"], + anchors=anchors_indices, + ) if len(anchors_indices) == 0: - anchors_indices = [0, len(ysmooth)-1] + anchors_indices = [0, len(ysmooth) - 1] anchors_indices.sort() snipBackground = 0.0 * ysmooth lastAnchor = 0 for anchor in anchors_indices: if (anchor > lastAnchor) and (anchor < len(ysmooth)): - snipBackground[lastAnchor:anchor] =\ - filters.snip1d(ysmooth[lastAnchor:anchor], - pars['SnipWidth']) + snipBackground[lastAnchor:anchor] = filters.snip1d( + ysmooth[lastAnchor:anchor], pars["SnipWidth"] + ) lastAnchor = anchor if lastAnchor < len(ysmooth): - snipBackground[lastAnchor:] =\ - filters.snip1d(ysmooth[lastAnchor:], - pars['SnipWidth']) - - self.graphWidget.addCurve(x, y, - legend='Input Data', - replace=True, - resetzoom=resetzoom) - self.graphWidget.addCurve(x, stripBackground, - legend='Strip Background', - resetzoom=False) - self.graphWidget.addCurve(x, snipBackground, - legend='SNIP Background', - resetzoom=False) + snipBackground[lastAnchor:] = filters.snip1d( + ysmooth[lastAnchor:], pars["SnipWidth"] + ) + + self.graphWidget.addCurve( + x, y, legend="Input Data", replace=True, resetzoom=resetzoom + ) + self.graphWidget.addCurve( + x, stripBackground, legend="Strip Background", resetzoom=False + ) + self.graphWidget.addCurve( + x, snipBackground, legend="SNIP Background", resetzoom=False + ) if self._xmin is not None and self._xmax is not None: self.graphWidget.getXAxis().setLimits(self._xmin, self._xmax) class BackgroundDialog(qt.QDialog): """QDialog window featuring a :class:`BackgroundWidget`""" + def __init__(self, parent=None): qt.QDialog.__init__(self, parent) self.setWindowTitle("Strip and Snip Configuration Window") @@ -452,14 +457,15 @@ class BackgroundDialog(qt.QDialog): # self.output = ddict def accept(self): - """Update :attr:`output`, then call :meth:`QDialog.accept` - """ + """Update :attr:`output`, then call :meth:`QDialog.accept`""" self.output = self.getParameters() super(BackgroundDialog, self).accept() def sizeHint(self): - return qt.QSize(int(1.5*qt.QDialog.sizeHint(self).width()), - qt.QDialog.sizeHint(self).height()) + return qt.QSize( + int(1.5 * qt.QDialog.sizeHint(self).width()), + qt.QDialog.sizeHint(self).height(), + ) def setData(self, x, y, xmin=None, xmax=None): """See :meth:`BackgroundWidget.setData`""" @@ -498,11 +504,7 @@ def main(): x = numpy.arange(5000) # (height1, center1, fwhm1, ...) 5 peaks - params1 = (50, 500, 100, - 20, 2000, 200, - 50, 2250, 100, - 40, 3000, 75, - 23, 4000, 150) + params1 = (50, 500, 100, 20, 2000, 200, 50, 2250, 100, 40, 3000, 75, 23, 4000, 150) y0 = sum_gauss(x, *params1) # random values between [-1;1] @@ -527,7 +529,8 @@ def main(): w.parametersWidget.parametersWidget.sigBackgroundParamWidgetSignal.connect(mySlot) w.setData(x, y) w.exec() - #a.exec() + # a.exec() + if __name__ == "__main__": main() diff --git a/src/silx/gui/fit/FitConfig.py b/src/silx/gui/fit/FitConfig.py index 09dbfaa..5887b4a 100644 --- a/src/silx/gui/fit/FitConfig.py +++ b/src/silx/gui/fit/FitConfig.py @@ -45,6 +45,7 @@ class TabsDialog(qt.QDialog): This dialog defines a __len__ returning the number of tabs, and an __iter__ method yielding the tab widgets. """ + def __init__(self, parent=None): qt.QDialog.__init__(self, parent) self.tabWidget = qt.QTabWidget(self) @@ -62,9 +63,9 @@ class TabsDialog(qt.QDialog): self.buttonDefault.setText("Undo changes") layout2.addWidget(self.buttonDefault) - spacer = qt.QSpacerItem(20, 20, - qt.QSizePolicy.Expanding, - qt.QSizePolicy.Minimum) + spacer = qt.QSpacerItem( + 20, 20, qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum + ) layout2.addItem(spacer) self.buttonOk = qt.QPushButton(self) @@ -119,6 +120,7 @@ class TabsDialogData(TabsDialog): A default dictionary can be supplied when this dialog is initialized, to be used as default data for :attr:`output`. """ + def __init__(self, parent=None, modal=True, default=None): """ @@ -197,11 +199,14 @@ class ConstraintsPage(qt.QGroupBox): """Checkable QGroupBox widget filled with QCheckBox widgets, to configure the fit estimation for standard fit theories. """ + def __init__(self, parent=None, title="Set constraints"): super(ConstraintsPage, self).__init__(parent) self.setTitle(title) - self.setToolTip("Disable 'Set constraints' to remove all " + - "constraints on all fit parameters") + self.setToolTip( + "Disable 'Set constraints' to remove all " + + "constraints on all fit parameters" + ) self.setCheckable(True) layout = qt.QVBoxLayout(self) @@ -212,8 +217,7 @@ class ConstraintsPage(qt.QGroupBox): layout.addWidget(self.positiveHeightCB) self.positionInIntervalCB = qt.QCheckBox("Force position in interval", self) - self.positionInIntervalCB.setToolTip( - "Fit must position peak within X limits") + self.positionInIntervalCB.setToolTip("Fit must position peak within X limits") layout.addWidget(self.positionInIntervalCB) self.positiveFwhmCB = qt.QCheckBox("Force positive FWHM", self) @@ -226,7 +230,8 @@ class ConstraintsPage(qt.QGroupBox): self.quotedEtaCB = qt.QCheckBox("Force Eta between 0 and 1", self) self.quotedEtaCB.setToolTip( - "Fit must find Eta between 0 and 1 for pseudo-Voigt function") + "Fit must find Eta between 0 and 1 for pseudo-Voigt function" + ) layout.addWidget(self.quotedEtaCB) layout.addStretch() @@ -241,29 +246,27 @@ class ConstraintsPage(qt.QGroupBox): if default_dict is None: default_dict = {} # this one uses reverse logic: if checked, NoConstraintsFlag must be False - self.setChecked( - not default_dict.get('NoConstraintsFlag', False)) + self.setChecked(not default_dict.get("NoConstraintsFlag", False)) self.positiveHeightCB.setChecked( - default_dict.get('PositiveHeightAreaFlag', True)) + default_dict.get("PositiveHeightAreaFlag", True) + ) self.positionInIntervalCB.setChecked( - default_dict.get('QuotedPositionFlag', False)) - self.positiveFwhmCB.setChecked( - default_dict.get('PositiveFwhmFlag', True)) - self.sameFwhmCB.setChecked( - default_dict.get('SameFwhmFlag', False)) - self.quotedEtaCB.setChecked( - default_dict.get('QuotedEtaFlag', False)) + default_dict.get("QuotedPositionFlag", False) + ) + self.positiveFwhmCB.setChecked(default_dict.get("PositiveFwhmFlag", True)) + self.sameFwhmCB.setChecked(default_dict.get("SameFwhmFlag", False)) + self.quotedEtaCB.setChecked(default_dict.get("QuotedEtaFlag", False)) def get(self): """Return a dictionary of constraint flags, to be processed by the :meth:`configure` method of the selected fit theory.""" ddict = { - 'NoConstraintsFlag': not self.isChecked(), - 'PositiveHeightAreaFlag': self.positiveHeightCB.isChecked(), - 'QuotedPositionFlag': self.positionInIntervalCB.isChecked(), - 'PositiveFwhmFlag': self.positiveFwhmCB.isChecked(), - 'SameFwhmFlag': self.sameFwhmCB.isChecked(), - 'QuotedEtaFlag': self.quotedEtaCB.isChecked(), + "NoConstraintsFlag": not self.isChecked(), + "PositiveHeightAreaFlag": self.positiveHeightCB.isChecked(), + "QuotedPositionFlag": self.positionInIntervalCB.isChecked(), + "PositiveFwhmFlag": self.positiveFwhmCB.isChecked(), + "SameFwhmFlag": self.sameFwhmCB.isChecked(), + "QuotedEtaFlag": self.quotedEtaCB.isChecked(), } return ddict @@ -276,8 +279,9 @@ class SearchPage(qt.QWidget): self.manualFwhmGB = qt.QGroupBox("Define FWHM manually", self) self.manualFwhmGB.setCheckable(True) self.manualFwhmGB.setToolTip( - "If disabled, the FWHM parameter used for peak search is " + - "estimated based on the highest peak in the data") + "If disabled, the FWHM parameter used for peak search is " + + "estimated based on the highest peak in the data" + ) layout.addWidget(self.manualFwhmGB) # ------------ GroupBox fwhm-------------------------- layout2 = qt.QHBoxLayout(self.manualFwhmGB) @@ -295,8 +299,9 @@ class SearchPage(qt.QWidget): self.manualScalingGB = qt.QGroupBox("Define scaling manually", self) self.manualScalingGB.setCheckable(True) self.manualScalingGB.setToolTip( - "If disabled, the Y scaling used for peak search is " + - "estimated automatically") + "If disabled, the Y scaling used for peak search is " + + "estimated automatically" + ) layout.addWidget(self.manualScalingGB) # ------------ GroupBox scaling----------------------- layout3 = qt.QHBoxLayout(self.manualScalingGB) @@ -307,8 +312,8 @@ class SearchPage(qt.QWidget): self.yScalingEntry = qt.QLineEdit(self.manualScalingGB) self.yScalingEntry.setToolTip( - "Data values will be multiplied by this value prior to peak" + - " search") + "Data values will be multiplied by this value prior to peak" + " search" + ) self.yScalingEntry.setValidator(qt.QDoubleValidator(self)) layout3.addWidget(self.yScalingEntry) # ---------------------------------------------------- @@ -323,9 +328,10 @@ class SearchPage(qt.QWidget): self.sensitivityEntry = qt.QLineEdit(containerWidget) self.sensitivityEntry.setToolTip( - "Peak search sensitivity threshold, expressed as a multiple " + - "of the standard deviation of the noise.\nMinimum value is 1 " + - "(to be detected, peak must be higher than the estimated noise)") + "Peak search sensitivity threshold, expressed as a multiple " + + "of the standard deviation of the noise.\nMinimum value is 1 " + + "(to be detected, peak must be higher than the estimated noise)" + ) sensivalidator = qt.QDoubleValidator(self) sensivalidator.setBottom(1.0) self.sensitivityEntry.setValidator(sensivalidator) @@ -335,8 +341,9 @@ class SearchPage(qt.QWidget): self.forcePeakPresenceCB = qt.QCheckBox("Force peak presence", self) self.forcePeakPresenceCB.setToolTip( - "If peak search algorithm is unsuccessful, place one peak " + - "at the maximum of the curve") + "If peak search algorithm is unsuccessful, place one peak " + + "at the maximum of the curve" + ) layout.addWidget(self.forcePeakPresenceCB) layout.addStretch() @@ -350,29 +357,25 @@ class SearchPage(qt.QWidget): a parameter, its values are used as default values.""" if default_dict is None: default_dict = {} - self.manualFwhmGB.setChecked( - not default_dict.get('AutoFwhm', True)) - self.fwhmPointsSpin.setValue( - default_dict.get('FwhmPoints', 8)) - self.sensitivityEntry.setText( - str(default_dict.get('Sensitivity', 1.0))) - self.manualScalingGB.setChecked( - not default_dict.get('AutoScaling', False)) - self.yScalingEntry.setText( - str(default_dict.get('Yscaling', 1.0))) + self.manualFwhmGB.setChecked(not default_dict.get("AutoFwhm", True)) + self.fwhmPointsSpin.setValue(default_dict.get("FwhmPoints", 8)) + self.sensitivityEntry.setText(str(default_dict.get("Sensitivity", 1.0))) + self.manualScalingGB.setChecked(not default_dict.get("AutoScaling", False)) + self.yScalingEntry.setText(str(default_dict.get("Yscaling", 1.0))) self.forcePeakPresenceCB.setChecked( - default_dict.get('ForcePeakPresence', False)) + default_dict.get("ForcePeakPresence", False) + ) def get(self): """Return a dictionary of peak search parameters, to be processed by the :meth:`configure` method of the selected fit theory.""" ddict = { - 'AutoFwhm': not self.manualFwhmGB.isChecked(), - 'FwhmPoints': self.fwhmPointsSpin.value(), - 'Sensitivity': safe_float(self.sensitivityEntry.text()), - 'AutoScaling': not self.manualScalingGB.isChecked(), - 'Yscaling': safe_float(self.yScalingEntry.text()), - 'ForcePeakPresence': self.forcePeakPresenceCB.isChecked() + "AutoFwhm": not self.manualFwhmGB.isChecked(), + "FwhmPoints": self.fwhmPointsSpin.value(), + "Sensitivity": safe_float(self.sensitivityEntry.text()), + "AutoScaling": not self.manualScalingGB.isChecked(), + "Yscaling": safe_float(self.yScalingEntry.text()), + "ForcePeakPresence": self.forcePeakPresenceCB.isChecked(), } return ddict @@ -380,60 +383,69 @@ class SearchPage(qt.QWidget): class BackgroundPage(qt.QGroupBox): """Background subtraction configuration, specific to fittheories estimation functions.""" - def __init__(self, parent=None, - title="Subtract strip background prior to estimation"): + + def __init__( + self, parent=None, title="Subtract strip background prior to estimation" + ): super(BackgroundPage, self).__init__(parent) self.setTitle(title) self.setCheckable(True) self.setToolTip( - "The strip algorithm strips away peaks to compute the " + - "background signal.\nAt each iteration, a sample is compared " + - "to the average of the two samples at a given distance in both" + - " directions,\n and if its value is higher than the average," - "it is replaced by the average.") + "The strip algorithm strips away peaks to compute the " + + "background signal.\nAt each iteration, a sample is compared " + + "to the average of the two samples at a given distance in both" + + " directions,\n and if its value is higher than the average," + "it is replaced by the average." + ) layout = qt.QGridLayout(self) self.setLayout(layout) for i, label_text in enumerate( - ["Strip width (in samples)", - "Number of iterations", - "Strip threshold factor"]): + [ + "Strip width (in samples)", + "Number of iterations", + "Strip threshold factor", + ] + ): label = qt.QLabel(label_text) layout.addWidget(label, i, 0) self.stripWidthSpin = qt.QSpinBox(self) self.stripWidthSpin.setToolTip( - "Width, in number of samples, of the strip operator") + "Width, in number of samples, of the strip operator" + ) self.stripWidthSpin.setRange(1, 999999) layout.addWidget(self.stripWidthSpin, 0, 1) self.numIterationsSpin = qt.QSpinBox(self) - self.numIterationsSpin.setToolTip( - "Number of iterations of the strip algorithm") + self.numIterationsSpin.setToolTip("Number of iterations of the strip algorithm") self.numIterationsSpin.setRange(1, 999999) layout.addWidget(self.numIterationsSpin, 1, 1) self.thresholdFactorEntry = qt.QLineEdit(self) self.thresholdFactorEntry.setToolTip( - "Factor used by the strip algorithm to decide whether a sample" + - "value should be stripped.\nThe value must be higher than the " + - "average of the 2 samples at +- w times this factor.\n") + "Factor used by the strip algorithm to decide whether a sample" + + "value should be stripped.\nThe value must be higher than the " + + "average of the 2 samples at +- w times this factor.\n" + ) self.thresholdFactorEntry.setValidator(qt.QDoubleValidator(self)) layout.addWidget(self.thresholdFactorEntry, 2, 1) self.smoothStripGB = qt.QGroupBox("Apply smoothing prior to strip", self) self.smoothStripGB.setCheckable(True) self.smoothStripGB.setToolTip( - "Apply a smoothing before subtracting strip background" + - " in fit and estimate processes") + "Apply a smoothing before subtracting strip background" + + " in fit and estimate processes" + ) smoothlayout = qt.QHBoxLayout(self.smoothStripGB) label = qt.QLabel("Smoothing width (Savitsky-Golay)") smoothlayout.addWidget(label) self.smoothingWidthSpin = qt.QSpinBox(self) self.smoothingWidthSpin.setToolTip( - "Width parameter for Savitsky-Golay smoothing (number of samples, must be odd)") + "Width parameter for Savitsky-Golay smoothing (number of samples, must be odd)" + ) self.smoothingWidthSpin.setRange(3, 101) self.smoothingWidthSpin.setSingleStep(2) smoothlayout.addWidget(self.smoothingWidthSpin) @@ -452,31 +464,25 @@ class BackgroundPage(qt.QGroupBox): if default_dict is None: default_dict = {} - self.setChecked( - default_dict.get('StripBackgroundFlag', True)) + self.setChecked(default_dict.get("StripBackgroundFlag", True)) - self.stripWidthSpin.setValue( - default_dict.get('StripWidth', 2)) - self.numIterationsSpin.setValue( - default_dict.get('StripIterations', 5000)) - self.thresholdFactorEntry.setText( - str(default_dict.get('StripThreshold', 1.0))) - self.smoothStripGB.setChecked( - default_dict.get('SmoothingFlag', False)) - self.smoothingWidthSpin.setValue( - default_dict.get('SmoothingWidth', 3)) + self.stripWidthSpin.setValue(default_dict.get("StripWidth", 2)) + self.numIterationsSpin.setValue(default_dict.get("StripIterations", 5000)) + self.thresholdFactorEntry.setText(str(default_dict.get("StripThreshold", 1.0))) + self.smoothStripGB.setChecked(default_dict.get("SmoothingFlag", False)) + self.smoothingWidthSpin.setValue(default_dict.get("SmoothingWidth", 3)) def get(self): """Return a dictionary of background subtraction parameters, to be processed by the :meth:`configure` method of the selected fit theory. """ ddict = { - 'StripBackgroundFlag': self.isChecked(), - 'StripWidth': self.stripWidthSpin.value(), - 'StripIterations': self.numIterationsSpin.value(), - 'StripThreshold': safe_float(self.thresholdFactorEntry.text()), - 'SmoothingFlag': self.smoothStripGB.isChecked(), - 'SmoothingWidth': self.smoothingWidthSpin.value() + "StripBackgroundFlag": self.isChecked(), + "StripWidth": self.stripWidthSpin.value(), + "StripIterations": self.numIterationsSpin.value(), + "StripThreshold": safe_float(self.thresholdFactorEntry.text()), + "SmoothingFlag": self.smoothStripGB.isChecked(), + "SmoothingWidth": self.smoothingWidthSpin.value(), } return ddict @@ -538,5 +544,6 @@ def main(): a.exec() + if __name__ == "__main__": main() diff --git a/src/silx/gui/fit/FitWidget.py b/src/silx/gui/fit/FitWidget.py index 88f95cf..2487c23 100644 --- a/src/silx/gui/fit/FitWidget.py +++ b/src/silx/gui/fit/FitWidget.py @@ -1,6 +1,6 @@ # /*########################################################################## # -# Copyright (c) 2004-2021 European Synchrotron Radiation Facility +# Copyright (c) 2004-2023 European Synchrotron Radiation Facility # # This file is part of the PyMca X-ray Fluorescence Toolkit developed at # the ESRF by the Software group. @@ -46,11 +46,14 @@ import traceback from silx.math.fit import fittheories from silx.math.fit import fitmanager, functions from silx.gui import qt -from .FitWidgets import (FitActionsButtons, FitStatusLines, - FitConfigWidget, ParametersTab) +from .FitWidgets import ( + FitActionsButtons, + FitStatusLines, + FitConfigWidget, + ParametersTab, +) from .FitConfig import getFitConfigDialog from .BackgroundWidget import getBgDialog, BackgroundDialog -from ...utils.deprecation import deprecated DEBUG = 0 _logger = logging.getLogger(__name__) @@ -88,6 +91,7 @@ class FitWidget(qt.QWidget): .. image:: img/FitWidget.png """ + sigFitWidgetSignal = qt.Signal(object) """This signal is emitted by the estimation and fit methods. It carries a dictionary with two items: @@ -105,8 +109,15 @@ class FitWidget(qt.QWidget): :attr:`silx.math.fit.fitmanager.FitManager.fit_results`) """ - def __init__(self, parent=None, title=None, fitmngr=None, - enableconfig=True, enablestatus=True, enablebuttons=True): + def __init__( + self, + parent=None, + title=None, + fitmngr=None, + enableconfig=True, + enablestatus=True, + enablebuttons=True, + ): """ :param parent: Parent widget @@ -199,15 +210,16 @@ class FitWidget(qt.QWidget): """Function selector and configuration widget""" self.guiConfig.FunConfigureButton.clicked.connect( - self.__funConfigureGuiSlot) - self.guiConfig.BgConfigureButton.clicked.connect( - self.__bgConfigureGuiSlot) + self.__funConfigureGuiSlot + ) + self.guiConfig.BgConfigureButton.clicked.connect(self.__bgConfigureGuiSlot) self.guiConfig.WeightCheckBox.setChecked( - self.fitconfig.get("WeightFlag", False)) + self.fitconfig.get("WeightFlag", False) + ) self.guiConfig.WeightCheckBox.stateChanged[int].connect(self.weightEvent) - if qt.BINDING in ('PySide2', 'PyQt5'): + if qt.BINDING == "PyQt5": self.guiConfig.BkgComBox.activated[str].connect(self.bkgEvent) self.guiConfig.FunComBox.activated[str].connect(self.funEvent) else: # Qt6 @@ -262,21 +274,21 @@ class FitWidget(qt.QWidget): # associate silx.gui.fit.FitConfig with all theories # Users can later associate their own custom dialogs to # replace the default. - configdialog = getFitConfigDialog(parent=self, - default=self.fitconfig) + configdialog = getFitConfigDialog(parent=self, default=self.fitconfig) for theory in self.fitmanager.theories: self.associateConfigDialog(theory, configdialog) for bgtheory in self.fitmanager.bgtheories: - self.associateConfigDialog(bgtheory, configdialog, - theory_is_background=True) + self.associateConfigDialog( + bgtheory, configdialog, theory_is_background=True + ) # associate silx.gui.fit.BackgroundWidget with Strip and Snip - bgdialog = getBgDialog(parent=self, - default=self.fitconfig) + bgdialog = getBgDialog(parent=self, default=self.fitconfig) for bgtheory in ["Strip", "Snip"]: if bgtheory in self.fitmanager.bgtheories: - self.associateConfigDialog(bgtheory, bgdialog, - theory_is_background=True) + self.associateConfigDialog( + bgtheory, bgdialog, theory_is_background=True + ) def _populateFunctions(self): """Fill combo-boxes with fit theories and background theories @@ -286,16 +298,18 @@ class FitWidget(qt.QWidget): for theory_name in self.fitmanager.bgtheories: self.guiConfig.BkgComBox.addItem(theory_name) self.guiConfig.BkgComBox.setItemData( - self.guiConfig.BkgComBox.findText(theory_name), - self.fitmanager.bgtheories[theory_name].description, - qt.Qt.ToolTipRole) + self.guiConfig.BkgComBox.findText(theory_name), + self.fitmanager.bgtheories[theory_name].description, + qt.Qt.ToolTipRole, + ) for theory_name in self.fitmanager.theories: self.guiConfig.FunComBox.addItem(theory_name) self.guiConfig.FunComBox.setItemData( - self.guiConfig.FunComBox.findText(theory_name), - self.fitmanager.theories[theory_name].description, - qt.Qt.ToolTipRole) + self.guiConfig.FunComBox.findText(theory_name), + self.fitmanager.theories[theory_name].description, + qt.Qt.ToolTipRole, + ) # - activate selected fit theory (if any) # - activate selected bg theory (if any) @@ -319,10 +333,6 @@ class FitWidget(qt.QWidget): configuration.update(self.configure()) - @deprecated(replacement='setData', since_version='0.3.0') - def setdata(self, x, y, sigmay=None, xmin=None, xmax=None): - self.setData(x, y, sigmay, xmin, xmax) - def setData(self, x=None, y=None, sigmay=None, xmin=None, xmax=None): """Set data to be fitted. @@ -345,14 +355,14 @@ class FitWidget(qt.QWidget): else: self.guibuttons.EstimateButton.setEnabled(True) self.guibuttons.StartFitButton.setEnabled(True) - self.fitmanager.setdata(x=x, y=y, sigmay=sigmay, - xmin=xmin, xmax=xmax) + self.fitmanager.setdata(x=x, y=y, sigmay=sigmay, xmin=xmin, xmax=xmax) for config_dialog in self.bgconfigdialogs.values(): if isinstance(config_dialog, BackgroundDialog): config_dialog.setData(x, y, xmin=xmin, xmax=xmax) - def associateConfigDialog(self, theory_name, config_widget, - theory_is_background=False): + def associateConfigDialog( + self, theory_name, config_widget, theory_is_background=False + ): """Associate an instance of custom configuration dialog widget to a fit theory or to a background theory. @@ -372,23 +382,30 @@ class FitWidget(qt.QWidget): methods (*show*, *exec*, *result*, *setDefault*) or the mandatory attribute (*output*). """ - theories = self.fitmanager.bgtheories if theory_is_background else\ - self.fitmanager.theories + theories = ( + self.fitmanager.bgtheories + if theory_is_background + else self.fitmanager.theories + ) if theory_name not in theories: raise KeyError("%s does not match an existing fitmanager theory") if config_widget is not None: - if (not hasattr(config_widget, "exec") and - not hasattr(config_widget, "exec_")): + if not hasattr(config_widget, "exec") and not hasattr( + config_widget, "exec_" + ): raise AttributeError( - "Custom configuration widget must define exec or exec_") + "Custom configuration widget must define exec or exec_" + ) for mandatory_attr in ["show", "result", "output"]: if not hasattr(config_widget, mandatory_attr): raise AttributeError( - "Custom configuration widget must define " + - "attribute or method " + mandatory_attr) + "Custom configuration widget must define " + + "attribute or method " + + mandatory_attr + ) if theory_is_background: self.bgconfigdialogs[theory_name] = config_widget @@ -427,25 +444,23 @@ class FitWidget(qt.QWidget): configuration.update(self.configure(**newconfiguration)) # set fit function theory try: - i = 1 + \ - list(self.fitmanager.theories.keys()).index( - self.fitmanager.selectedtheory) + i = 1 + list(self.fitmanager.theories.keys()).index( + self.fitmanager.selectedtheory + ) self.guiConfig.FunComBox.setCurrentIndex(i) self.funEvent(self.fitmanager.selectedtheory) except ValueError: - _logger.error("Function not in list %s", - self.fitmanager.selectedtheory) + _logger.error("Function not in list %s", self.fitmanager.selectedtheory) self.funEvent(list(self.fitmanager.theories.keys())[0]) # current background try: - i = 1 + \ - list(self.fitmanager.bgtheories.keys()).index( - self.fitmanager.selectedbg) + i = 1 + list(self.fitmanager.bgtheories.keys()).index( + self.fitmanager.selectedbg + ) self.guiConfig.BkgComBox.setCurrentIndex(i) self.bkgEvent(self.fitmanager.selectedbg) except ValueError: - _logger.error("Background not in list %s", - self.fitmanager.selectedbg) + _logger.error("Background not in list %s", self.fitmanager.selectedbg) self.bkgEvent(list(self.fitmanager.bgtheories.keys())[0]) # update the Gui @@ -509,8 +524,7 @@ class FitWidget(qt.QWidget): theory_name = self.fitmanager.selectedtheory estimation_function = self.fitmanager.theories[theory_name].estimate if estimation_function is not None: - ddict = {'event': 'EstimateStarted', - 'data': None} + ddict = {"event": "EstimateStarted", "data": None} self._emitSignal(ddict) self.fitmanager.estimate(callback=self.fitStatus) else: @@ -520,34 +534,25 @@ class FitWidget(qt.QWidget): text += "the initial parameters. Please, fill them\n" text += "yourself in the table and press Start Fit\n" msg.setText(text) - msg.setWindowTitle('FitWidget Message') + msg.setWindowTitle("FitWidget Message") msg.exec() return - except Exception as e: # noqa (we want to catch and report all errors) - _logger.warning('Estimate error: %s', traceback.format_exc()) + except Exception as e: # noqa (we want to catch and report all errors) + _logger.warning("Estimate error: %s", traceback.format_exc()) msg = qt.QMessageBox(self) msg.setIcon(qt.QMessageBox.Critical) msg.setWindowTitle("Estimate Error") msg.setText("Error on estimate: %s" % e) msg.exec() - ddict = { - 'event': 'EstimateFailed', - 'data': None} + ddict = {"event": "EstimateFailed", "data": None} self._emitSignal(ddict) return - self.guiParameters.fillFromFit( - self.fitmanager.fit_results, view='Fit') - self.guiParameters.removeAllViews(keep='Fit') - ddict = { - 'event': 'EstimateFinished', - 'data': self.fitmanager.fit_results} + self.guiParameters.fillFromFit(self.fitmanager.fit_results, view="Fit") + self.guiParameters.removeAllViews(keep="Fit") + ddict = {"event": "EstimateFinished", "data": self.fitmanager.fit_results} self._emitSignal(ddict) - @deprecated(replacement='startFit', since_version='0.3.0') - def startfit(self): - self.startFit() - def startFit(self): """Run fit, then emit :attr:`sigFitWidgetSignal` with a dictionary containing a status message and a list of fit @@ -563,31 +568,23 @@ class FitWidget(qt.QWidget): """ self.fitmanager.fit_results = self.guiParameters.getFitResults() try: - ddict = {'event': 'FitStarted', - 'data': None} + ddict = {"event": "FitStarted", "data": None} self._emitSignal(ddict) self.fitmanager.runfit(callback=self.fitStatus) except Exception as e: # noqa (we want to catch and report all errors) - _logger.warning('Estimate error: %s', traceback.format_exc()) + _logger.warning("Estimate error: %s", traceback.format_exc()) msg = qt.QMessageBox(self) msg.setIcon(qt.QMessageBox.Critical) msg.setWindowTitle("Fit Error") msg.setText("Error on Fit: %s" % e) msg.exec() - ddict = { - 'event': 'FitFailed', - 'data': None - } + ddict = {"event": "FitFailed", "data": None} self._emitSignal(ddict) return - self.guiParameters.fillFromFit( - self.fitmanager.fit_results, view='Fit') - self.guiParameters.removeAllViews(keep='Fit') - ddict = { - 'event': 'FitFinished', - 'data': self.fitmanager.fit_results - } + self.guiParameters.fillFromFit(self.fitmanager.fit_results, view="Fit") + self.guiParameters.removeAllViews(keep="Fit") + ddict = {"event": "FitFinished", "data": self.fitmanager.fit_results} self._emitSignal(ddict) return @@ -598,15 +595,17 @@ class FitWidget(qt.QWidget): self.fitmanager.setbackground(bgtheory) else: functionsfile = qt.QFileDialog.getOpenFileName( - self, "Select python module with your function(s)", "", - "Python Files (*.py);;All Files (*)") + self, + "Select python module with your function(s)", + "", + "Python Files (*.py);;All Files (*)", + ) if len(functionsfile): try: self.fitmanager.loadbgtheories(functionsfile) except ImportError: - qt.QMessageBox.critical(self, "ERROR", - "Function not imported") + qt.QMessageBox.critical(self, "ERROR", "Function not imported") return else: # empty the ComboBox @@ -616,9 +615,9 @@ class FitWidget(qt.QWidget): for key in self.fitmanager.bgtheories: self.guiConfig.BkgComBox.addItem(str(key)) - i = 1 + \ - list(self.fitmanager.bgtheories.keys()).index( - self.fitmanager.selectedbg) + i = 1 + list(self.fitmanager.bgtheories.keys()).index( + self.fitmanager.selectedbg + ) self.guiConfig.BkgComBox.setCurrentIndex(i) self.__initialParameters() @@ -637,15 +636,17 @@ class FitWidget(qt.QWidget): else: # open a load file dialog functionsfile = qt.QFileDialog.getOpenFileName( - self, "Select python module with your function(s)", "", - "Python Files (*.py);;All Files (*)") + self, + "Select python module with your function(s)", + "", + "Python Files (*.py);;All Files (*)", + ) if len(functionsfile): try: self.fitmanager.loadtheories(functionsfile) except ImportError: - qt.QMessageBox.critical(self, "ERROR", - "Function not imported") + qt.QMessageBox.critical(self, "ERROR", "Function not imported") return else: # empty the ComboBox @@ -655,9 +656,9 @@ class FitWidget(qt.QWidget): for key in self.fitmanager.theories: self.guiConfig.FunComBox.addItem(str(key)) - i = 1 + \ - list(self.fitmanager.theories.keys()).index( - self.fitmanager.selectedtheory) + i = 1 + list(self.fitmanager.theories.keys()).index( + self.fitmanager.selectedtheory + ) self.guiConfig.FunComBox.setCurrentIndex(i) self.__initialParameters() @@ -682,45 +683,52 @@ class FitWidget(qt.QWidget): self.fitmanager.fit_results = [] for pname in self.fitmanager.bgtheories[self.fitmanager.selectedbg].parameters: self.fitmanager.parameter_names.append(pname) - self.fitmanager.fit_results.append({'name': pname, - 'estimation': 0, - 'group': 0, - 'code': 'FREE', - 'cons1': 0, - 'cons2': 0, - 'fitresult': 0.0, - 'sigma': 0.0, - 'xmin': None, - 'xmax': None}) + self.fitmanager.fit_results.append( + { + "name": pname, + "estimation": 0, + "group": 0, + "code": "FREE", + "cons1": 0, + "cons2": 0, + "fitresult": 0.0, + "sigma": 0.0, + "xmin": None, + "xmax": None, + } + ) if self.fitmanager.selectedtheory is not None: theory = self.fitmanager.selectedtheory for pname in self.fitmanager.theories[theory].parameters: self.fitmanager.parameter_names.append(pname + "1") - self.fitmanager.fit_results.append({'name': pname + "1", - 'estimation': 0, - 'group': 1, - 'code': 'FREE', - 'cons1': 0, - 'cons2': 0, - 'fitresult': 0.0, - 'sigma': 0.0, - 'xmin': None, - 'xmax': None}) - - self.guiParameters.fillFromFit( - self.fitmanager.fit_results, view='Fit') + self.fitmanager.fit_results.append( + { + "name": pname + "1", + "estimation": 0, + "group": 1, + "code": "FREE", + "cons1": 0, + "cons2": 0, + "fitresult": 0.0, + "sigma": 0.0, + "xmin": None, + "xmax": None, + } + ) + + self.guiParameters.fillFromFit(self.fitmanager.fit_results, view="Fit") def fitStatus(self, data): """Set *status* and *chisq* in status bar""" - if 'chisq' in data: - if data['chisq'] is None: + if "chisq" in data: + if data["chisq"] is None: self.guistatus.ChisqLine.setText(" ") else: - chisq = data['chisq'] + chisq = data["chisq"] self.guistatus.ChisqLine.setText("%6.2f" % chisq) - if 'status' in data: - status = data['status'] + if "status" in data: + status = data["status"] self.guistatus.StatusLine.setText(str(status)) def dismiss(self): @@ -734,13 +742,29 @@ if __name__ == "__main__": x = numpy.arange(1500).astype(numpy.float64) constant_bg = 3.14 - p = [1000, 100., 30.0, - 500, 300., 25., - 1700, 500., 35., - 750, 700., 30.0, - 1234, 900., 29.5, - 302, 1100., 30.5, - 75, 1300., 21.] + p = [ + 1000, + 100.0, + 30.0, + 500, + 300.0, + 25.0, + 1700, + 500.0, + 35.0, + 750, + 700.0, + 30.0, + 1234, + 900.0, + 29.5, + 302, + 1100.0, + 30.5, + 75, + 1300.0, + 21.0, + ] y = functions.sum_gauss(x, *p) + constant_bg a = qt.QApplication(sys.argv) diff --git a/src/silx/gui/fit/FitWidgets.py b/src/silx/gui/fit/FitWidgets.py index 7bcf28c..b7aef07 100644 --- a/src/silx/gui/fit/FitWidgets.py +++ b/src/silx/gui/fit/FitWidgets.py @@ -1,5 +1,5 @@ # /*########################################################################## -# Copyright (C) 2004-2021 European Synchrotron Radiation Facility +# Copyright (C) 2004-2023 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -23,8 +23,6 @@ """Collection of widgets used to build :class:`silx.gui.fit.FitWidget.FitWidget`""" -from collections import OrderedDict - from silx.gui import qt from silx.gui.fit.Parameters import Parameters @@ -69,17 +67,17 @@ class FitActionsButtons(qt.QWidget): self.EstimateButton = qt.QPushButton(self) self.EstimateButton.setText("Estimate") layout.addWidget(self.EstimateButton) - spacer = qt.QSpacerItem(20, 20, - qt.QSizePolicy.Expanding, - qt.QSizePolicy.Minimum) + spacer = qt.QSpacerItem( + 20, 20, qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum + ) layout.addItem(spacer) self.StartFitButton = qt.QPushButton(self) self.StartFitButton.setText("Start Fit") layout.addWidget(self.StartFitButton) - spacer_2 = qt.QSpacerItem(20, 20, - qt.QSizePolicy.Expanding, - qt.QSizePolicy.Minimum) + spacer_2 = qt.QSpacerItem( + 20, 20, qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum + ) layout.addItem(spacer_2) self.DismissButton = qt.QPushButton(self) @@ -148,6 +146,7 @@ class FitConfigWidget(qt.QWidget): - open a dialog for modifying advanced parameters through :attr:`FunConfigureButton` """ + def __init__(self, parent=None): qt.QWidget.__init__(self, parent) @@ -163,9 +162,11 @@ class FitConfigWidget(qt.QWidget): self.FunComBox = qt.QComboBox(self) self.FunComBox.addItem("Add Function(s)") - self.FunComBox.setItemData(self.FunComBox.findText("Add Function(s)"), - "Load fit theories from a file", - qt.Qt.ToolTipRole) + self.FunComBox.setItemData( + self.FunComBox.findText("Add Function(s)"), + "Load fit theories from a file", + qt.Qt.ToolTipRole, + ) layout.addWidget(self.FunComBox, 0, 1) self.BkgLabel = qt.QLabel(self) @@ -174,28 +175,33 @@ class FitConfigWidget(qt.QWidget): self.BkgComBox = qt.QComboBox(self) self.BkgComBox.addItem("Add Background(s)") - self.BkgComBox.setItemData(self.BkgComBox.findText("Add Background(s)"), - "Load background theories from a file", - qt.Qt.ToolTipRole) + self.BkgComBox.setItemData( + self.BkgComBox.findText("Add Background(s)"), + "Load background theories from a file", + qt.Qt.ToolTipRole, + ) layout.addWidget(self.BkgComBox, 1, 1) self.FunConfigureButton = qt.QPushButton(self) self.FunConfigureButton.setText("Configure") self.FunConfigureButton.setToolTip( - "Open a configuration dialog for the selected function") + "Open a configuration dialog for the selected function" + ) layout.addWidget(self.FunConfigureButton, 0, 2) self.BgConfigureButton = qt.QPushButton(self) self.BgConfigureButton.setText("Configure") self.BgConfigureButton.setToolTip( - "Open a configuration dialog for the selected background") + "Open a configuration dialog for the selected background" + ) layout.addWidget(self.BgConfigureButton, 1, 2) self.WeightCheckBox = qt.QCheckBox(self) self.WeightCheckBox.setText("Weighted fit") self.WeightCheckBox.setToolTip( - "Enable usage of weights in the least-square problem.\n Use" + - " the uncertainties (sigma) if provided, else use sqrt(y).") + "Enable usage of weights in the least-square problem.\n Use" + + " the uncertainties (sigma) if provided, else use sqrt(y)." + ) layout.addWidget(self.WeightCheckBox, 0, 3, 2, 1) @@ -281,7 +287,7 @@ class ParametersTab(qt.QTabWidget): self.setWindowTitle(name) self.setContentsMargins(0, 0, 0, 0) - self.views = OrderedDict() + self.views = {} """Dictionary of views. Keys are view names, items are :class:`Parameters` widgets""" @@ -310,8 +316,8 @@ class ParametersTab(qt.QTabWidget): view = self.latest_view else: raise KeyError( - "No view available. You must specify a view" + - " name the first time you call this method." + "No view available. You must specify a view" + + " name the first time you call this method." ) if view in self.tables.keys(): @@ -403,7 +409,7 @@ class ParametersTab(qt.QTabWidget): text += "<tr>" ncols = table.columnCount() for l in range(ncols): - text += ('<td align="left" bgcolor="%s"><b>' % hcolor) + text += '<td align="left" bgcolor="%s"><b>' % hcolor text += str(table.horizontalHeaderItem(l).text()) text += "</b></td>" text += "</tr>" @@ -437,11 +443,9 @@ class ParametersTab(qt.QTabWidget): else: finalcolor = "white" if c < 2: - text += ('<td align="left" bgcolor="%s">%s' % - (finalcolor, b)) + text += '<td align="left" bgcolor="%s">%s' % (finalcolor, b) else: - text += ('<td align="right" bgcolor="%s">%s' % - (finalcolor, b)) + text += '<td align="right" bgcolor="%s">%s' % (finalcolor, b) text += newtext if len(b): text += "</td>" @@ -505,14 +509,18 @@ def test(): fit = fitmanager.FitManager(x=x, y=y1) fitfuns = fittheories.FitTheories() - fit.addtheory(name="Gaussian", - function=functions.sum_gauss, - parameters=("height", "peak center", "fwhm"), - estimate=fitfuns.estimate_height_position_fwhm) - fit.settheory('Gaussian') - fit.configure(PositiveFwhmFlag=True, - PositiveHeightAreaFlag=True, - AutoFwhm=True,) + fit.addtheory( + name="Gaussian", + function=functions.sum_gauss, + parameters=("height", "peak center", "fwhm"), + estimate=fitfuns.estimate_height_position_fwhm, + ) + fit.settheory("Gaussian") + fit.configure( + PositiveFwhmFlag=True, + PositiveHeightAreaFlag=True, + AutoFwhm=True, + ) # Fit fit.estimate() @@ -520,26 +528,27 @@ def test(): w = ParametersTab() w.show() - w.fillFromFit(fit.fit_results, view='Gaussians') + w.fillFromFit(fit.fit_results, view="Gaussians") - y2 = functions.sum_splitgauss(x, - 100, 400, 100, 40, - 10, 600, 50, 500, - 80, 850, 10, 50) + y2 = functions.sum_splitgauss( + x, 100, 400, 100, 40, 10, 600, 50, 500, 80, 850, 10, 50 + ) fit.setdata(x=x, y=y2) # Define new theory - fit.addtheory(name="Asymetric gaussian", - function=functions.sum_splitgauss, - parameters=("height", "peak center", "left fwhm", "right fwhm"), - estimate=fitfuns.estimate_splitgauss) - fit.settheory('Asymetric gaussian') + fit.addtheory( + name="Asymetric gaussian", + function=functions.sum_splitgauss, + parameters=("height", "peak center", "left fwhm", "right fwhm"), + estimate=fitfuns.estimate_splitgauss, + ) + fit.settheory("Asymetric gaussian") # Fit fit.estimate() fit.runfit() - w.fillFromFit(fit.fit_results, view='Asymetric gaussians') + w.fillFromFit(fit.fit_results, view="Asymetric gaussians") # Plot pw = PlotWindow(control=True) diff --git a/src/silx/gui/fit/Parameters.py b/src/silx/gui/fit/Parameters.py index e9601a8..bd2605e 100644 --- a/src/silx/gui/fit/Parameters.py +++ b/src/silx/gui/fit/Parameters.py @@ -1,5 +1,5 @@ # /*########################################################################## -# Copyright (C) 2004-2021 European Synchrotron Radiation Facility +# Copyright (C) 2004-2023 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -27,7 +27,6 @@ __license__ = "MIT" __date__ = "25/11/2016" import sys -from collections import OrderedDict from silx.gui import qt from silx.gui.widgets.TableWidget import TableWidget @@ -55,6 +54,7 @@ class QComboTableItem(qt.QComboBox): :param row: Row number of the table cell containing this widget :param col: Column number of the table cell containing this widget""" + sigCellChanged = qt.Signal(int, int) """Signal emitted when this ``QComboBox`` is activated. A ``(row, column)`` tuple is passed.""" @@ -78,6 +78,7 @@ class QCheckBoxItem(qt.QCheckBox): :param row: Row number of the table cell containing this widget :param col: Column number of the table cell containing this widget""" + sigCellChanged = qt.Signal(int, int) """Signal emitted when this ``QCheckBox`` is clicked. A ``(row, column)`` tuple is passed.""" @@ -106,22 +107,39 @@ class Parameters(TableWidget): peak. :type paramlist: list[str] or None """ + def __init__(self, parent=None, paramlist=None): TableWidget.__init__(self, parent) self.setContentsMargins(0, 0, 0, 0) - labels = ['Parameter', 'Estimation', 'Fit Value', 'Sigma', - 'Constraints', 'Min/Parame', 'Max/Factor/Delta'] - tooltips = ["Fit parameter name", - "Estimated value for fit parameter. You can edit this column.", - "Actual value for parameter, after fit", - "Uncertainty (same unit as the parameter)", - "Constraint to be applied to the parameter for fit", - "First parameter for constraint (name of another param or min value)", - "Second parameter for constraint (max value, or factor/delta)"] - - self.columnKeys = ['name', 'estimation', 'fitresult', - 'sigma', 'code', 'val1', 'val2'] + labels = [ + "Parameter", + "Estimation", + "Fit Value", + "Sigma", + "Constraints", + "Min/Parame", + "Max/Factor/Delta", + ] + tooltips = [ + "Fit parameter name", + "Estimated value for fit parameter. You can edit this column.", + "Actual value for parameter, after fit", + "Uncertainty (same unit as the parameter)", + "Constraint to be applied to the parameter for fit", + "First parameter for constraint (name of another param or min value)", + "Second parameter for constraint (max value, or factor/delta)", + ] + + self.columnKeys = [ + "name", + "estimation", + "fitresult", + "sigma", + "code", + "val1", + "val2", + ] """This list assigns shorter keys to refer to columns than the displayed labels.""" @@ -133,8 +151,7 @@ class Parameters(TableWidget): for i, label in enumerate(labels): item = self.horizontalHeaderItem(i) if item is None: - item = qt.QTableWidgetItem(label, - qt.QTableWidgetItem.Type) + item = qt.QTableWidgetItem(label, qt.QTableWidgetItem.Type) self.setHorizontalHeaderItem(i, item) item.setText(label) @@ -148,7 +165,7 @@ class Parameters(TableWidget): # Initialize the table with one line per supplied parameter paramlist = paramlist if paramlist is not None else [] - self.parameters = OrderedDict() + self.parameters = {} """This attribute stores all the data in an ordered dictionary. New data can be added using :meth:`newParameterLine`. Existing data can be modified using :meth:`configureLine` @@ -184,8 +201,17 @@ class Parameters(TableWidget): for line, param in enumerate(paramlist): self.newParameterLine(param, line) - self.code_options = ["FREE", "POSITIVE", "QUOTED", "FIXED", - "FACTOR", "DELTA", "SUM", "IGNORE", "ADD"] + self.code_options = [ + "FREE", + "POSITIVE", + "QUOTED", + "FIXED", + "FACTOR", + "DELTA", + "SUM", + "IGNORE", + "ADD", + ] """Possible values in the combo boxes in the 'Constraints' column. """ @@ -210,43 +236,46 @@ class Parameters(TableWidget): self.setRowCount(line + 1) # default configuration for fit parameters - self.parameters[param] = OrderedDict((('line', line), - ('estimation', '0'), - ('fitresult', ''), - ('sigma', ''), - ('code', 'FREE'), - ('val1', ''), - ('val2', ''), - ('cons1', 0), - ('cons2', 0), - ('vmin', '0'), - ('vmax', '1'), - ('relatedto', ''), - ('factor', '1.0'), - ('delta', '0.0'), - ('sum', '0.0'), - ('group', ''), - ('name', param), - ('xmin', None), - ('xmax', None))) - self.setReadWrite(param, 'estimation') - self.setReadOnly(param, ['name', 'fitresult', 'sigma', 'val1', 'val2']) + self.parameters[param] = dict( + ( + ("line", line), + ("estimation", "0"), + ("fitresult", ""), + ("sigma", ""), + ("code", "FREE"), + ("val1", ""), + ("val2", ""), + ("cons1", 0), + ("cons2", 0), + ("vmin", "0"), + ("vmax", "1"), + ("relatedto", ""), + ("factor", "1.0"), + ("delta", "0.0"), + ("sum", "0.0"), + ("group", ""), + ("name", param), + ("xmin", None), + ("xmax", None), + ) + ) + self.setReadWrite(param, "estimation") + self.setReadOnly(param, ["name", "fitresult", "sigma", "val1", "val2"]) # Constraint codes a = [] for option in self.code_options: a.append(option) - code_column_index = self.columnIndexByField('code') + code_column_index = self.columnIndexByField("code") cellWidget = self.cellWidget(line, code_column_index) if cellWidget is None: - cellWidget = QComboTableItem(self, row=line, - col=code_column_index) + cellWidget = QComboTableItem(self, row=line, col=code_column_index) cellWidget.addItems(a) self.setCellWidget(line, code_column_index, cellWidget) cellWidget.sigCellChanged[int, int].connect(self.onCellChanged) - self.parameters[param]['code_item'] = cellWidget - self.parameters[param]['relatedto_item'] = None + self.parameters[param]["code_item"] = cellWidget + self.parameters[param]["relatedto_item"] = None self.__configuring = False def columnIndexByField(self, field): @@ -268,44 +297,48 @@ class Parameters(TableWidget): self.setRowCount(len(fitresults)) # Reinitialize and fill self.parameters - self.parameters = OrderedDict() - for (line, param) in enumerate(fitresults): - self.newParameterLine(param['name'], line) + self.parameters = {} + for line, param in enumerate(fitresults): + self.newParameterLine(param["name"], line) for param in fitresults: - name = param['name'] - code = str(param['code']) + name = param["name"] + code = str(param["code"]) if code not in self.code_options: # convert code from int to descriptive string code = self.code_options[int(code)] - val1 = param['cons1'] - val2 = param['cons2'] - estimation = param['estimation'] - group = param['group'] - sigma = param['sigma'] - fitresult = param['fitresult'] - - xmin = param.get('xmin') - xmax = param.get('xmax') - - self.configureLine(name=name, - code=code, - val1=val1, val2=val2, - estimation=estimation, - fitresult=fitresult, - sigma=sigma, - group=group, - xmin=xmin, xmax=xmax) + val1 = param["cons1"] + val2 = param["cons2"] + estimation = param["estimation"] + group = param["group"] + sigma = param["sigma"] + fitresult = param["fitresult"] + + xmin = param.get("xmin") + xmax = param.get("xmax") + + self.configureLine( + name=name, + code=code, + val1=val1, + val2=val2, + estimation=estimation, + fitresult=fitresult, + sigma=sigma, + group=group, + xmin=xmin, + xmax=xmax, + ) def getConfiguration(self): """Return ``FitManager.paramlist`` dictionary encapsulated in another dictionary""" - return {'parameters': self.getFitResults()} + return {"parameters": self.getFitResults()} def setConfiguration(self, ddict): """Fill table with values from a ``FitManager.paramlist`` dictionary encapsulated in another dictionary""" - self.fillFromFit(ddict['parameters']) + self.fillFromFit(ddict["parameters"]) def getFitResults(self): """Return fit parameters as a list of dictionaries in the format used @@ -316,33 +349,33 @@ class Parameters(TableWidget): fitparam = {} name = param estimation, [code, cons1, cons2] = self.getEstimationConstraints(name) - buf = str(self.parameters[param]['fitresult']) - xmin = self.parameters[param]['xmin'] - xmax = self.parameters[param]['xmax'] + buf = str(self.parameters[param]["fitresult"]) + xmin = self.parameters[param]["xmin"] + xmax = self.parameters[param]["xmax"] if len(buf): fitresult = float(buf) else: fitresult = 0.0 - buf = str(self.parameters[param]['sigma']) + buf = str(self.parameters[param]["sigma"]) if len(buf): sigma = float(buf) else: sigma = 0.0 - buf = str(self.parameters[param]['group']) + buf = str(self.parameters[param]["group"]) if len(buf): group = float(buf) else: group = 0 - fitparam['name'] = name - fitparam['estimation'] = estimation - fitparam['fitresult'] = fitresult - fitparam['sigma'] = sigma - fitparam['group'] = group - fitparam['code'] = code - fitparam['cons1'] = cons1 - fitparam['cons2'] = cons2 - fitparam['xmin'] = xmin - fitparam['xmax'] = xmax + fitparam["name"] = name + fitparam["estimation"] = estimation + fitparam["fitresult"] = fitresult + fitparam["sigma"] = sigma + fitparam["group"] = group + fitparam["code"] = code + fitparam["cons1"] = cons1 + fitparam["cons2"] = cons2 + fitparam["xmin"] = xmin + fitparam["xmax"] = xmax fitparameterslist.append(fitparam) return fitparameterslist @@ -370,7 +403,7 @@ class Parameters(TableWidget): if item is not None: newvalue = item.text() else: - newvalue = '' + newvalue = "" else: # this is the combobox widget = self.cellWidget(row, col) @@ -379,12 +412,12 @@ class Parameters(TableWidget): paramdict = {"name": param, field: newvalue} self.configureLine(**paramdict) else: - if field == 'code': + if field == "code": # New code not valid, try restoring the old one index = self.code_options.index(oldvalue) self.__configuring = True try: - self.parameters[param]['code_item'].setCurrentIndex(index) + self.parameters[param]["code_item"].setCurrentIndex(index) finally: self.__configuring = False else: @@ -400,10 +433,14 @@ class Parameters(TableWidget): :param newvalue: New value to be validated :return: True if new cell value is valid, else False """ - if field == 'code': + if field == "code": return self.setCodeValue(param, oldvalue, newvalue) # FIXME: validate() shouldn't have side effects. Move this bit to configureLine()? - if field == 'val1' and str(self.parameters[param]['code']) in ['DELTA', 'FACTOR', 'SUM']: + if field == "val1" and str(self.parameters[param]["code"]) in [ + "DELTA", + "FACTOR", + "SUM", + ]: _, candidates = self.getRelatedCandidates(param) # We expect val1 to be a fit parameter name if str(newvalue) in candidates: @@ -429,52 +466,48 @@ class Parameters(TableWidget): :return: ``True`` if code was successfully updated """ - if str(newvalue) in ['FREE', 'POSITIVE', 'QUOTED', 'FIXED']: - self.configureLine(name=param, - code=newvalue) - if str(oldvalue) == 'IGNORE': + if str(newvalue) in ["FREE", "POSITIVE", "QUOTED", "FIXED"]: + self.configureLine(name=param, code=newvalue) + if str(oldvalue) == "IGNORE": self.freeRestOfGroup(param) return True - elif str(newvalue) in ['FACTOR', 'DELTA', 'SUM']: + elif str(newvalue) in ["FACTOR", "DELTA", "SUM"]: # I should check here that some parameter is set best, candidates = self.getRelatedCandidates(param) if len(candidates) == 0: return False - self.configureLine(name=param, - code=newvalue, - relatedto=best) - if str(oldvalue) == 'IGNORE': + self.configureLine(name=param, code=newvalue, relatedto=best) + if str(oldvalue) == "IGNORE": self.freeRestOfGroup(param) return True - elif str(newvalue) == 'IGNORE': + elif str(newvalue) == "IGNORE": # I should check if the group can be ignored # for the time being I just fix all of them to ignore - group = int(float(str(self.parameters[param]['group']))) + group = int(float(str(self.parameters[param]["group"]))) candidates = [] for param in self.parameters.keys(): - if group == int(float(str(self.parameters[param]['group']))): + if group == int(float(str(self.parameters[param]["group"]))): candidates.append(param) # print candidates # I should check here if there is any relation to them for param in candidates: - self.configureLine(name=param, - code=newvalue) + self.configureLine(name=param, code=newvalue) return True - elif str(newvalue) == 'ADD': - group = int(float(str(self.parameters[param]['group']))) + elif str(newvalue) == "ADD": + group = int(float(str(self.parameters[param]["group"]))) if group == 0: # One cannot add a background group return False i = 0 for param in self.parameters: - if i <= int(float(str(self.parameters[param]['group']))): + if i <= int(float(str(self.parameters[param]["group"]))): i += 1 - if (group == 0) and (i == 1): # FIXME: why +1? + if (group == 0) and (i == 1): # FIXME: why +1? i += 1 self.addGroup(i, group) return False - elif str(newvalue) == 'SHOW': + elif str(newvalue) == "SHOW": print(self.getEstimationConstraints(param)) return False @@ -492,14 +525,14 @@ class Parameters(TableWidget): newparam = [] # loop through parameters until we encounter group number `gtype` for param in list(self.parameters): - paramgroup = int(float(str(self.parameters[param]['group']))) + paramgroup = int(float(str(self.parameters[param]["group"]))) # copy parameter names in group number `gtype` if paramgroup == gtype: # but replace `gtype` with `newg` newparam.append(param.rstrip("0123456789") + "%d" % newg) - xmin = self.parameters[param]['xmin'] - xmax = self.parameters[param]['xmax'] + xmin = self.parameters[param]["xmin"] + xmax = self.parameters[param]["xmax"] # Add new parameters (one table line per parameter) and configureLine each # one by updating xmin and xmax to the same values as group `gtype` @@ -519,16 +552,14 @@ class Parameters(TableWidget): :param workparam: Fit parameter name """ if workparam in self.parameters.keys(): - group = int(float(str(self.parameters[workparam]['group']))) + group = int(float(str(self.parameters[workparam]["group"]))) for param in self.parameters: - if param != workparam and\ - group == int(float(str(self.parameters[param]['group']))): - self.configureLine(name=param, - code='FREE', - cons1=0, - cons2=0, - val1='', - val2='') + if param != workparam and group == int( + float(str(self.parameters[param]["group"])) + ): + self.configureLine( + name=param, code="FREE", cons1=0, cons2=0, val1="", val2="" + ) def getRelatedCandidates(self, workparam): """If fit parameter ``workparam`` has a constraint that involves other @@ -543,12 +574,16 @@ class Parameters(TableWidget): for param_name in self.parameters: if param_name != workparam: # ignore parameters that are fixed by a constraint - if str(self.parameters[param_name]['code']) not in\ - ['IGNORE', 'FACTOR', 'DELTA', 'SUM']: + if str(self.parameters[param_name]["code"]) not in [ + "IGNORE", + "FACTOR", + "DELTA", + "SUM", + ]: candidates.append(param_name) # take the previous one (before code cell changed) if possible - if str(self.parameters[workparam]['relatedto']) in candidates: - best = str(self.parameters[workparam]['relatedto']) + if str(self.parameters[workparam]["relatedto"]) in candidates: + best = str(self.parameters[workparam]["relatedto"]) return best, candidates # take the first with same base name (after removing numbers) for param_name in candidates: @@ -584,9 +619,7 @@ class Parameters(TableWidget): :param fields: Field names identifying the columns :type fields: str or list[str] """ - editflags = qt.Qt.ItemIsSelectable |\ - qt.Qt.ItemIsEnabled |\ - qt.Qt.ItemIsEditable + editflags = qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled | qt.Qt.ItemIsEditable self.setField(parameter, fields, editflags) def setField(self, parameter, fields, edit_flags): @@ -601,13 +634,11 @@ class Parameters(TableWidget): qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled | qt.Qt.ItemIsEditable """ - if isinstance(parameter, list) or \ - isinstance(parameter, tuple): + if isinstance(parameter, list) or isinstance(parameter, tuple): paramlist = parameter else: paramlist = [parameter] - if isinstance(fields, list) or \ - isinstance(fields, tuple): + if isinstance(fields, list) or isinstance(fields, tuple): fieldlist = fields else: fieldlist = [fields] @@ -623,7 +654,7 @@ class Parameters(TableWidget): row = list(self.parameters.keys()).index(param) for field in fieldlist: col = self.columnIndexByField(field) - if field != 'code': + if field != "code": key = field + "_item" item = self.item(row, col) if item is None: @@ -638,10 +669,22 @@ class Parameters(TableWidget): # Restore previous _configuring flag self.__configuring = _oldvalue - def configureLine(self, name, code=None, val1=None, val2=None, - sigma=None, estimation=None, fitresult=None, - group=None, xmin=None, xmax=None, relatedto=None, - cons1=None, cons2=None): + def configureLine( + self, + name, + code=None, + val1=None, + val2=None, + sigma=None, + estimation=None, + fitresult=None, + group=None, + xmin=None, + xmax=None, + relatedto=None, + cons1=None, + cons2=None, + ): """This function updates values in a line of the table :param name: Name of the parameter (serves as unique identifier for @@ -675,73 +718,88 @@ class Parameters(TableWidget): # update code first, if specified if code is not None: code = str(code) - self.parameters[name]['code'] = code + self.parameters[name]["code"] = code # update combobox - index = self.parameters[name]['code_item'].findText(code) - self.parameters[name]['code_item'].setCurrentIndex(index) + index = self.parameters[name]["code_item"].findText(code) + self.parameters[name]["code_item"].setCurrentIndex(index) else: # set code to previous value, used later for setting val1 val2 - code = self.parameters[name]['code'] + code = self.parameters[name]["code"] # val1 and sigma have special formats if val1 is not None: - fmt = None if self.parameters[name]['code'] in\ - ['DELTA', 'FACTOR', 'SUM'] else "%8g" + fmt = ( + None + if self.parameters[name]["code"] in ["DELTA", "FACTOR", "SUM"] + else "%8g" + ) self._updateField(name, "val1", val1, fmat=fmt) if sigma is not None: self._updateField(name, "sigma", sigma, fmat="%6.3g") # other fields are formatted as "%8g" - keys_params = (("val2", val2), ("estimation", estimation), - ("fitresult", fitresult)) + keys_params = ( + ("val2", val2), + ("estimation", estimation), + ("fitresult", fitresult), + ) for key, value in keys_params: if value is not None: self._updateField(name, key, value, fmat="%8g") # the rest of the parameters are treated as strings and don't need # validation - keys_params = (("group", group), ("xmin", xmin), - ("xmax", xmax), ("relatedto", relatedto), - ("cons1", cons1), ("cons2", cons2)) + keys_params = ( + ("group", group), + ("xmin", xmin), + ("xmax", xmax), + ("relatedto", relatedto), + ("cons1", cons1), + ("cons2", cons2), + ) for key, value in keys_params: if value is not None: self.parameters[name][key] = str(value) # val1 and val2 have different meanings depending on the code - if code == 'QUOTED': + if code == "QUOTED": if val1 is not None: - self.parameters[name]['vmin'] = self.parameters[name]['val1'] + self.parameters[name]["vmin"] = self.parameters[name]["val1"] else: - self.parameters[name]['val1'] = self.parameters[name]['vmin'] + self.parameters[name]["val1"] = self.parameters[name]["vmin"] if val2 is not None: - self.parameters[name]['vmax'] = self.parameters[name]['val2'] + self.parameters[name]["vmax"] = self.parameters[name]["val2"] else: - self.parameters[name]['val2'] = self.parameters[name]['vmax'] + self.parameters[name]["val2"] = self.parameters[name]["vmax"] # cons1 and cons2 are scalar representations of val1 and val2 - self.parameters[name]['cons1'] =\ - float_else_zero(self.parameters[name]['val1']) - self.parameters[name]['cons2'] =\ - float_else_zero(self.parameters[name]['val2']) + self.parameters[name]["cons1"] = float_else_zero( + self.parameters[name]["val1"] + ) + self.parameters[name]["cons2"] = float_else_zero( + self.parameters[name]["val2"] + ) # cons1, cons2 = min(val1, val2), max(val1, val2) - if self.parameters[name]['cons1'] > self.parameters[name]['cons2']: - self.parameters[name]['cons1'], self.parameters[name]['cons2'] =\ - self.parameters[name]['cons2'], self.parameters[name]['cons1'] + if self.parameters[name]["cons1"] > self.parameters[name]["cons2"]: + self.parameters[name]["cons1"], self.parameters[name]["cons2"] = ( + self.parameters[name]["cons2"], + self.parameters[name]["cons1"], + ) - elif code in ['DELTA', 'SUM', 'FACTOR']: + elif code in ["DELTA", "SUM", "FACTOR"]: # For these codes, val1 is the fit parameter name on which the # constraint depends if val1 is not None and val1 in paramlist: - self.parameters[name]['relatedto'] = self.parameters[name]["val1"] + self.parameters[name]["relatedto"] = self.parameters[name]["val1"] elif val1 is not None: # val1 could be the index of the fit parameter try: - self.parameters[name]['relatedto'] = paramlist[int(val1)] + self.parameters[name]["relatedto"] = paramlist[int(val1)] except ValueError: - self.parameters[name]['relatedto'] = self.parameters[name]["val1"] + self.parameters[name]["relatedto"] = self.parameters[name]["val1"] elif relatedto is not None: # code changed, val1 not specified but relatedto specified: @@ -753,25 +811,27 @@ class Parameters(TableWidget): self.parameters[name][key] = self.parameters[name]["val2"] # FIXME: val1 is sometimes specified as an index rather than a param name - self.parameters[name]['val1'] = self.parameters[name]['relatedto'] + self.parameters[name]["val1"] = self.parameters[name]["relatedto"] # cons1 is the index of the fit parameter in the ordered dictionary - if self.parameters[name]['val1'] in paramlist: - self.parameters[name]['cons1'] =\ - paramlist.index(self.parameters[name]['val1']) + if self.parameters[name]["val1"] in paramlist: + self.parameters[name]["cons1"] = paramlist.index( + self.parameters[name]["val1"] + ) # cons2 is the constraint value (factor, delta or sum) try: - self.parameters[name]['cons2'] =\ - float(str(self.parameters[name]['val2'])) + self.parameters[name]["cons2"] = float( + str(self.parameters[name]["val2"]) + ) except ValueError: - self.parameters[name]['cons2'] = 1.0 if code == "FACTOR" else 0.0 + self.parameters[name]["cons2"] = 1.0 if code == "FACTOR" else 0.0 - elif code in ['FREE', 'POSITIVE', 'IGNORE', 'FIXED']: - self.parameters[name]['val1'] = "" - self.parameters[name]['val2'] = "" - self.parameters[name]['cons1'] = 0 - self.parameters[name]['cons2'] = 0 + elif code in ["FREE", "POSITIVE", "IGNORE", "FIXED"]: + self.parameters[name]["val1"] = "" + self.parameters[name]["val2"] = "" + self.parameters[name]["cons1"] = 0 + self.parameters[name]["cons2"] = 0 self._updateCellRWFlags(name, code) @@ -793,9 +853,9 @@ class Parameters(TableWidget): newvalue = fmat % float(value) if value != "" else "" else: newvalue = value - self.parameters[name][field] = newvalue if\ - self.validate(name, field, oldvalue, newvalue) else\ - oldvalue + self.parameters[name][field] = ( + newvalue if self.validate(name, field, oldvalue, newvalue) else oldvalue + ) def _updateCellRWFlags(self, name, code=None): """Set read-only or read-write flags in a row, @@ -806,12 +866,12 @@ class Parameters(TableWidget): `'FIXED', 'FACTOR', 'DELTA', 'SUM', 'ADD'` :return: """ - if code in ['FREE', 'POSITIVE', 'IGNORE', 'FIXED']: - self.setReadWrite(name, 'estimation') - self.setReadOnly(name, ['fitresult', 'sigma', 'val1', 'val2']) + if code in ["FREE", "POSITIVE", "IGNORE", "FIXED"]: + self.setReadWrite(name, "estimation") + self.setReadOnly(name, ["fitresult", "sigma", "val1", "val2"]) else: - self.setReadWrite(name, ['estimation', 'val1', 'val2']) - self.setReadOnly(name, ['fitresult', 'sigma']) + self.setReadWrite(name, ["estimation", "val1", "val2"]) + self.setReadOnly(name, ["fitresult", "sigma"]) def getEstimationConstraints(self, param): """ @@ -822,18 +882,17 @@ class Parameters(TableWidget): estimation = None constraints = None if param in self.parameters.keys(): - buf = str(self.parameters[param]['estimation']) + buf = str(self.parameters[param]["estimation"]) if len(buf): estimation = float(buf) else: estimation = 0 - if str(self.parameters[param]['code']) in self.code_options: - code = self.code_options.index( - str(self.parameters[param]['code'])) + if str(self.parameters[param]["code"]) in self.code_options: + code = self.code_options.index(str(self.parameters[param]["code"])) else: - code = str(self.parameters[param]['code']) - cons1 = self.parameters[param]['cons1'] - cons2 = self.parameters[param]['cons2'] + code = str(self.parameters[param]["code"]) + cons1 = self.parameters[param]["cons1"] + cons2 = self.parameters[param]["cons2"] constraints = [code, cons1, cons2] return estimation, constraints @@ -841,21 +900,24 @@ class Parameters(TableWidget): def main(args): from silx.math.fit import fittheories from silx.math.fit import fitmanager + try: from PyMca5 import PyMcaDataDir except ImportError: raise ImportError("This demo requires PyMca data. Install PyMca5.") import numpy import os + app = qt.QApplication(args) - tab = Parameters(paramlist=['Height', 'Position', 'FWHM']) + tab = Parameters(paramlist=["Height", "Position", "FWHM"]) tab.showGrid() - tab.configureLine(name='Height', estimation='1234', group=0) - tab.configureLine(name='Position', code='FIXED', group=1) - tab.configureLine(name='FWHM', group=1) + tab.configureLine(name="Height", estimation="1234", group=0) + tab.configureLine(name="Position", code="FIXED", group=1) + tab.configureLine(name="FWHM", group=1) - y = numpy.loadtxt(os.path.join(PyMcaDataDir.PYMCA_DATA_DIR, - "XRFSpectrum.mca")) # FIXME + y = numpy.loadtxt( + os.path.join(PyMcaDataDir.PYMCA_DATA_DIR, "XRFSpectrum.mca") + ) # FIXME x = numpy.arange(len(y)) * 0.0502883 - 0.492773 fit = fitmanager.FitManager() @@ -863,19 +925,22 @@ def main(args): fit.loadtheories(fittheories) - fit.settheory('ahypermet') - fit.configure(Yscaling=1., - PositiveFwhmFlag=True, - PositiveHeightAreaFlag=True, - FwhmPoints=16, - QuotedPositionFlag=1, - HypermetTails=1) - fit.setbackground('Linear') + fit.settheory("ahypermet") + fit.configure( + Yscaling=1.0, + PositiveFwhmFlag=True, + PositiveHeightAreaFlag=True, + FwhmPoints=16, + QuotedPositionFlag=1, + HypermetTails=1, + ) + fit.setbackground("Linear") fit.estimate() fit.runfit() tab.fillFromFit(fit.fit_results) tab.show() app.exec() + if __name__ == "__main__": main(sys.argv) diff --git a/src/silx/gui/fit/test/testBackgroundWidget.py b/src/silx/gui/fit/test/testBackgroundWidget.py index 353d3d5..73e3fba 100644 --- a/src/silx/gui/fit/test/testBackgroundWidget.py +++ b/src/silx/gui/fit/test/testBackgroundWidget.py @@ -21,8 +21,6 @@ # THE SOFTWARE. # # ###########################################################################*/ -import unittest - from silx.gui.utils.testutils import TestCaseQt from .. import BackgroundWidget @@ -36,8 +34,7 @@ class TestBackgroundWidget(TestCaseQt): def setUp(self): super(TestBackgroundWidget, self).setUp() self.bgdialog = BackgroundWidget.BackgroundDialog() - self.bgdialog.setData(list([0, 1, 2, 3]), - list([0, 1, 4, 8])) + self.bgdialog.setData(list([0, 1, 2, 3]), list([0, 1, 4, 8])) self.qWaitForWindowExposed(self.bgdialog) def tearDown(self): @@ -60,9 +57,17 @@ class TestBackgroundWidget(TestCaseQt): self.bgdialog.accept() output = self.bgdialog.output - for key in ["algorithm", "StripThreshold", "SnipWidth", - "StripIterations", "StripWidth", "SmoothingFlag", - "SmoothingWidth", "AnchorsFlag", "AnchorsList"]: + for key in [ + "algorithm", + "StripThreshold", + "SnipWidth", + "StripIterations", + "StripWidth", + "SmoothingFlag", + "SmoothingWidth", + "AnchorsFlag", + "AnchorsList", + ]: self.assertIn(key, output) self.assertFalse(output["AnchorsFlag"]) diff --git a/src/silx/gui/fit/test/testFitConfig.py b/src/silx/gui/fit/test/testFitConfig.py index 114ff62..d59562c 100644 --- a/src/silx/gui/fit/test/testFitConfig.py +++ b/src/silx/gui/fit/test/testFitConfig.py @@ -27,8 +27,6 @@ __authors__ = ["P. Knobel"] __license__ = "MIT" __date__ = "05/12/2016" -import unittest - from silx.gui.utils.testutils import TestCaseQt from .. import FitConfig @@ -61,22 +59,24 @@ class TestFitConfig(TestCaseQt): self.fit_config.accept() output = self.fit_config.output - for key in ["AutoFwhm", - "PositiveHeightAreaFlag", - "QuotedPositionFlag", - "PositiveFwhmFlag", - "SameFwhmFlag", - "QuotedEtaFlag", - "NoConstraintsFlag", - "FwhmPoints", - "Sensitivity", - "Yscaling", - "ForcePeakPresence", - "StripBackgroundFlag", - "StripWidth", - "StripIterations", - "StripThreshold", - "SmoothingFlag"]: + for key in [ + "AutoFwhm", + "PositiveHeightAreaFlag", + "QuotedPositionFlag", + "PositiveFwhmFlag", + "SameFwhmFlag", + "QuotedEtaFlag", + "NoConstraintsFlag", + "FwhmPoints", + "Sensitivity", + "Yscaling", + "ForcePeakPresence", + "StripBackgroundFlag", + "StripWidth", + "StripIterations", + "StripThreshold", + "SmoothingFlag", + ]: self.assertIn(key, output) self.assertTrue(output["AutoFwhm"]) diff --git a/src/silx/gui/fit/test/testFitWidget.py b/src/silx/gui/fit/test/testFitWidget.py index fe61268..e59fa92 100644 --- a/src/silx/gui/fit/test/testFitWidget.py +++ b/src/silx/gui/fit/test/testFitWidget.py @@ -23,8 +23,6 @@ # ###########################################################################*/ """Basic tests for :class:`FitWidget`""" -import unittest - from silx.gui.utils.testutils import TestCaseQt from ... import qt @@ -82,13 +80,9 @@ class TestFitWidget(TestCaseQt): y = [fitfun(x_, 2, 3) for x_ in x] def conf(**kw): - return {"spam": "eggs", - "hello": "world!"} + return {"spam": "eggs", "hello": "world!"} - theory = FitTheory( - function=fitfun, - parameters=["a", "b"], - configure=conf) + theory = FitTheory(function=fitfun, parameters=["a", "b"], configure=conf) fitmngr = FitManager() fitmngr.setdata(x, y) @@ -97,8 +91,9 @@ class TestFitWidget(TestCaseQt): fitmngr.addbgtheory("spam", theory) fw = FitWidget(fitmngr=fitmngr) - fw.associateConfigDialog("spam", CustomConfigWidget(), - theory_is_background=True) + fw.associateConfigDialog( + "spam", CustomConfigWidget(), theory_is_background=True + ) fw.associateConfigDialog("foo", CustomConfigWidget()) fw.show() self.qWaitForWindowExposed(fw) @@ -106,8 +101,7 @@ class TestFitWidget(TestCaseQt): fw.bgconfigdialogs["spam"].accept() self.assertTrue(fw.bgconfigdialogs["spam"].result()) - self.assertEqual(fw.bgconfigdialogs["spam"].output, - {"hello": "world"}) + self.assertEqual(fw.bgconfigdialogs["spam"].output, {"hello": "world"}) fw.bgconfigdialogs["spam"].reject() self.assertFalse(fw.bgconfigdialogs["spam"].result()) |