summaryrefslogtreecommitdiff
path: root/silx/gui/fit
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/fit')
-rw-r--r--silx/gui/fit/BackgroundWidget.py534
-rw-r--r--silx/gui/fit/FitConfig.py543
-rw-r--r--silx/gui/fit/FitWidget.py739
-rw-r--r--silx/gui/fit/FitWidgets.py559
-rw-r--r--silx/gui/fit/Parameters.py882
-rw-r--r--silx/gui/fit/__init__.py28
-rw-r--r--silx/gui/fit/setup.py43
-rw-r--r--silx/gui/fit/test/__init__.py43
-rw-r--r--silx/gui/fit/test/testBackgroundWidget.py83
-rw-r--r--silx/gui/fit/test/testFitConfig.py95
-rw-r--r--silx/gui/fit/test/testFitWidget.py135
11 files changed, 0 insertions, 3684 deletions
diff --git a/silx/gui/fit/BackgroundWidget.py b/silx/gui/fit/BackgroundWidget.py
deleted file mode 100644
index 76bc043..0000000
--- a/silx/gui/fit/BackgroundWidget.py
+++ /dev/null
@@ -1,534 +0,0 @@
-# coding: utf-8
-#/*##########################################################################
-# Copyright (C) 2004-2020 V.A. Sole, European Synchrotron Radiation Facility
-#
-# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
-# the ESRF by the Software group.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# #########################################################################*/
-"""This module provides a background configuration widget
-:class:`BackgroundWidget` and a corresponding dialog window
-:class:`BackgroundDialog`.
-
-.. image:: img/BackgroundDialog.png
- :height: 300px
-"""
-import sys
-import numpy
-from silx.gui import qt
-from silx.gui.plot import PlotWidget
-from silx.math.fit import filters
-
-__authors__ = ["V.A. Sole", "P. Knobel"]
-__license__ = "MIT"
-__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))
-
-
-class BackgroundParamWidget(qt.QWidget):
- """Background configuration composite widget.
-
- Strip and snip filters parameters can be adjusted using input widgets.
-
- Updating the widgets causes :attr:`sigBackgroundParamWidgetSignal` to
- be emitted.
- """
- sigBackgroundParamWidgetSignal = qt.pyqtSignal(object)
-
- def __init__(self, parent=None):
- qt.QWidget.__init__(self, parent)
-
- self.mainLayout = qt.QGridLayout(self)
- self.mainLayout.setColumnStretch(1, 1)
-
- # Algorithm choice ---------------------------------------------------
- self.algorithmComboLabel = qt.QLabel(self)
- self.algorithmComboLabel.setText("Background algorithm")
- self.algorithmCombo = qt.QComboBox(self)
- self.algorithmCombo.addItem("Strip")
- self.algorithmCombo.addItem("Snip")
- self.algorithmCombo.activated[int].connect(
- self._algorithmComboActivated)
-
- # Strip parameters ---------------------------------------------------
- self.stripWidthLabel = qt.QLabel(self)
- self.stripWidthLabel.setText("Strip Width")
-
- self.stripWidthSpin = qt.QSpinBox(self)
- self.stripWidthSpin.setMaximum(100)
- self.stripWidthSpin.setMinimum(1)
- self.stripWidthSpin.valueChanged[int].connect(self._emitSignal)
-
- self.stripIterLabel = qt.QLabel(self)
- self.stripIterLabel.setText("Strip Iterations")
- self.stripIterValue = qt.QLineEdit(self)
- validator = qt.QIntValidator(self.stripIterValue)
- self.stripIterValue._v = validator
- 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.")
-
- # Snip parameters ----------------------------------------------------
- self.snipWidthLabel = qt.QLabel(self)
- self.snipWidthLabel.setText("Snip Width")
-
- self.snipWidthSpin = qt.QSpinBox(self)
- self.snipWidthSpin.setMaximum(300)
- 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)")
- self.smoothingFlagCheck.toggled.connect(self._smoothingToggled)
-
- self.smoothingSpin = qt.QSpinBox(self)
- self.smoothingSpin.setMinimum(3)
- #self.smoothingSpin.setMaximum(40)
- self.smoothingSpin.setSingleStep(2)
- self.smoothingSpin.valueChanged[int].connect(self._emitSignal)
-
- # Anchors ------------------------------------------------------------
-
- self.anchorsGroup = qt.QWidget(self)
- anchorsLayout = qt.QHBoxLayout(self.anchorsGroup)
- anchorsLayout.setSpacing(2)
- anchorsLayout.setContentsMargins(0, 0, 0, 0)
-
- 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)
- anchorsLayout.addWidget(self.anchorsFlagCheck)
-
- maxnchannel = 16384 * 4 # Fixme ?
- self.anchorsList = []
- num_anchors = 4
- for i in range(num_anchors):
- anchorSpin = qt.QSpinBox(self.anchorsGroup)
- anchorSpin.setMinimum(0)
- anchorSpin.setMaximum(maxnchannel)
- anchorSpin.valueChanged[int].connect(self._emitSignal)
- anchorsLayout.addWidget(anchorSpin)
- self.anchorsList.append(anchorSpin)
-
- # Layout ------------------------------------------------------------
- self.mainLayout.addWidget(self.algorithmComboLabel, 0, 0)
- self.mainLayout.addWidget(self.algorithmCombo, 0, 2)
- self.mainLayout.addWidget(self.stripWidthLabel, 1, 0)
- self.mainLayout.addWidget(self.stripWidthSpin, 1, 2)
- self.mainLayout.addWidget(self.stripIterLabel, 2, 0)
- self.mainLayout.addWidget(self.stripIterValue, 2, 2)
- self.mainLayout.addWidget(self.snipWidthLabel, 3, 0)
- self.mainLayout.addWidget(self.snipWidthSpin, 3, 2)
- self.mainLayout.addWidget(self.smoothingFlagCheck, 4, 0)
- self.mainLayout.addWidget(self.smoothingSpin, 4, 2)
- self.mainLayout.addWidget(self.anchorsGroup, 5, 0, 1, 4)
-
- # Initialize interface -----------------------------------------------
- self._setAlgorithm("strip")
- self.smoothingFlagCheck.setChecked(False)
- self._smoothingToggled(is_checked=False)
- self.anchorsFlagCheck.setChecked(False)
- self._anchorsToggled(is_checked=False)
-
- def _algorithmComboActivated(self, algorithm_index):
- self._setAlgorithm("strip" if algorithm_index == 0 else "snip")
-
- def _setAlgorithm(self, algorithm):
- """Enable/disable snip and snip input widgets, depending on the
- chosen algorithm.
- :param algorithm: "snip" or "strip"
- """
- if algorithm not in ["strip", "snip"]:
- raise ValueError(
- "Unknown background filter algorithm %s" % algorithm)
-
- self.algorithm = algorithm
- self.stripWidthSpin.setEnabled(algorithm == "strip")
- self.stripIterValue.setEnabled(algorithm == "strip")
- self.snipWidthSpin.setEnabled(algorithm == "snip")
-
- def _smoothingToggled(self, is_checked):
- """Enable/disable smoothing input widgets, emit dictionary"""
- self.smoothingSpin.setEnabled(is_checked)
- self._emitSignal()
-
- def _anchorsToggled(self, is_checked):
- """Enable/disable all spin widgets defining anchor X coordinates,
- emit signal.
- """
- for anchor_spin in self.anchorsList:
- anchor_spin.setEnabled(is_checked)
- self._emitSignal()
-
- def setParameters(self, ddict):
- """Set values for all input widgets.
-
- :param dict ddict: Input dictionary, must have the same
- keys as the dictionary output by :meth:`getParameters`
- """
- if "algorithm" in ddict:
- self._setAlgorithm(ddict["algorithm"])
-
- if "SnipWidth" in ddict:
- self.snipWidthSpin.setValue(int(ddict["SnipWidth"]))
-
- if "StripWidth" in ddict:
- self.stripWidthSpin.setValue(int(ddict["StripWidth"]))
-
- if "StripIterations" in ddict:
- self.stripIterValue.setText("%d" % int(ddict["StripIterations"]))
-
- if "SmoothingFlag" in ddict:
- self.smoothingFlagCheck.setChecked(bool(ddict["SmoothingFlag"]))
-
- if "SmoothingWidth" in ddict:
- self.smoothingSpin.setValue(int(ddict["SmoothingWidth"]))
-
- if "AnchorsFlag" in ddict:
- self.anchorsFlagCheck.setChecked(bool(ddict["AnchorsFlag"]))
-
- if "AnchorsList" in ddict:
- anchorslist = ddict["AnchorsList"]
- if anchorslist in [None, 'None']:
- anchorslist = []
- for spin in self.anchorsList:
- spin.setValue(0)
-
- i = 0
- for value in anchorslist:
- self.anchorsList[i].setValue(int(value))
- i += 1
-
- def getParameters(self):
- """Return dictionary of parameters defined in the GUI
-
- The returned dictionary contains following values:
-
- - *algorithm*: *"strip"* or *"snip"*
- - *StripWidth*: width of strip iterator
- - *StripIterations*: number of iterations
- - *StripThreshold*: curvature parameter (currently fixed to 1.0)
- - *SnipWidth*: width of snip algorithm
- - *SmoothingFlag*: flag to enable/disable smoothing
- - *SmoothingWidth*: width of Savitsky-Golay smoothing filter
- - *AnchorsFlag*: flag to enable/disable anchors
- - *AnchorsList*: list of anchors (X coordinates of fixed values)
- """
- 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]}
-
- def _emitSignal(self, dummy=None):
- self.sigBackgroundParamWidgetSignal.emit(
- {'event': 'ParametersChanged',
- 'parameters': self.getParameters()})
-
-
-class BackgroundWidget(qt.QWidget):
- """Background configuration widget, with a plot to preview the results.
-
- 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")
- self.mainLayout = qt.QVBoxLayout(self)
- self.mainLayout.setContentsMargins(0, 0, 0, 0)
- self.mainLayout.setSpacing(2)
- self.parametersWidget = BackgroundParamWidget(self)
- self.graphWidget = PlotWidget(parent=self)
- self.mainLayout.addWidget(self.parametersWidget)
- self.mainLayout.addWidget(self.graphWidget)
- self._x = None
- self._y = None
- self.parametersWidget.sigBackgroundParamWidgetSignal.connect(self._slot)
-
- def getParameters(self):
- """Return dictionary of parameters defined in the GUI
-
- The returned dictionary contains following values:
-
- - *algorithm*: *"strip"* or *"snip"*
- - *StripWidth*: width of strip iterator
- - *StripIterations*: number of iterations
- - *StripThreshold*: strip curvature (currently fixed to 1.0)
- - *SnipWidth*: width of snip algorithm
- - *SmoothingFlag*: flag to enable/disable smoothing
- - *SmoothingWidth*: width of Savitsky-Golay smoothing filter
- - *AnchorsFlag*: flag to enable/disable anchors
- - *AnchorsList*: list of anchors (X coordinates of fixed values)
- """
- return self.parametersWidget.getParameters()
-
- def setParameters(self, ddict):
- """Set values for all input widgets.
-
- :param dict ddict: Input dictionary, must have the same
- keys as the dictionary output by :meth:`getParameters`
- """
- return self.parametersWidget.setParameters(ddict)
-
- def setData(self, x, y, xmin=None, xmax=None):
- """Set data for the original curve, and _update strip and snip
- curves accordingly.
-
- :param x: Array or sequence of curve abscissa values
- :param y: Array or sequence of curve ordinate values
- :param xmin: Min value to be displayed on the X axis
- :param xmax: Max value to be displayed on the X axis
- """
- self._x = x
- self._y = y
- self._xmin = xmin
- self._xmax = xmax
- self._update(resetzoom=True)
-
- def _slot(self, ddict):
- self._update()
-
- def _update(self, resetzoom=False):
- """Compute strip and snip backgrounds, update the curves
- """
- if self._y is None:
- return
-
- pars = self.getParameters()
-
- # smoothed data
- y = numpy.ravel(numpy.array(self._y)).astype(numpy.float64)
- if pars["SmoothingFlag"]:
- 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])
- ysmooth[-1] = 0.5 * (ysmooth[-1] + ysmooth[-2])
- else:
- ysmooth = y
-
-
- # loop for anchors
- x = self._x
- niter = pars['StripIterations']
- anchors_indices = []
- if pars['AnchorsFlag'] and pars['AnchorsList'] is not None:
- ravelled = x
- for channel in pars['AnchorsList']:
- if channel <= ravelled[0]:
- continue
- index = numpy.nonzero(ravelled >= channel)[0]
- if len(index):
- index = min(index)
- if index > 0:
- anchors_indices.append(index)
-
- 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)
-
- if len(anchors_indices) == 0:
- 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'])
- 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)
- 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")
- self.mainLayout = qt.QVBoxLayout(self)
- self.mainLayout.setContentsMargins(0, 0, 0, 0)
- self.mainLayout.setSpacing(2)
- self.parametersWidget = BackgroundWidget(self)
- self.mainLayout.addWidget(self.parametersWidget)
- hbox = qt.QWidget(self)
- hboxLayout = qt.QHBoxLayout(hbox)
- hboxLayout.setContentsMargins(0, 0, 0, 0)
- hboxLayout.setSpacing(2)
- self.okButton = qt.QPushButton(hbox)
- self.okButton.setText("OK")
- self.okButton.setAutoDefault(False)
- self.dismissButton = qt.QPushButton(hbox)
- self.dismissButton.setText("Cancel")
- self.dismissButton.setAutoDefault(False)
- hboxLayout.addWidget(HorizontalSpacer(hbox))
- hboxLayout.addWidget(self.okButton)
- hboxLayout.addWidget(self.dismissButton)
- self.mainLayout.addWidget(hbox)
- self.dismissButton.clicked.connect(self.reject)
- self.okButton.clicked.connect(self.accept)
-
- self.output = {}
- """Configuration dictionary containing following fields:
-
- - *SmoothingFlag*
- - *SmoothingWidth*
- - *StripWidth*
- - *StripIterations*
- - *StripThreshold*
- - *SnipWidth*
- - *AnchorsFlag*
- - *AnchorsList*
- """
-
- # self.parametersWidget.parametersWidget.sigBackgroundParamWidgetSignal.connect(self.updateOutput)
-
- # def updateOutput(self, ddict):
- # self.output = ddict
-
- def accept(self):
- """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())
-
- def setData(self, x, y, xmin=None, xmax=None):
- """See :meth:`BackgroundWidget.setData`"""
- return self.parametersWidget.setData(x, y, xmin, xmax)
-
- def getParameters(self):
- """See :meth:`BackgroundWidget.getParameters`"""
- return self.parametersWidget.getParameters()
-
- def setParameters(self, ddict):
- """See :meth:`BackgroundWidget.setPrintGeometry`"""
- return self.parametersWidget.setParameters(ddict)
-
- def setDefault(self, ddict):
- """Alias for :meth:`setPrintGeometry`"""
- return self.setParameters(ddict)
-
-
-def getBgDialog(parent=None, default=None, modal=True):
- """Instantiate and return a bg configuration dialog, adapted
- for configuring standard background theories from
- :mod:`silx.math.fit.bgtheories`.
-
- :return: Instance of :class:`BackgroundDialog`
- """
- bgd = BackgroundDialog(parent=parent)
- # apply default to newly added pages
- bgd.setParameters(default)
-
- return bgd
-
-
-def main():
- # synthetic data
- from silx.math.fit.functions import sum_gauss
-
- 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)
- y0 = sum_gauss(x, *params1)
-
- # random values between [-1;1]
- noise = 2 * numpy.random.random(5000) - 1
- # make it +- 5%
- noise *= 0.05
-
- # 2 gaussians with very large fwhm, as background signal
- actual_bg = sum_gauss(x, 15, 3500, 3000, 5, 1000, 1500)
-
- # Add 5% random noise to gaussians and add background
- y = y0 + numpy.average(y0) * noise + actual_bg
-
- # Open widget
- a = qt.QApplication(sys.argv)
- a.lastWindowClosed.connect(a.quit)
-
- def mySlot(ddict):
- print(ddict)
-
- w = BackgroundDialog()
- w.parametersWidget.parametersWidget.sigBackgroundParamWidgetSignal.connect(mySlot)
- w.setData(x, y)
- w.exec_()
- #a.exec_()
-
-if __name__ == "__main__":
- main()
diff --git a/silx/gui/fit/FitConfig.py b/silx/gui/fit/FitConfig.py
deleted file mode 100644
index 479e469..0000000
--- a/silx/gui/fit/FitConfig.py
+++ /dev/null
@@ -1,543 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2004-2018 V.A. Sole, European Synchrotron Radiation Facility
-#
-# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
-# the ESRF by the Software group.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ######################################################################### */
-"""This module defines widgets used to build a fit configuration dialog.
-The resulting dialog widget outputs a dictionary of configuration parameters.
-"""
-from silx.gui import qt
-
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "30/11/2016"
-
-
-class TabsDialog(qt.QDialog):
- """Dialog widget containing a QTabWidget :attr:`tabWidget`
- and a buttons:
-
- # - buttonHelp
- - buttonDefaults
- - buttonOk
- - buttonCancel
-
- 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)
-
- layout = qt.QVBoxLayout(self)
- layout.addWidget(self.tabWidget)
-
- layout2 = qt.QHBoxLayout(None)
-
- # self.buttonHelp = qt.QPushButton(self)
- # self.buttonHelp.setText("Help")
- # layout2.addWidget(self.buttonHelp)
-
- self.buttonDefault = qt.QPushButton(self)
- self.buttonDefault.setText("Undo changes")
- layout2.addWidget(self.buttonDefault)
-
- spacer = qt.QSpacerItem(20, 20,
- qt.QSizePolicy.Expanding,
- qt.QSizePolicy.Minimum)
- layout2.addItem(spacer)
-
- self.buttonOk = qt.QPushButton(self)
- self.buttonOk.setText("OK")
- layout2.addWidget(self.buttonOk)
-
- self.buttonCancel = qt.QPushButton(self)
- self.buttonCancel.setText("Cancel")
- layout2.addWidget(self.buttonCancel)
-
- layout.addLayout(layout2)
-
- self.buttonOk.clicked.connect(self.accept)
- self.buttonCancel.clicked.connect(self.reject)
-
- def __len__(self):
- """Return number of tabs"""
- return self.tabWidget.count()
-
- def __iter__(self):
- """Return the next tab widget in :attr:`tabWidget` every
- time this method is called.
-
- :return: Tab widget
- :rtype: QWidget
- """
- for widget_index in range(len(self)):
- yield self.tabWidget.widget(widget_index)
-
- def addTab(self, page, label):
- """Add a new tab
-
- :param page: Content of new page. Must be a widget with
- a get() method returning a dictionary.
- :param str label: Tab label
- """
- self.tabWidget.addTab(page, label)
-
- def getTabLabels(self):
- """
- Return a list of all tab labels in :attr:`tabWidget`
- """
- return [self.tabWidget.tabText(i) for i in range(len(self))]
-
-
-class TabsDialogData(TabsDialog):
- """This dialog adds a data attribute to :class:`TabsDialog`.
-
- Data input in widgets, such as text entries or checkboxes, is stored in an
- attribute :attr:`output` when the user clicks the OK button.
-
- 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):
- """
-
- :param parent: Parent :class:`QWidget`
- :param modal: If `True`, dialog is modal, meaning this dialog remains
- in front of it's parent window and disables it until the user is
- done interacting with the dialog
- :param default: Default dictionary, used to initialize and reset
- :attr:`output`.
- """
- TabsDialog.__init__(self, parent)
- self.setModal(modal)
- self.setWindowTitle("Fit configuration")
-
- self.output = {}
-
- self.default = {} if default is None else default
-
- self.buttonDefault.clicked.connect(self._resetDefault)
- # self.keyPressEvent(qt.Qt.Key_Enter).
-
- def keyPressEvent(self, event):
- """Redefining this method to ignore Enter key
- (for some reason it activates buttonDefault callback which
- resets all widgets)
- """
- if event.key() in [qt.Qt.Key_Enter, qt.Qt.Key_Return]:
- return
- TabsDialog.keyPressEvent(self, event)
-
- def accept(self):
- """When *OK* is clicked, update :attr:`output` with data from
- various widgets
- """
- self.output.update(self.default)
-
- # loop over all tab widgets (uses TabsDialog.__iter__)
- for tabWidget in self:
- self.output.update(tabWidget.get())
-
- # avoid pathological None cases
- for key in self.output.keys():
- if self.output[key] is None:
- if key in self.default:
- self.output[key] = self.default[key]
- super(TabsDialogData, self).accept()
-
- def reject(self):
- """When the *Cancel* button is clicked, reinitialize :attr:`output`
- and quit
- """
- self.setDefault()
- super(TabsDialogData, self).reject()
-
- def _resetDefault(self, checked):
- self.setDefault()
-
- def setDefault(self, newdefault=None):
- """Reinitialize :attr:`output` with :attr:`default` or with
- new dictionary ``newdefault`` if provided.
- Call :meth:`setDefault` for each tab widget, if available.
- """
- self.output = {}
- if newdefault is None:
- newdefault = self.default
- else:
- self.default = newdefault
- self.output.update(newdefault)
-
- for tabWidget in self:
- if hasattr(tabWidget, "setDefault"):
- tabWidget.setDefault(self.output)
-
-
-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.setCheckable(True)
-
- layout = qt.QVBoxLayout(self)
- self.setLayout(layout)
-
- self.positiveHeightCB = qt.QCheckBox("Force positive height/area", self)
- self.positiveHeightCB.setToolTip("Fit must find positive peaks")
- layout.addWidget(self.positiveHeightCB)
-
- self.positionInIntervalCB = qt.QCheckBox("Force position in interval", self)
- self.positionInIntervalCB.setToolTip(
- "Fit must position peak within X limits")
- layout.addWidget(self.positionInIntervalCB)
-
- self.positiveFwhmCB = qt.QCheckBox("Force positive FWHM", self)
- self.positiveFwhmCB.setToolTip("Fit must find a positive FWHM")
- layout.addWidget(self.positiveFwhmCB)
-
- self.sameFwhmCB = qt.QCheckBox("Force same FWHM for all peaks", self)
- self.sameFwhmCB.setToolTip("Fit must find same FWHM for all peaks")
- layout.addWidget(self.sameFwhmCB)
-
- 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")
- layout.addWidget(self.quotedEtaCB)
-
- layout.addStretch()
-
- self.setDefault()
-
- def setDefault(self, default_dict=None):
- """Set default state for all widgets.
-
- :param default_dict: If a default config dictionary is provided as
- a parameter, its values are used as default state."""
- 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.positiveHeightCB.setChecked(
- 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))
-
- 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(),
- }
- return ddict
-
-
-class SearchPage(qt.QWidget):
- def __init__(self, parent=None):
- super(SearchPage, self).__init__(parent)
- layout = qt.QVBoxLayout(self)
-
- 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")
- layout.addWidget(self.manualFwhmGB)
- # ------------ GroupBox fwhm--------------------------
- layout2 = qt.QHBoxLayout(self.manualFwhmGB)
- self.manualFwhmGB.setLayout(layout2)
-
- label = qt.QLabel("Fwhm Points", self.manualFwhmGB)
- layout2.addWidget(label)
-
- self.fwhmPointsSpin = qt.QSpinBox(self.manualFwhmGB)
- self.fwhmPointsSpin.setRange(0, 999999)
- self.fwhmPointsSpin.setToolTip("Typical peak fwhm (number of data points)")
- layout2.addWidget(self.fwhmPointsSpin)
- # ----------------------------------------------------
-
- 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")
- layout.addWidget(self.manualScalingGB)
- # ------------ GroupBox scaling-----------------------
- layout3 = qt.QHBoxLayout(self.manualScalingGB)
- self.manualScalingGB.setLayout(layout3)
-
- label = qt.QLabel("Y Scaling", self.manualScalingGB)
- layout3.addWidget(label)
-
- self.yScalingEntry = qt.QLineEdit(self.manualScalingGB)
- self.yScalingEntry.setToolTip(
- "Data values will be multiplied by this value prior to peak" +
- " search")
- self.yScalingEntry.setValidator(qt.QDoubleValidator(self))
- layout3.addWidget(self.yScalingEntry)
- # ----------------------------------------------------
-
- # ------------------- grid layout --------------------
- containerWidget = qt.QWidget(self)
- layout4 = qt.QHBoxLayout(containerWidget)
- containerWidget.setLayout(layout4)
-
- label = qt.QLabel("Sensitivity", containerWidget)
- layout4.addWidget(label)
-
- 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)")
- sensivalidator = qt.QDoubleValidator(self)
- sensivalidator.setBottom(1.0)
- self.sensitivityEntry.setValidator(sensivalidator)
- layout4.addWidget(self.sensitivityEntry)
- # ----------------------------------------------------
- layout.addWidget(containerWidget)
-
- 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")
- layout.addWidget(self.forcePeakPresenceCB)
-
- layout.addStretch()
-
- self.setDefault()
-
- def setDefault(self, default_dict=None):
- """Set default values for all widgets.
-
- :param default_dict: If a default config dictionary is provided as
- 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.forcePeakPresenceCB.setChecked(
- 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()
- }
- return ddict
-
-
-class BackgroundPage(qt.QGroupBox):
- """Background subtraction configuration, specific to fittheories
- estimation functions."""
- 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.")
-
- layout = qt.QGridLayout(self)
- self.setLayout(layout)
-
- for i, label_text in enumerate(
- ["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")
- 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.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")
- 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")
- 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)")
- self.smoothingWidthSpin.setRange(3, 101)
- self.smoothingWidthSpin.setSingleStep(2)
- smoothlayout.addWidget(self.smoothingWidthSpin)
-
- layout.addWidget(self.smoothStripGB, 3, 0, 1, 2)
-
- layout.setRowStretch(4, 1)
-
- self.setDefault()
-
- def setDefault(self, default_dict=None):
- """Set default values for all widgets.
-
- :param default_dict: If a default config dictionary is provided as
- a parameter, its values are used as default values."""
- if default_dict is None:
- default_dict = {}
-
- 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))
-
- 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()
- }
- return ddict
-
-
-def safe_float(string_, default=1.0):
- """Convert a string into a float.
- If the conversion fails, return the default value.
- """
- try:
- ret = float(string_)
- except ValueError:
- return default
- else:
- return ret
-
-
-def safe_int(string_, default=1):
- """Convert a string into a integer.
- If the conversion fails, return the default value.
- """
- try:
- ret = int(float(string_))
- except ValueError:
- return default
- else:
- return ret
-
-
-def getFitConfigDialog(parent=None, default=None, modal=True):
- """Instantiate and return a fit configuration dialog, adapted
- for configuring standard fit theories from
- :mod:`silx.math.fit.fittheories`.
-
- :return: Instance of :class:`TabsDialogData` with 3 tabs:
- :class:`ConstraintsPage`, :class:`SearchPage` and
- :class:`BackgroundPage`
- """
- tdd = TabsDialogData(parent=parent, default=default)
- tdd.addTab(ConstraintsPage(), label="Constraints")
- tdd.addTab(SearchPage(), label="Peak search")
- tdd.addTab(BackgroundPage(), label="Background")
- # apply default to newly added pages
- tdd.setDefault()
-
- return tdd
-
-
-def main():
- a = qt.QApplication([])
-
- mw = qt.QMainWindow()
- mw.show()
-
- tdd = getFitConfigDialog(mw, default={"a": 1})
- tdd.show()
- tdd.exec_()
- print("TabsDialogData result: ", tdd.result())
- print("TabsDialogData output: ", tdd.output)
-
- a.exec_()
-
-if __name__ == "__main__":
- main()
diff --git a/silx/gui/fit/FitWidget.py b/silx/gui/fit/FitWidget.py
deleted file mode 100644
index 08731f1..0000000
--- a/silx/gui/fit/FitWidget.py
+++ /dev/null
@@ -1,739 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2004-2020 European Synchrotron Radiation Facility
-#
-# This file is part of the PyMca X-ray Fluorescence Toolkit developed at
-# the ESRF by the Software group.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ######################################################################### */
-"""This module provides a widget designed to configure and run a fitting
-process with constraints on parameters.
-
-The main class is :class:`FitWidget`. It relies on
-:mod:`silx.math.fit.fitmanager`, which relies on :func:`silx.math.fit.leastsq`.
-
-The user can choose between functions before running the fit. These function can
-be user defined, or by default are loaded from
-:mod:`silx.math.fit.fittheories`.
-"""
-
-__authors__ = ["V.A. Sole", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "17/07/2018"
-
-import logging
-import sys
-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 .FitConfig import getFitConfigDialog
-from .BackgroundWidget import getBgDialog, BackgroundDialog
-from ...utils.deprecation import deprecated
-
-QTVERSION = qt.qVersion()
-DEBUG = 0
-_logger = logging.getLogger(__name__)
-
-
-__authors__ = ["V.A. Sole", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "30/11/2016"
-
-
-class FitWidget(qt.QWidget):
- """This widget can be used to configure, run and display results of a
- fitting process.
-
- The standard steps for using this widget is to initialize it, then load
- the data to be fitted.
-
- Optionally, you can also load user defined fit theories. If you skip this
- step, a series of default fit functions will be presented (gaussian-like
- functions), and you can later load your custom fit theories from an
- external file using the GUI.
-
- A fit theory is a fit function and its associated features:
-
- - estimation function,
- - list of parameter names
- - numerical derivative algorithm
- - configuration widget
-
- Once the widget is up and running, the user may select a fit theory and a
- background theory, change configuration parameters specific to the theory
- run the estimation, set constraints on parameters and run the actual fit.
-
- The results are displayed in a table.
-
- .. 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:
-
- - *event*: one of the following strings
-
- - *EstimateStarted*,
- - *FitStarted*
- - *EstimateFinished*,
- - *FitFinished*
- - *EstimateFailed*
- - *FitFailed*
-
- - *data*: None, or fit/estimate results (see documentation for
- :attr:`silx.math.fit.fitmanager.FitManager.fit_results`)
- """
-
- def __init__(self, parent=None, title=None, fitmngr=None,
- enableconfig=True, enablestatus=True, enablebuttons=True):
- """
-
- :param parent: Parent widget
- :param title: Window title
- :param fitmngr: User defined instance of
- :class:`silx.math.fit.fitmanager.FitManager`, or ``None``
- :param enableconfig: If ``True``, activate widgets to modify the fit
- configuration (select between several fit functions or background
- functions, apply global constraints, peak search parameters…)
- :param enablestatus: If ``True``, add a fit status widget, to display
- a message when fit estimation is available and when fit results
- are available, as well as a measure of the fit error.
- :param enablebuttons: If ``True``, add buttons to run estimation and
- fitting.
- """
- if title is None:
- title = "FitWidget"
- qt.QWidget.__init__(self, parent)
-
- self.setWindowTitle(title)
- layout = qt.QVBoxLayout(self)
-
- self.fitmanager = self._setFitManager(fitmngr)
- """Instance of :class:`FitManager`.
- This is the underlying data model of this FitWidget.
-
- If no custom theories are defined, the default ones from
- :mod:`silx.math.fit.fittheories` are imported.
- """
-
- # reference fitmanager.configure method for direct access
- self.configure = self.fitmanager.configure
- self.fitconfig = self.fitmanager.fitconfig
-
- self.configdialogs = {}
- """This dictionary defines the fit configuration widgets
- associated with the fit theories in :attr:`fitmanager.theories`
-
- Keys must correspond to existing theory names, i.e. existing keys
- in :attr:`fitmanager.theories`.
-
- Values must be instances of QDialog widgets with an additional
- *output* attribute, a dictionary storing configuration parameters
- interpreted by the corresponding fit theory.
-
- The dialog can also define a *setDefault* method to initialize the
- widget values with values in a dictionary passed as a parameter.
- This will be executed first.
-
- In case the widget does not actually inherit :class:`QDialog`, it
- must at least implement the following methods (executed in this
- particular order):
-
- - :meth:`show`: should cause the widget to become visible to the
- user)
- - :meth:`exec_`: should run while the user is interacting with the
- widget, interrupting the rest of the program. It should
- typically end (*return*) when the user clicks an *OK*
- or a *Cancel* button.
- - :meth:`result`: must return ``True`` if the new configuration in
- attribute :attr:`output` is to be accepted (user clicked *OK*),
- or return ``False`` if :attr:`output` is to be rejected (user
- clicked *Cancel*)
-
- To associate a custom configuration widget with a fit theory, use
- :meth:`associateConfigDialog`. E.g.::
-
- fw = FitWidget()
- my_config_widget = MyGaussianConfigWidget(parent=fw)
- fw.associateConfigDialog(theory_name="Gaussians",
- config_widget=my_config_widget)
- """
-
- self.bgconfigdialogs = {}
- """Same as :attr:`configdialogs`, except that the widget is associated
- with a background theory in :attr:`fitmanager.bgtheories`"""
-
- self._associateDefaultConfigDialogs()
-
- self.guiConfig = None
- """Configuration widget at the top of FitWidget, to select
- fit function, background function, and open an advanced
- configuration dialog."""
-
- self.guiParameters = ParametersTab(self)
- """Table widget for display of fit parameters and constraints"""
-
- if enableconfig:
- self.guiConfig = FitConfigWidget(self)
- """Function selector and configuration widget"""
-
- self.guiConfig.FunConfigureButton.clicked.connect(
- self.__funConfigureGuiSlot)
- self.guiConfig.BgConfigureButton.clicked.connect(
- self.__bgConfigureGuiSlot)
-
- self.guiConfig.WeightCheckBox.setChecked(
- self.fitconfig.get("WeightFlag", False))
- self.guiConfig.WeightCheckBox.stateChanged[int].connect(self.weightEvent)
-
- self.guiConfig.BkgComBox.activated[str].connect(self.bkgEvent)
- self.guiConfig.FunComBox.activated[str].connect(self.funEvent)
- self._populateFunctions()
-
- layout.addWidget(self.guiConfig)
-
- layout.addWidget(self.guiParameters)
-
- if enablestatus:
- self.guistatus = FitStatusLines(self)
- """Status bar"""
- layout.addWidget(self.guistatus)
-
- if enablebuttons:
- self.guibuttons = FitActionsButtons(self)
- """Widget with estimate, start fit and dismiss buttons"""
- self.guibuttons.EstimateButton.clicked.connect(self.estimate)
- self.guibuttons.EstimateButton.setEnabled(False)
- self.guibuttons.StartFitButton.clicked.connect(self.startFit)
- self.guibuttons.StartFitButton.setEnabled(False)
- self.guibuttons.DismissButton.clicked.connect(self.dismiss)
- layout.addWidget(self.guibuttons)
-
- def _setFitManager(self, fitinstance):
- """Initialize a :class:`FitManager` instance, to be assigned to
- :attr:`fitmanager`, or use a custom FitManager instance.
-
- :param fitinstance: Existing instance of FitManager, possibly
- customized by the user, or None to load a default instance."""
- if isinstance(fitinstance, fitmanager.FitManager):
- # customized
- fitmngr = fitinstance
- else:
- # initialize default instance
- fitmngr = fitmanager.FitManager()
-
- # initialize the default fitting functions in case
- # none is present
- if not len(fitmngr.theories):
- fitmngr.loadtheories(fittheories)
-
- return fitmngr
-
- def _associateDefaultConfigDialogs(self):
- """Fill :attr:`bgconfigdialogs` and :attr:`configdialogs` by calling
- :meth:`associateConfigDialog` with default config dialog widgets.
- """
- # 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)
- for theory in self.fitmanager.theories:
- self.associateConfigDialog(theory, configdialog)
- for bgtheory in self.fitmanager.bgtheories:
- self.associateConfigDialog(bgtheory, configdialog,
- theory_is_background=True)
-
- # associate silx.gui.fit.BackgroundWidget with Strip and Snip
- 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)
-
- def _populateFunctions(self):
- """Fill combo-boxes with fit theories and background theories
- loaded by :attr:`fitmanager`.
- Run :meth:`fitmanager.configure` to ensure the custom configuration
- of the selected theory has been loaded into :attr:`fitconfig`"""
- 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)
-
- 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)
-
- # - activate selected fit theory (if any)
- # - activate selected bg theory (if any)
- configuration = self.fitmanager.configure()
- if self.fitmanager.selectedtheory is None:
- # take the first one by default
- self.guiConfig.FunComBox.setCurrentIndex(1)
- self.funEvent(list(self.fitmanager.theories.keys())[0])
- else:
- idx = list(self.fitmanager.theories).index(self.fitmanager.selectedtheory)
- self.guiConfig.FunComBox.setCurrentIndex(idx + 1)
- self.funEvent(self.fitmanager.selectedtheory)
-
- if self.fitmanager.selectedbg is None:
- self.guiConfig.BkgComBox.setCurrentIndex(1)
- self.bkgEvent(list(self.fitmanager.bgtheories.keys())[0])
- else:
- idx = list(self.fitmanager.bgtheories).index(self.fitmanager.selectedbg)
- self.guiConfig.BkgComBox.setCurrentIndex(idx + 1)
- self.bkgEvent(self.fitmanager.selectedbg)
-
- 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.
-
- :param x: Abscissa data. If ``None``, :attr:`xdata`` is set to
- ``numpy.array([0.0, 1.0, 2.0, ..., len(y)-1])``
- :type x: Sequence or numpy array or None
- :param y: The dependant data ``y = f(x)``. ``y`` must have the same
- shape as ``x`` if ``x`` is not ``None``.
- :type y: Sequence or numpy array or None
- :param sigmay: The uncertainties in the ``ydata`` array. These are
- used as weights in the least-squares problem.
- If ``None``, the uncertainties are assumed to be 1.
- :type sigmay: Sequence or numpy array or None
- :param xmin: Lower value of x values to use for fitting
- :param xmax: Upper value of x values to use for fitting
- """
- if y is None:
- self.guibuttons.EstimateButton.setEnabled(False)
- self.guibuttons.StartFitButton.setEnabled(False)
- else:
- self.guibuttons.EstimateButton.setEnabled(True)
- self.guibuttons.StartFitButton.setEnabled(True)
- 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):
- """Associate an instance of custom configuration dialog widget to
- a fit theory or to a background theory.
-
- This adds or modifies an item in the correspondence table
- :attr:`configdialogs` or :attr:`bgconfigdialogs`.
-
- :param str theory_name: Name of fit theory. This must be a key of dict
- :attr:`fitmanager.theories`
- :param config_widget: Custom configuration widget. See documentation
- for :attr:`configdialogs`
- :param bool theory_is_background: If flag is *True*, add dialog to
- :attr:`bgconfigdialogs` rather than :attr:`configdialogs`
- (default).
- :raise: KeyError if parameter ``theory_name`` does not match an
- existing fit theory or background theory in :attr:`fitmanager`.
- :raise: AttributeError if the widget does not implement the mandatory
- methods (*show*, *exec_*, *result*, *setDefault*) or the mandatory
- attribute (*output*).
- """
- 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:
- for mandatory_attr in ["show", "exec_", "result", "output"]:
- if not hasattr(config_widget, mandatory_attr):
- raise AttributeError(
- "Custom configuration widget must define " +
- "attribute or method " + mandatory_attr)
-
- if theory_is_background:
- self.bgconfigdialogs[theory_name] = config_widget
- else:
- self.configdialogs[theory_name] = config_widget
-
- def _emitSignal(self, ddict):
- """Emit pyqtSignal after estimation completed
- (``ddict = {'event': 'EstimateFinished', 'data': fit_results}``)
- and after fit completed
- (``ddict = {'event': 'FitFinished', 'data': fit_results}``)"""
- self.sigFitWidgetSignal.emit(ddict)
-
- def __funConfigureGuiSlot(self):
- """Open an advanced configuration dialog widget"""
- self.__configureGui(dialog_type="function")
-
- def __bgConfigureGuiSlot(self):
- """Open an advanced configuration dialog widget"""
- self.__configureGui(dialog_type="background")
-
- def __configureGui(self, newconfiguration=None, dialog_type="function"):
- """Open an advanced configuration dialog widget to get a configuration
- dictionary, or use a supplied configuration dictionary. Call
- :meth:`configure` with this dictionary as a parameter. Update the gui
- accordingly. Reinitialize the fit results in the table and in
- :attr:`fitmanager`.
-
- :param newconfiguration: User supplied configuration dictionary. If ``None``,
- open a dialog widget that returns a dictionary."""
- configuration = self.configure()
- # get new dictionary
- if newconfiguration is None:
- newconfiguration = self.configureDialog(configuration, dialog_type)
- # update configuration
- configuration.update(self.configure(**newconfiguration))
- # set fit function theory
- try:
- 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)
- self.funEvent(list(self.fitmanager.theories.keys())[0])
- # current background
- try:
- 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)
- self.bkgEvent(list(self.fitmanager.bgtheories.keys())[0])
-
- # update the Gui
- self.__initialParameters()
-
- def configureDialog(self, oldconfiguration, dialog_type="function"):
- """Display a dialog, allowing the user to define fit configuration
- parameters.
-
- By default, a common dialog is used for all fit theories. But if the
- defined a custom dialog using :meth:`associateConfigDialog`, it is
- used instead.
-
- :param dict oldconfiguration: Dictionary containing previous configuration
- :param str dialog_type: "function" or "background"
- :return: User defined parameters in a dictionary
- """
- newconfiguration = {}
- newconfiguration.update(oldconfiguration)
-
- if dialog_type == "function":
- theory = self.fitmanager.selectedtheory
- configdialog = self.configdialogs[theory]
- elif dialog_type == "background":
- theory = self.fitmanager.selectedbg
- configdialog = self.bgconfigdialogs[theory]
-
- # this should only happen if a user specifically associates None
- # with a theory, to have no configuration option
- if configdialog is None:
- return {}
-
- # update state of configdialog before showing it
- if hasattr(configdialog, "setDefault"):
- configdialog.setDefault(newconfiguration)
- configdialog.show()
- configdialog.exec_()
- if configdialog.result():
- newconfiguration.update(configdialog.output)
-
- return newconfiguration
-
- def estimate(self):
- """Run parameter estimation function then emit
- :attr:`sigFitWidgetSignal` with a dictionary containing a status
- message and a list of fit parameters estimations
- in the format defined in
- :attr:`silx.math.fit.fitmanager.FitManager.fit_results`
-
- The emitted dictionary has an *"event"* key that can have
- following values:
-
- - *'EstimateStarted'*
- - *'EstimateFailed'*
- - *'EstimateFinished'*
- """
- try:
- theory_name = self.fitmanager.selectedtheory
- estimation_function = self.fitmanager.theories[theory_name].estimate
- if estimation_function is not None:
- ddict = {'event': 'EstimateStarted',
- 'data': None}
- self._emitSignal(ddict)
- self.fitmanager.estimate(callback=self.fitStatus)
- else:
- msg = qt.QMessageBox(self)
- msg.setIcon(qt.QMessageBox.Information)
- text = "Function does not define a way to estimate\n"
- 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.exec_()
- return
- 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}
- 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._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
- parameters results in the format defined in
- :attr:`silx.math.fit.fitmanager.FitManager.fit_results`
-
- The emitted dictionary has an *"event"* key that can have
- following values:
-
- - *'FitStarted'*
- - *'FitFailed'*
- - *'FitFinished'*
- """
- self.fitmanager.fit_results = self.guiParameters.getFitResults()
- try:
- 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())
- 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
- }
- 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._emitSignal(ddict)
- return
-
- def bkgEvent(self, bgtheory):
- """Select background theory, then reinitialize parameters"""
- bgtheory = str(bgtheory)
- if bgtheory in self.fitmanager.bgtheories:
- self.fitmanager.setbackground(bgtheory)
- else:
- functionsfile = qt.QFileDialog.getOpenFileName(
- 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")
- return
- else:
- # empty the ComboBox
- while self.guiConfig.BkgComBox.count() > 1:
- self.guiConfig.BkgComBox.removeItem(1)
- # and fill it again
- for key in self.fitmanager.bgtheories:
- self.guiConfig.BkgComBox.addItem(str(key))
-
- i = 1 + \
- list(self.fitmanager.bgtheories.keys()).index(
- self.fitmanager.selectedbg)
- self.guiConfig.BkgComBox.setCurrentIndex(i)
- self.__initialParameters()
-
- def funEvent(self, theoryname):
- """Select a fit theory to be used for fitting. If this theory exists
- in :attr:`fitmanager`, use it. Then, reinitialize table.
-
- :param theoryname: Name of the fit theory to use for fitting. If this theory
- exists in :attr:`fitmanager`, use it. Else, open a file dialog to open
- a custom fit function definition file with
- :meth:`fitmanager.loadtheories`.
- """
- theoryname = str(theoryname)
- if theoryname in self.fitmanager.theories:
- self.fitmanager.settheory(theoryname)
- else:
- # open a load file dialog
- functionsfile = qt.QFileDialog.getOpenFileName(
- 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")
- return
- else:
- # empty the ComboBox
- while self.guiConfig.FunComBox.count() > 1:
- self.guiConfig.FunComBox.removeItem(1)
- # and fill it again
- for key in self.fitmanager.theories:
- self.guiConfig.FunComBox.addItem(str(key))
-
- i = 1 + \
- list(self.fitmanager.theories.keys()).index(
- self.fitmanager.selectedtheory)
- self.guiConfig.FunComBox.setCurrentIndex(i)
- self.__initialParameters()
-
- def weightEvent(self, flag):
- """This is called when WeightCheckBox is clicked, to configure the
- *WeightFlag* field in :attr:`fitmanager.fitconfig` and set weights
- in the least-square problem."""
- self.configure(WeightFlag=flag)
- if flag:
- self.fitmanager.enableweight()
- else:
- # set weights back to 1
- self.fitmanager.disableweight()
-
- def __initialParameters(self):
- """Fill the fit parameters names with names of the parameters of
- the selected background theory and the selected fit theory.
- Initialize :attr:`fitmanager.fit_results` with these names, and
- initialize the table with them. This creates a view called "Fit"
- in :attr:`guiParameters`"""
- self.fitmanager.parameter_names = []
- 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})
- 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')
-
- def fitStatus(self, data):
- """Set *status* and *chisq* in status bar"""
- if 'chisq' in data:
- if data['chisq'] is None:
- self.guistatus.ChisqLine.setText(" ")
- else:
- chisq = data['chisq']
- self.guistatus.ChisqLine.setText("%6.2f" % chisq)
-
- if 'status' in data:
- status = data['status']
- self.guistatus.StatusLine.setText(str(status))
-
- def dismiss(self):
- """Close FitWidget"""
- self.close()
-
-
-if __name__ == "__main__":
- import numpy
-
- 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.]
- y = functions.sum_gauss(x, *p) + constant_bg
-
- a = qt.QApplication(sys.argv)
- w = FitWidget()
- w.setData(x=x, y=y)
- w.show()
- a.exec_()
diff --git a/silx/gui/fit/FitWidgets.py b/silx/gui/fit/FitWidgets.py
deleted file mode 100644
index 408666b..0000000
--- a/silx/gui/fit/FitWidgets.py
+++ /dev/null
@@ -1,559 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2004-2016 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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ######################################################################### */
-"""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
-
-QTVERSION = qt.qVersion()
-
-__authors__ = ["V.A. Sole", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "13/10/2016"
-
-
-class FitActionsButtons(qt.QWidget):
- """Widget with 3 ``QPushButton``:
-
- The buttons can be accessed as public attributes::
-
- - ``EstimateButton``
- - ``StartFitButton``
- - ``DismissButton``
-
- You will typically need to access these attributes to connect the buttons
- to actions. For instance, if you have 3 functions ``estimate``,
- ``runfit`` and ``dismiss``, you can connect them like this::
-
- >>> fit_actions_buttons = FitActionsButtons()
- >>> fit_actions_buttons.EstimateButton.clicked.connect(estimate)
- >>> fit_actions_buttons.StartFitButton.clicked.connect(runfit)
- >>> fit_actions_buttons.DismissButton.clicked.connect(dismiss)
-
- """
-
- def __init__(self, parent=None):
- qt.QWidget.__init__(self, parent)
-
- self.resize(234, 53)
-
- grid_layout = qt.QGridLayout(self)
- grid_layout.setContentsMargins(11, 11, 11, 11)
- grid_layout.setSpacing(6)
- layout = qt.QHBoxLayout(None)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(6)
-
- self.EstimateButton = qt.QPushButton(self)
- self.EstimateButton.setText("Estimate")
- layout.addWidget(self.EstimateButton)
- 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)
- layout.addItem(spacer_2)
-
- self.DismissButton = qt.QPushButton(self)
- self.DismissButton.setText("Dismiss")
- layout.addWidget(self.DismissButton)
-
- grid_layout.addLayout(layout, 0, 0)
-
-
-class FitStatusLines(qt.QWidget):
- """Widget with 2 greyed out write-only ``QLineEdit``.
-
- These text widgets can be accessed as public attributes::
-
- - ``StatusLine``
- - ``ChisqLine``
-
- You will typically need to access these widgets to update the displayed
- text::
-
- >>> fit_status_lines = FitStatusLines()
- >>> fit_status_lines.StatusLine.setText("Ready")
- >>> fit_status_lines.ChisqLine.setText("%6.2f" % 0.01)
-
- """
-
- def __init__(self, parent=None):
- qt.QWidget.__init__(self, parent)
-
- self.resize(535, 47)
-
- layout = qt.QHBoxLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(6)
-
- self.StatusLabel = qt.QLabel(self)
- self.StatusLabel.setText("Status:")
- layout.addWidget(self.StatusLabel)
-
- self.StatusLine = qt.QLineEdit(self)
- self.StatusLine.setText("Ready")
- self.StatusLine.setReadOnly(1)
- layout.addWidget(self.StatusLine)
-
- self.ChisqLabel = qt.QLabel(self)
- self.ChisqLabel.setText("Reduced chisq:")
- layout.addWidget(self.ChisqLabel)
-
- self.ChisqLine = qt.QLineEdit(self)
- self.ChisqLine.setMaximumSize(qt.QSize(16000, 32767))
- self.ChisqLine.setText("")
- self.ChisqLine.setReadOnly(1)
- layout.addWidget(self.ChisqLine)
-
-
-class FitConfigWidget(qt.QWidget):
- """Widget whose purpose is to select a fit theory and a background
- theory, load a new fit theory definition file and provide
- a "Configure" button to open an advanced configuration dialog.
-
- This is used in :class:`silx.gui.fit.FitWidget.FitWidget`, to offer
- an interface to quickly modify the main parameters prior to running a fit:
-
- - select a fitting function through :attr:`FunComBox`
- - select a background function through :attr:`BkgComBox`
- - open a dialog for modifying advanced parameters through
- :attr:`FunConfigureButton`
- """
- def __init__(self, parent=None):
- qt.QWidget.__init__(self, parent)
-
- self.setWindowTitle("FitConfigGUI")
-
- layout = qt.QGridLayout(self)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSpacing(6)
-
- self.FunLabel = qt.QLabel(self)
- self.FunLabel.setText("Function")
- layout.addWidget(self.FunLabel, 0, 0)
-
- 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)
- layout.addWidget(self.FunComBox, 0, 1)
-
- self.BkgLabel = qt.QLabel(self)
- self.BkgLabel.setText("Background")
- layout.addWidget(self.BkgLabel, 1, 0)
-
- 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)
- 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")
- 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")
- 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).")
-
- layout.addWidget(self.WeightCheckBox, 0, 3, 2, 1)
-
- layout.setColumnStretch(4, 1)
-
-
-class ParametersTab(qt.QTabWidget):
- """This widget provides tabs to display and modify fit parameters. Each
- tab contains a table with fit data such as parameter names, estimated
- values, fit constraints, and final fit results.
-
- The usual way to initialize the table is to fill it with the fit
- parameters from a :class:`silx.math.fit.fitmanager.FitManager` object, after
- the estimation process or after the final fit.
-
- In the following example we use a :class:`ParametersTab` to display the
- results of two separate fits::
-
- from silx.math.fit import fittheories
- from silx.math.fit import fitmanager
- from silx.math.fit import functions
- from silx.gui import qt
- import numpy
-
- a = qt.QApplication([])
-
- # Create synthetic data
- x = numpy.arange(1000)
- y1 = functions.sum_gauss(x, 100, 400, 100)
-
- fit = fitmanager.FitManager(x=x, y=y1)
-
- fitfuns = fittheories.FitTheories()
- fit.addtheory(theory="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()
- fit.runfit()
-
- # Show first fit result in a tab in our widget
- w = ParametersTab()
- w.show()
- w.fillFromFit(fit.fit_results, view='Gaussians')
-
- # new synthetic data
- 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(theory="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()
-
- # Show first fit result in another tab in our widget
- w.fillFromFit(fit.fit_results, view='Asymetric gaussians')
- a.exec_()
-
- """
-
- def __init__(self, parent=None, name="FitParameters"):
- """
-
- :param parent: Parent widget
- :param name: Widget title
- """
- qt.QTabWidget.__init__(self, parent)
- self.setWindowTitle(name)
- self.setContentsMargins(0, 0, 0, 0)
-
- self.views = OrderedDict()
- """Dictionary of views. Keys are view names,
- items are :class:`Parameters` widgets"""
-
- self.latest_view = None
- """Name of latest view"""
-
- # the widgets/tables themselves
- self.tables = {}
- """Dictionary of :class:`silx.gui.fit.parameters.Parameters` objects.
- These objects store fit results
- """
-
- self.setContentsMargins(10, 10, 10, 10)
-
- def setView(self, view=None, fitresults=None):
- """Add or update a table. Fill it with data from a fit
-
- :param view: Tab name to be added or updated. If ``None``, use the
- latest view.
- :param fitresults: Fit data to be added to the table
- :raise: KeyError if no view name specified and no latest view
- available.
- """
- if view is None:
- if self.latest_view is not None:
- view = self.latest_view
- else:
- raise KeyError(
- "No view available. You must specify a view" +
- " name the first time you call this method."
- )
-
- if view in self.tables.keys():
- table = self.tables[view]
- else:
- # create the parameters instance
- self.tables[view] = Parameters(self)
- table = self.tables[view]
- self.views[view] = table
- self.addTab(table, str(view))
-
- if fitresults is not None:
- table.fillFromFit(fitresults)
-
- self.setCurrentWidget(self.views[view])
- self.latest_view = view
-
- def renameView(self, oldname=None, newname=None):
- """Rename a view (tab)
-
- :param oldname: Name of the view to be renamed
- :param newname: New name of the view"""
- error = 1
- if newname is not None:
- if newname not in self.views.keys():
- if oldname in self.views.keys():
- parameterlist = self.tables[oldname].getFitResults()
- self.setView(view=newname, fitresults=parameterlist)
- self.removeView(oldname)
- error = 0
- return error
-
- def fillFromFit(self, fitparameterslist, view=None):
- """Update a view with data from a fit (alias for :meth:`setView`)
-
- :param view: Tab name to be added or updated (default: latest view)
- :param fitparameterslist: Fit data to be added to the table
- """
- self.setView(view=view, fitresults=fitparameterslist)
-
- def getFitResults(self, name=None):
- """Call :meth:`getFitResults` for the
- :class:`silx.gui.fit.parameters.Parameters` corresponding to the
- latest table or to the named table (if ``name`` is not
- ``None``). This return a list of dictionaries in the format used by
- :class:`silx.math.fit.fitmanager.FitManager` to store fit parameter
- results.
-
- :param name: View name.
- """
- if name is None:
- name = self.latest_view
- return self.tables[name].getFitResults()
-
- def removeView(self, name):
- """Remove a view by name.
-
- :param name: View name.
- """
- if name in self.views:
- index = self.indexOf(self.tables[name])
- self.removeTab(index)
- index = self.indexOf(self.views[name])
- self.removeTab(index)
- del self.tables[name]
- del self.views[name]
-
- def removeAllViews(self, keep=None):
- """Remove all views, except the one specified (argument
- ``keep``)
-
- :param keep: Name of the view to be kept."""
- for view in self.tables:
- if view != keep:
- self.removeView(view)
-
- def getHtmlText(self, name=None):
- """Return the table data as HTML
-
- :param name: View name."""
- if name is None:
- name = self.latest_view
- table = self.tables[name]
- lemon = ("#%x%x%x" % (255, 250, 205)).upper()
- hcolor = ("#%x%x%x" % (230, 240, 249)).upper()
- text = ""
- text += "<nobr>"
- text += "<table>"
- text += "<tr>"
- ncols = table.columnCount()
- for l in range(ncols):
- text += ('<td align="left" bgcolor="%s"><b>' % hcolor)
- if QTVERSION < '4.0.0':
- text += (str(table.horizontalHeader().label(l)))
- else:
- text += (str(table.horizontalHeaderItem(l).text()))
- text += "</b></td>"
- text += "</tr>"
- nrows = table.rowCount()
- for r in range(nrows):
- text += "<tr>"
- item = table.item(r, 0)
- newtext = ""
- if item is not None:
- newtext = str(item.text())
- if len(newtext):
- color = "white"
- b = "<b>"
- else:
- b = ""
- color = lemon
- try:
- # MyQTable item has color defined
- cc = table.item(r, 0).color
- cc = ("#%x%x%x" % (cc.red(), cc.green(), cc.blue())).upper()
- color = cc
- except:
- pass
- for c in range(ncols):
- item = table.item(r, c)
- newtext = ""
- if item is not None:
- newtext = str(item.text())
- if len(newtext):
- finalcolor = color
- else:
- finalcolor = "white"
- if c < 2:
- text += ('<td align="left" bgcolor="%s">%s' %
- (finalcolor, b))
- else:
- text += ('<td align="right" bgcolor="%s">%s' %
- (finalcolor, b))
- text += newtext
- if len(b):
- text += "</td>"
- else:
- text += "</b></td>"
- item = table.item(r, 0)
- newtext = ""
- if item is not None:
- newtext = str(item.text())
- if len(newtext):
- text += "</b>"
- text += "</tr>"
- text += "\n"
- text += "</table>"
- text += "</nobr>"
- return text
-
- def getText(self, name=None):
- """Return the table data as CSV formatted text, using tabulation
- characters as separators.
-
- :param name: View name."""
- if name is None:
- name = self.latest_view
- table = self.tables[name]
- text = ""
- ncols = table.columnCount()
- for l in range(ncols):
- text += (str(table.horizontalHeaderItem(l).text())) + "\t"
- text += "\n"
- nrows = table.rowCount()
- for r in range(nrows):
- for c in range(ncols):
- newtext = ""
- if c != 4:
- item = table.item(r, c)
- if item is not None:
- newtext = str(item.text())
- else:
- item = table.cellWidget(r, c)
- if item is not None:
- newtext = str(item.currentText())
- text += newtext + "\t"
- text += "\n"
- text += "\n"
- return text
-
-
-def test():
- from silx.math.fit import fittheories
- from silx.math.fit import fitmanager
- from silx.math.fit import functions
- from silx.gui.plot.PlotWindow import PlotWindow
- import numpy
-
- a = qt.QApplication([])
-
- x = numpy.arange(1000)
- y1 = functions.sum_gauss(x, 100, 400, 100)
-
- 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
- fit.estimate()
- fit.runfit()
-
- w = ParametersTab()
- w.show()
- w.fillFromFit(fit.fit_results, view='Gaussians')
-
- 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
- fit.estimate()
- fit.runfit()
-
- w.fillFromFit(fit.fit_results, view='Asymetric gaussians')
-
- # Plot
- pw = PlotWindow(control=True)
- pw.addCurve(x, y1, "Gaussians")
- pw.addCurve(x, y2, "Asymetric gaussians")
- pw.show()
-
- a.exec_()
-
-
-if __name__ == "__main__":
- test()
diff --git a/silx/gui/fit/Parameters.py b/silx/gui/fit/Parameters.py
deleted file mode 100644
index 62e3278..0000000
--- a/silx/gui/fit/Parameters.py
+++ /dev/null
@@ -1,882 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2004-2017 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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ######################################################################### */
-"""This module defines a table widget that is specialized in displaying fit
-parameter results and associated constraints."""
-__authors__ = ["V.A. Sole", "P. Knobel"]
-__license__ = "MIT"
-__date__ = "25/11/2016"
-
-import sys
-from collections import OrderedDict
-
-from silx.gui import qt
-from silx.gui.widgets.TableWidget import TableWidget
-
-
-def float_else_zero(sstring):
- """Return converted string to float. If conversion fail, return zero.
-
- :param sstring: String to be converted
- :return: ``float(sstrinq)`` if ``sstring`` can be converted to float
- (e.g. ``"3.14"``), else ``0``
- """
- try:
- return float(sstring)
- except ValueError:
- return 0
-
-
-class QComboTableItem(qt.QComboBox):
- """:class:`qt.QComboBox` augmented with a ``sigCellChanged`` signal
- to emit a tuple of ``(row, column)`` coordinates when the value is
- changed.
-
- This signal can be used to locate the modified combo box in a table.
-
- :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."""
-
- def __init__(self, parent=None, row=None, col=None):
- self._row = row
- self._col = col
- qt.QComboBox.__init__(self, parent)
- self.activated[int].connect(self._cellChanged)
-
- def _cellChanged(self, idx): # noqa
- self.sigCellChanged.emit(self._row, self._col)
-
-
-class QCheckBoxItem(qt.QCheckBox):
- """:class:`qt.QCheckBox` augmented with a ``sigCellChanged`` signal
- to emit a tuple of ``(row, column)`` coordinates when the check box has
- been clicked on.
-
- This signal can be used to locate the modified check box in a table.
-
- :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."""
-
- def __init__(self, parent=None, row=None, col=None):
- self._row = row
- self._col = col
- qt.QCheckBox.__init__(self, parent)
- self.clicked.connect(self._cellChanged)
-
- def _cellChanged(self):
- self.sigCellChanged.emit(self._row, self._col)
-
-
-class Parameters(TableWidget):
- """:class:`TableWidget` customized to display fit results
- and to interact with :class:`FitManager` objects.
-
- Data and references to cell widgets are kept in a dictionary
- attribute :attr:`parameters`.
-
- :param parent: Parent widget
- :param labels: Column headers. If ``None``, default headers will be used.
- :type labels: List of strings or None
- :param paramlist: List of fit parameters to be displayed for each fitted
- 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']
- """This list assigns shorter keys to refer to columns than the
- displayed labels."""
-
- self.__configuring = False
-
- # column headers and associated tooltips
- self.setColumnCount(len(labels))
-
- for i, label in enumerate(labels):
- item = self.horizontalHeaderItem(i)
- if item is None:
- item = qt.QTableWidgetItem(label,
- qt.QTableWidgetItem.Type)
- self.setHorizontalHeaderItem(i, item)
-
- item.setText(label)
- if tooltips is not None:
- item.setToolTip(tooltips[i])
-
- # resize columns
- for col_key in ["name", "estimation", "sigma", "val1", "val2"]:
- col_idx = self.columnIndexByField(col_key)
- self.resizeColumnToContents(col_idx)
-
- # Initialize the table with one line per supplied parameter
- paramlist = paramlist if paramlist is not None else []
- self.parameters = OrderedDict()
- """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`
-
- Keys of the dictionary are:
-
- - 'name': parameter name
- - 'line': line index for the parameter in the table
- - 'estimation'
- - 'fitresult'
- - 'sigma'
- - 'code': constraint code (one of the elements of
- :attr:`code_options`)
- - 'val1': first parameter related to constraint, formatted
- as a string, as typed in the table
- - 'val2': second parameter related to constraint, formatted
- as a string, as typed in the table
- - 'cons1': scalar representation of 'val1'
- (e.g. when val1 is the name of a fit parameter, cons1
- will be the line index of this parameter)
- - 'cons2': scalar representation of 'val2'
- - 'vmin': equal to 'val1' when 'code' is "QUOTED"
- - 'vmax': equal to 'val2' when 'code' is "QUOTED"
- - 'relatedto': name of related parameter when this parameter
- is constrained to another parameter (same as 'val1')
- - 'factor': same as 'val2' when 'code' is 'FACTOR'
- - 'delta': same as 'val2' when 'code' is 'DELTA'
- - 'sum': same as 'val2' when 'code' is 'SUM'
- - 'group': group index for the parameter
- - 'xmin': data range minimum
- - 'xmax': data range maximum
- """
- for line, param in enumerate(paramlist):
- self.newParameterLine(param, line)
-
- self.code_options = ["FREE", "POSITIVE", "QUOTED", "FIXED",
- "FACTOR", "DELTA", "SUM", "IGNORE", "ADD"]
- """Possible values in the combo boxes in the 'Constraints' column.
- """
-
- # connect signal
- self.cellChanged[int, int].connect(self.onCellChanged)
-
- def newParameterLine(self, param, line):
- """Add a line to the :class:`QTableWidget`.
-
- Each line represents one of the fit parameters for one of
- the fitted peaks.
-
- :param param: Name of the fit parameter
- :type param: str
- :param line: 0-based line index
- :type line: int
- """
- # get current number of lines
- nlines = self.rowCount()
- self.__configuring = True
- if line >= nlines:
- 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'])
-
- # Constraint codes
- a = []
- for option in self.code_options:
- a.append(option)
-
- 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.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.__configuring = False
-
- def columnIndexByField(self, field):
- """
-
- :param field: Field name (column key)
- :return: Index of the column with this field name
- """
- return self.columnKeys.index(field)
-
- def fillFromFit(self, fitresults):
- """Fill table with values from a list of dictionaries
- (see :attr:`silx.math.fit.fitmanager.FitManager.fit_results`)
-
- :param fitresults: List of parameters as recorded
- in the ``paramlist`` attribute of a :class:`FitManager` object
- :type fitresults: list[dict]
- """
- self.setRowCount(len(fitresults))
-
- # Reinitialize and fill self.parameters
- self.parameters = OrderedDict()
- for (line, param) in enumerate(fitresults):
- self.newParameterLine(param['name'], line)
-
- for param in fitresults:
- 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)
-
- def getConfiguration(self):
- """Return ``FitManager.paramlist`` dictionary
- encapsulated in another dictionary"""
- 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'])
-
- def getFitResults(self):
- """Return fit parameters as a list of dictionaries in the format used
- by :class:`FitManager` (attribute ``paramlist``).
- """
- fitparameterslist = []
- for param in self.parameters:
- 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']
- if len(buf):
- fitresult = float(buf)
- else:
- fitresult = 0.0
- buf = str(self.parameters[param]['sigma'])
- if len(buf):
- sigma = float(buf)
- else:
- sigma = 0.0
- 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
- fitparameterslist.append(fitparam)
- return fitparameterslist
-
- def onCellChanged(self, row, col):
- """Slot called when ``cellChanged`` signal is emitted.
- Checks the validity of the new text in the cell, then calls
- :meth:`configureLine` to update the internal ``self.parameters``
- dictionary.
-
- :param row: Row number of the changed cell (0-based index)
- :param col: Column number of the changed cell (0-based index)
- """
- if (col != self.columnIndexByField("code")) and (col != -1):
- if row != self.currentRow():
- return
- if col != self.currentColumn():
- return
- if self.__configuring:
- return
- param = list(self.parameters)[row]
- field = self.columnKeys[col]
- oldvalue = self.parameters[param][field]
- if col != 4:
- item = self.item(row, col)
- if item is not None:
- newvalue = item.text()
- else:
- newvalue = ''
- else:
- # this is the combobox
- widget = self.cellWidget(row, col)
- newvalue = widget.currentText()
- if self.validate(param, field, oldvalue, newvalue):
- paramdict = {"name": param, field: newvalue}
- self.configureLine(**paramdict)
- else:
- 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)
- finally:
- self.__configuring = False
- else:
- paramdict = {"name": param, field: oldvalue}
- self.configureLine(**paramdict)
-
- def validate(self, param, field, oldvalue, newvalue):
- """Check validity of ``newvalue`` when a cell's value is modified.
-
- :param param: Fit parameter name
- :param field: Column name
- :param oldvalue: Cell value before change attempt
- :param newvalue: New value to be validated
- :return: True if new cell value is valid, else False
- """
- 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']:
- _, candidates = self.getRelatedCandidates(param)
- # We expect val1 to be a fit parameter name
- if str(newvalue) in candidates:
- return True
- else:
- return False
- # except for code, val1 and name (which is read-only and does not need
- # validation), all fields must always be convertible to float
- else:
- try:
- float(str(newvalue))
- except ValueError:
- return False
- return True
-
- def setCodeValue(self, param, oldvalue, newvalue):
- """Update 'code' and 'relatedto' fields when code cell is
- changed.
-
- :param param: Fit parameter name
- :param oldvalue: Cell value before change attempt
- :param newvalue: New value to be validated
- :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':
- self.freeRestOfGroup(param)
- return True
- 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.freeRestOfGroup(param)
- return True
-
- 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'])))
- candidates = []
- for param in self.parameters.keys():
- 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)
- return True
- 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']))):
- i += 1
- if (group == 0) and (i == 1): # FIXME: why +1?
- i += 1
- self.addGroup(i, group)
- return False
- elif str(newvalue) == 'SHOW':
- print(self.getEstimationConstraints(param))
- return False
-
- def addGroup(self, newg, gtype):
- """Add a fit parameter group with the same fit parameters as an
- existing group.
-
- This function is called when the user selects "ADD" in the
- "constraints" combobox.
-
- :param int newg: New group number
- :param int gtype: Group number whose parameters we want to copy
-
- """
- newparam = []
- # loop through parameters until we encounter group number `gtype`
- for param in list(self.parameters):
- 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']
-
- # Add new parameters (one table line per parameter) and configureLine each
- # one by updating xmin and xmax to the same values as group `gtype`
- line = len(list(self.parameters))
- for param in newparam:
- self.newParameterLine(param, line)
- line += 1
- for param in newparam:
- self.configureLine(name=param, group=newg, xmin=xmin, xmax=xmax)
-
- def freeRestOfGroup(self, workparam):
- """Set ``code`` to ``"FREE"`` for all fit parameters belonging to
- the same group as ``workparam``. This is done when the entire group
- of parameters was previously ignored and one of them has his code
- set to something different than ``"IGNORE"``.
-
- :param workparam: Fit parameter name
- """
- if workparam in self.parameters.keys():
- 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='')
-
- def getRelatedCandidates(self, workparam):
- """If fit parameter ``workparam`` has a constraint that involves other
- fit parameters, find possible candidates and try to guess which one
- is the most likely.
-
- :param workparam: Fit parameter name
- :return: (best_candidate, possible_candidates) tuple
- :rtype: (str, list[str])
- """
- candidates = []
- 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']:
- 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'])
- return best, candidates
- # take the first with same base name (after removing numbers)
- for param_name in candidates:
- basename = param_name.rstrip("0123456789")
- try:
- pos = workparam.index(basename)
- if pos == 0:
- best = param_name
- return best, candidates
- except ValueError:
- pass
- # take the first
- return candidates[0], candidates
-
- def setReadOnly(self, parameter, fields):
- """Make table cells read-only by setting it's flags and omitting
- flag ``qt.Qt.ItemIsEditable``
-
- :param parameter: Fit parameter names identifying the rows
- :type parameter: str or list[str]
- :param fields: Field names identifying the columns
- :type fields: str or list[str]
- """
- editflags = qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled
- self.setField(parameter, fields, editflags)
-
- def setReadWrite(self, parameter, fields):
- """Make table cells read-write by setting it's flags including
- flag ``qt.Qt.ItemIsEditable``
-
- :param parameter: Fit parameter names identifying the rows
- :type parameter: str or list[str]
- :param fields: Field names identifying the columns
- :type fields: str or list[str]
- """
- editflags = qt.Qt.ItemIsSelectable |\
- qt.Qt.ItemIsEnabled |\
- qt.Qt.ItemIsEditable
- self.setField(parameter, fields, editflags)
-
- def setField(self, parameter, fields, edit_flags):
- """Set text and flags in a table cell.
-
- :param parameter: Fit parameter names identifying the rows
- :type parameter: str or list[str]
- :param fields: Field names identifying the columns
- :type fields: str or list[str]
- :param edit_flags: Flag combination, e.g::
-
- qt.Qt.ItemIsSelectable | qt.Qt.ItemIsEnabled |
- qt.Qt.ItemIsEditable
- """
- if isinstance(parameter, list) or \
- isinstance(parameter, tuple):
- paramlist = parameter
- else:
- paramlist = [parameter]
- if isinstance(fields, list) or \
- isinstance(fields, tuple):
- fieldlist = fields
- else:
- fieldlist = [fields]
-
- # Set _configuring flag to ignore cellChanged signals in
- # self.onCellChanged
- _oldvalue = self.__configuring
- self.__configuring = True
-
- # 2D loop through parameter list and field list
- # to update their cells
- for param in paramlist:
- row = list(self.parameters.keys()).index(param)
- for field in fieldlist:
- col = self.columnIndexByField(field)
- if field != 'code':
- key = field + "_item"
- item = self.item(row, col)
- if item is None:
- item = qt.QTableWidgetItem()
- item.setText(self.parameters[param][field])
- self.setItem(row, col, item)
- else:
- item.setText(self.parameters[param][field])
- self.parameters[param][key] = item
- item.setFlags(edit_flags)
-
- # 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):
- """This function updates values in a line of the table
-
- :param name: Name of the parameter (serves as unique identifier for
- a line).
- :param code: Constraint code *FREE, FIXED, POSITIVE, DELTA, FACTOR,
- SUM, QUOTED, IGNORE*
- :param val1: Constraint 1 (can be the index or name of another
- parameter for code *DELTA, FACTOR, SUM*, or a min value
- for code *QUOTED*)
- :param val2: Constraint 2
- :param sigma: Standard deviation for a fit parameter
- :param estimation: Estimated initial value for a fit parameter (used
- as input to iterative fit)
- :param fitresult: Final result of fit
- :param group: Group number of a fit parameter (peak number when doing
- multi-peak fitting, as each peak corresponds to a group
- of several consecutive parameters)
- :param xmin:
- :param xmax:
- :param relatedto: Index or name of another fit parameter
- to which this parameter is related to (constraints)
- :param cons1: similar meaning to ``val1``, but is always a number
- :param cons2: similar meaning to ``val2``, but is always a number
- :return:
- """
- paramlist = list(self.parameters.keys())
-
- if name not in self.parameters:
- raise KeyError("'%s' is not in the parameter list" % name)
-
- # update code first, if specified
- if code is not None:
- code = str(code)
- self.parameters[name]['code'] = code
- # update combobox
- 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']
-
- # val1 and sigma have special formats
- if val1 is not None:
- 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))
- 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))
- 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 val1 is not None:
- self.parameters[name]['vmin'] = self.parameters[name]['val1']
- else:
- self.parameters[name]['val1'] = self.parameters[name]['vmin']
- if val2 is not None:
- self.parameters[name]['vmax'] = self.parameters[name]['val2']
- else:
- 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'])
-
- # 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']
-
- 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"]
-
- elif val1 is not None:
- # val1 could be the index of the fit parameter
- try:
- self.parameters[name]['relatedto'] = paramlist[int(val1)]
- except ValueError:
- self.parameters[name]['relatedto'] = self.parameters[name]["val1"]
-
- elif relatedto is not None:
- # code changed, val1 not specified but relatedto specified:
- # set val1 to relatedto (pre-fill best guess)
- self.parameters[name]["val1"] = relatedto
-
- # update fields "delta", "sum" or "factor"
- key = code.lower()
- 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']
-
- # 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'])
-
- # cons2 is the constraint value (factor, delta or sum)
- try:
- self.parameters[name]['cons2'] =\
- float(str(self.parameters[name]['val2']))
- except ValueError:
- 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
-
- self._updateCellRWFlags(name, code)
-
- def _updateField(self, name, field, value, fmat=None):
- """Update field in ``self.parameters`` dictionary, if the new value
- is valid.
-
- :param name: Fit parameter name
- :param field: Field name
- :param value: New value to assign
- :type value: String
- :param fmat: Format string (e.g. "%8g") to be applied if value represents
- a scalar. If ``None``, format is not modified. If ``value`` is an
- empty string, ``fmat`` is ignored.
- """
- if value is not None:
- oldvalue = self.parameters[name][field]
- if fmat is not None:
- newvalue = fmat % float(value) if value != "" else ""
- else:
- newvalue = value
- 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,
- depending on the constraint code
-
- :param name: Fit parameter name identifying the row
- :param code: Constraint code, in `'FREE', 'POSITIVE', 'IGNORE',`
- `'FIXED', 'FACTOR', 'DELTA', 'SUM', 'ADD'`
- :return:
- """
- 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'])
-
- def getEstimationConstraints(self, param):
- """
- Return tuple ``(estimation, constraints)`` where ``estimation`` is the
- value in the ``estimate`` field and ``constraints`` are the relevant
- constraints according to the active code
- """
- estimation = None
- constraints = None
- if param in self.parameters.keys():
- 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']))
- else:
- code = str(self.parameters[param]['code'])
- cons1 = self.parameters[param]['cons1']
- cons2 = self.parameters[param]['cons2']
- constraints = [code, cons1, cons2]
- return estimation, constraints
-
-
-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.showGrid()
- 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
-
- x = numpy.arange(len(y)) * 0.0502883 - 0.492773
- fit = fitmanager.FitManager()
- fit.setdata(x=x, y=y, xmin=20, xmax=150)
-
- fit.loadtheories(fittheories)
-
- fit.settheory('ahypermet')
- fit.configure(Yscaling=1.,
- 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/silx/gui/fit/__init__.py b/silx/gui/fit/__init__.py
deleted file mode 100644
index e4fd3ab..0000000
--- a/silx/gui/fit/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-# Copyright (C) 2016 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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ############################################################################*/
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "07/07/2016"
-
-from .FitWidget import FitWidget
diff --git a/silx/gui/fit/setup.py b/silx/gui/fit/setup.py
deleted file mode 100644
index 6672363..0000000
--- a/silx/gui/fit/setup.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "21/07/2016"
-
-
-from numpy.distutils.misc_util import Configuration
-
-
-def configuration(parent_package='', top_path=None):
- config = Configuration('fit', parent_package, top_path)
- config.add_subpackage('test')
-
- return config
-
-
-if __name__ == "__main__":
- from numpy.distutils.core import setup
-
- setup(configuration=configuration)
diff --git a/silx/gui/fit/test/__init__.py b/silx/gui/fit/test/__init__.py
deleted file mode 100644
index 2236d64..0000000
--- a/silx/gui/fit/test/__init__.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-import unittest
-
-from .testFitWidget import suite as testFitWidgetSuite
-from .testFitConfig import suite as testFitConfigSuite
-from .testBackgroundWidget import suite as testBackgroundWidgetSuite
-
-
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "21/07/2016"
-
-
-def suite():
- test_suite = unittest.TestSuite()
- test_suite.addTests(
- [testFitWidgetSuite(),
- testFitConfigSuite(),
- testBackgroundWidgetSuite()])
- return test_suite
diff --git a/silx/gui/fit/test/testBackgroundWidget.py b/silx/gui/fit/test/testBackgroundWidget.py
deleted file mode 100644
index 03b17b9..0000000
--- a/silx/gui/fit/test/testBackgroundWidget.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-import unittest
-
-from silx.gui.utils.testutils import TestCaseQt
-
-from .. import BackgroundWidget
-
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "05/12/2016"
-
-
-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.qWaitForWindowExposed(self.bgdialog)
-
- def tearDown(self):
- del self.bgdialog
- super(TestBackgroundWidget, self).tearDown()
-
- def testShow(self):
- self.bgdialog.show()
- self.bgdialog.hide()
-
- def testAccept(self):
- self.bgdialog.accept()
- self.assertTrue(self.bgdialog.result())
-
- def testReject(self):
- self.bgdialog.reject()
- self.assertFalse(self.bgdialog.result())
-
- def testDefaultOutput(self):
- self.bgdialog.accept()
- output = self.bgdialog.output
-
- for key in ["algorithm", "StripThreshold", "SnipWidth",
- "StripIterations", "StripWidth", "SmoothingFlag",
- "SmoothingWidth", "AnchorsFlag", "AnchorsList"]:
- self.assertIn(key, output)
-
- self.assertFalse(output["AnchorsFlag"])
- self.assertEqual(output["StripWidth"], 1)
- self.assertEqual(output["SmoothingFlag"], False)
- self.assertEqual(output["SmoothingWidth"], 3)
-
-
-def suite():
- test_suite = unittest.TestSuite()
- test_suite.addTest(
- unittest.defaultTestLoader.loadTestsFromTestCase(TestBackgroundWidget))
- return test_suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')
diff --git a/silx/gui/fit/test/testFitConfig.py b/silx/gui/fit/test/testFitConfig.py
deleted file mode 100644
index f89c099..0000000
--- a/silx/gui/fit/test/testFitConfig.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""Basic tests for :class:`FitConfig`"""
-
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "05/12/2016"
-
-import unittest
-
-from silx.gui.utils.testutils import TestCaseQt
-from .. import FitConfig
-
-
-class TestFitConfig(TestCaseQt):
- """Basic test for FitWidget"""
-
- def setUp(self):
- super(TestFitConfig, self).setUp()
- self.fit_config = FitConfig.getFitConfigDialog(modal=False)
- self.qWaitForWindowExposed(self.fit_config)
-
- def tearDown(self):
- del self.fit_config
- super(TestFitConfig, self).tearDown()
-
- def testShow(self):
- self.fit_config.show()
- self.fit_config.hide()
-
- def testAccept(self):
- self.fit_config.accept()
- self.assertTrue(self.fit_config.result())
-
- def testReject(self):
- self.fit_config.reject()
- self.assertFalse(self.fit_config.result())
-
- def testDefaultOutput(self):
- 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"]:
- self.assertIn(key, output)
-
- self.assertTrue(output["AutoFwhm"])
- self.assertEqual(output["StripWidth"], 2)
-
-
-def suite():
- test_suite = unittest.TestSuite()
- test_suite.addTest(
- unittest.defaultTestLoader.loadTestsFromTestCase(TestFitConfig))
- return test_suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')
diff --git a/silx/gui/fit/test/testFitWidget.py b/silx/gui/fit/test/testFitWidget.py
deleted file mode 100644
index cfd2bc9..0000000
--- a/silx/gui/fit/test/testFitWidget.py
+++ /dev/null
@@ -1,135 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016 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
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-#
-# ###########################################################################*/
-"""Basic tests for :class:`FitWidget`"""
-
-import unittest
-
-from silx.gui.utils.testutils import TestCaseQt
-
-from ... import qt
-from .. import FitWidget
-
-from ....math.fit.fittheory import FitTheory
-from ....math.fit.fitmanager import FitManager
-
-__authors__ = ["P. Knobel"]
-__license__ = "MIT"
-__date__ = "05/12/2016"
-
-
-class TestFitWidget(TestCaseQt):
- """Basic test for FitWidget"""
-
- def setUp(self):
- super(TestFitWidget, self).setUp()
- self.fit_widget = FitWidget()
- self.fit_widget.show()
- self.qWaitForWindowExposed(self.fit_widget)
-
- def tearDown(self):
- self.fit_widget.setAttribute(qt.Qt.WA_DeleteOnClose)
- self.fit_widget.close()
- del self.fit_widget
- super(TestFitWidget, self).tearDown()
-
- def testShow(self):
- pass
-
- def testInteract(self):
- self.mouseClick(self.fit_widget, qt.Qt.LeftButton)
- self.keyClick(self.fit_widget, qt.Qt.Key_Enter)
- self.qapp.processEvents()
-
- def testCustomConfigWidget(self):
- class CustomConfigWidget(qt.QDialog):
- def __init__(self):
- qt.QDialog.__init__(self)
- self.setModal(True)
- self.ok = qt.QPushButton("ok", self)
- self.ok.clicked.connect(self.accept)
- cancel = qt.QPushButton("cancel", self)
- cancel.clicked.connect(self.reject)
- layout = qt.QVBoxLayout(self)
- layout.addWidget(self.ok)
- layout.addWidget(cancel)
- self.output = {"hello": "world"}
-
- def fitfun(x, a, b):
- return a * x + b
-
- x = list(range(0, 100))
- y = [fitfun(x_, 2, 3) for x_ in x]
-
- def conf(**kw):
- return {"spam": "eggs",
- "hello": "world!"}
-
- theory = FitTheory(
- function=fitfun,
- parameters=["a", "b"],
- configure=conf)
-
- fitmngr = FitManager()
- fitmngr.setdata(x, y)
- fitmngr.addtheory("foo", theory)
- fitmngr.addtheory("bar", theory)
- fitmngr.addbgtheory("spam", theory)
-
- fw = FitWidget(fitmngr=fitmngr)
- fw.associateConfigDialog("spam", CustomConfigWidget(),
- theory_is_background=True)
- fw.associateConfigDialog("foo", CustomConfigWidget())
- fw.show()
- self.qWaitForWindowExposed(fw)
-
- fw.bgconfigdialogs["spam"].accept()
- self.assertTrue(fw.bgconfigdialogs["spam"].result())
-
- self.assertEqual(fw.bgconfigdialogs["spam"].output,
- {"hello": "world"})
-
- fw.bgconfigdialogs["spam"].reject()
- self.assertFalse(fw.bgconfigdialogs["spam"].result())
-
- fw.configdialogs["foo"].accept()
- self.assertTrue(fw.configdialogs["foo"].result())
-
- # todo: figure out how to click fw.configdialog.ok to close dialog
- # open dialog
- # self.mouseClick(fw.guiConfig.FunConfigureButton, qt.Qt.LeftButton)
- # clove dialog
- # self.mouseClick(fw.configdialogs["foo"].ok, qt.Qt.LeftButton)
- # self.qapp.processEvents()
-
-
-def suite():
- test_suite = unittest.TestSuite()
- test_suite.addTest(
- unittest.defaultTestLoader.loadTestsFromTestCase(TestFitWidget))
- return test_suite
-
-
-if __name__ == '__main__':
- unittest.main(defaultTest='suite')