diff options
Diffstat (limited to 'silx/gui/fit/BackgroundWidget.py')
-rw-r--r-- | silx/gui/fit/BackgroundWidget.py | 534 |
1 files changed, 0 insertions, 534 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() |