text: # coding: utf-8
  #/*##########################################################################
  # Copyright (C) 2004-2017 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`."""
  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__ = "24/01/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 :class:`PlotWindow`.
  
      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.float)
          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.setGraphXLimits(xmin=self._xmin, xmax=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.setParameters`"""
          return self.parametersWidget.setParameters(ddict)
  
      def setDefault(self, ddict):
          """Alias for :meth:`setParameters`"""
          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()
  