summaryrefslogtreecommitdiff
path: root/silx/gui/plot/actions/fit.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot/actions/fit.py')
-rw-r--r--silx/gui/plot/actions/fit.py189
1 files changed, 189 insertions, 0 deletions
diff --git a/silx/gui/plot/actions/fit.py b/silx/gui/plot/actions/fit.py
new file mode 100644
index 0000000..d7256ab
--- /dev/null
+++ b/silx/gui/plot/actions/fit.py
@@ -0,0 +1,189 @@
+# 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.
+#
+# ###########################################################################*/
+"""
+:mod:`silx.gui.plot.actions.fit` module provides actions relative to fit.
+
+The following QAction are available:
+
+- :class:`.FitAction`
+
+.. autoclass:`.FitAction`
+"""
+
+from __future__ import division
+
+__authors__ = ["V.A. Sole", "T. Vincent", "P. Knobel"]
+__license__ = "MIT"
+__date__ = "28/06/2017"
+
+from . import PlotAction
+import logging
+from silx.gui import qt
+from silx.gui.plot.ItemsSelectionDialog import ItemsSelectionDialog
+from silx.gui.plot.items import Curve, Histogram
+
+_logger = logging.getLogger(__name__)
+
+
+def _getUniqueCurve(plt):
+ """Get a single curve from the plot.
+ Get the active curve if any, else if a single curve is plotted
+ get it, else return None.
+
+ :param plt: :class:`.PlotWidget` instance on which to operate
+
+ :return: return value of plt.getActiveCurve(), or plt.getAllCurves()[0],
+ or None
+ """
+ curve = plt.getActiveCurve()
+ if curve is not None:
+ return curve
+
+ curves = plt.getAllCurves()
+ if len(curves) == 0:
+ return None
+
+ if len(curves) == 1 and len(plt._getItems(kind='histogram')) == 0:
+ return curves[0]
+
+ return None
+
+
+def _getUniqueHistogram(plt):
+ """Return the histogram if there is a single histogram and no curve in
+ the plot. In all other cases, return None.
+
+ :param plt: :class:`.PlotWidget` instance on which to operate
+ :return: histogram or None
+ """
+ histograms = plt._getItems(kind='histogram')
+ if len(histograms) != 1:
+ return None
+ if plt.getAllCurves(just_legend=True):
+ return None
+ return histograms[0]
+
+
+class FitAction(PlotAction):
+ """QAction to open a :class:`FitWidget` and set its data to the
+ active curve if any, or to the first curve.
+
+ :param plot: :class:`.PlotWidget` instance on which to operate
+ :param parent: See :class:`QAction`
+ """
+ def __init__(self, plot, parent=None):
+ super(FitAction, self).__init__(
+ plot, icon='math-fit', text='Fit curve',
+ tooltip='Open a fit dialog',
+ triggered=self._getFitWindow,
+ checkable=False, parent=parent)
+ self.fit_window = None
+
+ def _getFitWindow(self):
+ self.xlabel = self.plot.getXAxis().getLabel()
+ self.ylabel = self.plot.getYAxis().getLabel()
+ self.xmin, self.xmax = self.plot.getXAxis().getLimits()
+
+ histo = _getUniqueHistogram(self.plot)
+ curve = _getUniqueCurve(self.plot)
+
+ if histo is None and curve is None:
+ # ambiguous case, we need to ask which plot item to fit
+ isd = ItemsSelectionDialog(plot=self.plot)
+ isd.setWindowTitle("Select item to be fitted")
+ isd.setItemsSelectionMode(qt.QTableWidget.SingleSelection)
+ isd.setAvailableKinds(["curve", "histogram"])
+ isd.selectAllKinds()
+
+ result = isd.exec_()
+ if result and len(isd.getSelectedItems()) == 1:
+ item = isd.getSelectedItems()[0]
+ else:
+ return
+ elif histo is not None:
+ # presence of a unique histo and no curve
+ item = histo
+ elif curve is not None:
+ # presence of a unique or active curve
+ item = curve
+
+ self.legend = item.getLegend()
+
+ if isinstance(item, Histogram):
+ bin_edges = item.getBinEdgesData(copy=False)
+ # take the middle coordinate between adjacent bin edges
+ self.x = (bin_edges[1:] + bin_edges[:-1]) / 2
+ self.y = item.getValueData(copy=False)
+ # else take the active curve, or else the unique curve
+ elif isinstance(item, Curve):
+ self.x = item.getXData(copy=False)
+ self.y = item.getYData(copy=False)
+
+ # open a window with a FitWidget
+ if self.fit_window is None:
+ self.fit_window = qt.QMainWindow()
+ # import done here rather than at module level to avoid circular import
+ # FitWidget -> BackgroundWidget -> PlotWindow -> actions -> fit -> FitWidget
+ from ...fit.FitWidget import FitWidget
+ self.fit_widget = FitWidget(parent=self.fit_window)
+ self.fit_window.setCentralWidget(
+ self.fit_widget)
+ self.fit_widget.guibuttons.DismissButton.clicked.connect(
+ self.fit_window.close)
+ self.fit_widget.sigFitWidgetSignal.connect(
+ self.handle_signal)
+ self.fit_window.show()
+ else:
+ if self.fit_window.isHidden():
+ self.fit_window.show()
+ self.fit_widget.show()
+ self.fit_window.raise_()
+
+ self.fit_widget.setData(self.x, self.y,
+ xmin=self.xmin, xmax=self.xmax)
+ self.fit_window.setWindowTitle(
+ "Fitting " + self.legend +
+ " on x range %f-%f" % (self.xmin, self.xmax))
+
+ def handle_signal(self, ddict):
+ x_fit = self.x[self.xmin <= self.x]
+ x_fit = x_fit[x_fit <= self.xmax]
+ fit_legend = "Fit <%s>" % self.legend
+ fit_curve = self.plot.getCurve(fit_legend)
+
+ if ddict["event"] == "FitFinished":
+ y_fit = self.fit_widget.fitmanager.gendata()
+ if fit_curve is None:
+ self.plot.addCurve(x_fit, y_fit,
+ fit_legend,
+ xlabel=self.xlabel, ylabel=self.ylabel,
+ resetzoom=False)
+ else:
+ fit_curve.setData(x_fit, y_fit)
+ fit_curve.setVisible(True)
+
+ if ddict["event"] in ["FitStarted", "FitFailed"]:
+ if fit_curve is not None:
+ fit_curve.setVisible(False)