source: sasview/src/sas/qtgui/Plotting/LinearFit.py @ 033b1f2

Last change on this file since 033b1f2 was 33c0561, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Replace Apply button menu driven functionality with additional button.
Removed Cancel.
Removed the window system context help button from all affected widgets.
SASVIEW-1239

  • Property mode set to 100644
File size: 8.7 KB
Line 
1"""
2Adds a linear fit plot to the chart
3"""
4import re
5import numpy
6from PyQt5 import QtCore
7from PyQt5 import QtGui
8from PyQt5 import QtWidgets
9
10from sas.qtgui.Utilities.GuiUtils import formatNumber, DoubleValidator
11
12from sas.qtgui.Plotting import Fittings
13from sas.qtgui.Plotting import DataTransform
14from sas.qtgui.Plotting.LineModel import LineModel
15import sas.qtgui.Utilities.GuiUtils as GuiUtils
16
17# Local UI
18from sas.qtgui.UI import main_resources_rc
19from sas.qtgui.Plotting.UI.LinearFitUI import Ui_LinearFitUI
20
21class LinearFit(QtWidgets.QDialog, Ui_LinearFitUI):
22    updatePlot = QtCore.pyqtSignal(tuple)
23    def __init__(self, parent=None,
24                 data=None,
25                 max_range=(0.0, 0.0),
26                 fit_range=(0.0, 0.0),
27                 xlabel="",
28                 ylabel=""):
29        super(LinearFit, self).__init__()
30
31        self.setupUi(self)
32        # disable the context help icon
33        self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
34
35        assert(isinstance(max_range, tuple))
36        assert(isinstance(fit_range, tuple))
37
38        self.data = data
39        self.parent = parent
40
41        self.max_range = max_range
42        self.fit_range = fit_range
43        self.xLabel = xlabel
44        self.yLabel = ylabel
45
46        self.x_is_log = self.xLabel == "log10(x)"
47        self.y_is_log = self.yLabel == "log10(y)"
48
49        self.txtFitRangeMin.setValidator(DoubleValidator())
50        self.txtFitRangeMax.setValidator(DoubleValidator())
51
52        # Default values in the line edits
53        self.txtA.setText("1")
54        self.txtB.setText("1")
55        self.txtAerr.setText("0")
56        self.txtBerr.setText("0")
57        self.txtChi2.setText("0")
58
59        # Initial ranges
60        self.txtRangeMin.setText(str(max_range[0]))
61        self.txtRangeMax.setText(str(max_range[1]))
62        # Assure nice display of ranges
63        fr_min = GuiUtils.formatNumber(fit_range[0])
64        fr_max = GuiUtils.formatNumber(fit_range[1])
65        self.txtFitRangeMin.setText(str(fr_min))
66        self.txtFitRangeMax.setText(str(fr_max))
67
68        # cast xLabel into html
69        label = re.sub(r'\^\((.)\)(.*)', r'<span style=" vertical-align:super;">\1</span>\2',
70                      str(self.xLabel).rstrip())
71        self.lblRange.setText('Fit range of ' + label)
72
73        self.model = LineModel()
74        # Display the fittings values
75        self.default_A = self.model.getParam('A')
76        self.default_B = self.model.getParam('B')
77        self.cstA = Fittings.Parameter(self.model, 'A', self.default_A)
78        self.cstB = Fittings.Parameter(self.model, 'B', self.default_B)
79        self.transform = DataTransform
80
81        self.setFixedSize(self.minimumSizeHint())
82
83        # connect Fit button
84        self.cmdFit.clicked.connect(self.fit)
85
86    def setRangeLabel(self, label=""):
87        """
88        Overwrite default fit range label to correspond to actual unit
89        """
90        assert(isinstance(label, str))
91        self.lblRange.setText(label)
92
93    def range(self):
94        return (float(self.txtFitRangeMin.text()), float(self.txtFitRangeMax.text()))
95
96    def fit(self, event):
97        """
98        Performs the fit. Receive an event when clicking on
99        the button Fit.Computes chisqr ,
100        A and B parameters of the best linear fit y=Ax +B
101        Push a plottable to the caller
102        """
103        tempx = []
104        tempy = []
105        tempdy = []
106
107        # Checks to assure data correctness
108        if len(self.data.view.x) < 2:
109            return
110        if not self.checkFitValues(self.txtFitRangeMin):
111            return
112
113        self.xminFit, self.xmaxFit = self.range()
114
115        xmin = self.xminFit
116        xmax = self.xmaxFit
117        xminView = xmin
118        xmaxView = xmax
119
120        # Set the qmin and qmax in the panel that matches the
121        # transformed min and max
122        value_xmin = self.floatInvTransform(xmin)
123        value_xmax = self.floatInvTransform(xmax)
124        self.txtRangeMin.setText(formatNumber(value_xmin))
125        self.txtRangeMax.setText(formatNumber(value_xmax))
126
127        tempx, tempy, tempdy = self.origData()
128
129        # Find the fitting parameters
130        self.cstA = Fittings.Parameter(self.model, 'A', self.default_A)
131        self.cstB = Fittings.Parameter(self.model, 'B', self.default_B)
132        tempdy = numpy.asarray(tempdy)
133        tempdy[tempdy == 0] = 1
134
135        if self.x_is_log:
136            xmin = numpy.log10(xmin)
137            xmax = numpy.log10(xmax)
138
139        chisqr, out, cov = Fittings.sasfit(self.model,
140                                           [self.cstA, self.cstB],
141                                           tempx, tempy, tempdy,
142                                           xmin, xmax)
143        # Use chi2/dof
144        if len(tempx) > 0:
145            chisqr = chisqr / len(tempx)
146
147        # Check that cov and out are iterable before displaying them
148        errA = numpy.sqrt(cov[0][0]) if cov is not None else 0
149        errB = numpy.sqrt(cov[1][1]) if cov is not None else 0
150        cstA = out[0] if out is not None else 0.0
151        cstB = out[1] if out is not None else 0.0
152
153        # Reset model with the right values of A and B
154        self.model.setParam('A', float(cstA))
155        self.model.setParam('B', float(cstB))
156
157        tempx = []
158        tempy = []
159        y_model = 0.0
160
161        # load tempy with the minimum transformation
162        y_model = self.model.run(xmin)
163        tempx.append(xminView)
164        tempy.append(numpy.power(10.0, y_model) if self.y_is_log else y_model)
165
166        # load tempy with the maximum transformation
167        y_model = self.model.run(xmax)
168        tempx.append(xmaxView)
169        tempy.append(numpy.power(10.0, y_model) if self.y_is_log else y_model)
170
171        # Set the fit parameter display when  FitDialog is opened again
172        self.Avalue = cstA
173        self.Bvalue = cstB
174        self.ErrAvalue = errA
175        self.ErrBvalue = errB
176        self.Chivalue = chisqr
177
178        # Update the widget
179        self.txtA.setText(formatNumber(self.Avalue))
180        self.txtAerr.setText(formatNumber(self.ErrAvalue))
181        self.txtB.setText(formatNumber(self.Bvalue))
182        self.txtBerr.setText(formatNumber(self.ErrBvalue))
183        self.txtChi2.setText(formatNumber(self.Chivalue))
184
185        self.updatePlot.emit((tempx, tempy))
186
187    def origData(self):
188        # Store the transformed values of view x, y and dy before the fit
189        xmin_check = numpy.log10(self.xminFit)
190        # Local shortcuts
191        x = self.data.view.x
192        y = self.data.view.y
193        dy = self.data.view.dy
194
195        if self.y_is_log:
196            if self.x_is_log:
197                tempy  = [numpy.log10(y[i])
198                         for i in range(len(x)) if x[i] >= xmin_check]
199                tempdy = [DataTransform.errToLogX(y[i], 0, dy[i], 0)
200                         for i in range(len(x)) if x[i] >= xmin_check]
201            else:
202                tempy = list(map(numpy.log10, y))
203                tempdy = list(map(lambda t1,t2:DataTransform.errToLogX(t1,0,t2,0),y,dy))
204        else:
205            tempy = y
206            tempdy = dy
207
208        if self.x_is_log:
209            tempx = [numpy.log10(x) for x in self.data.view.x if x > xmin_check]
210        else:
211            tempx = x
212
213        return tempx, tempy, tempdy
214
215    def checkFitValues(self, item):
216        """
217        Check the validity of input values
218        """
219        flag = True
220        value = item.text()
221        p_white = item.palette()
222        p_white.setColor(item.backgroundRole(), QtCore.Qt.white)
223        p_pink = item.palette()
224        p_pink.setColor(item.backgroundRole(), QtGui.QColor(255, 128, 128))
225        item.setAutoFillBackground(True)
226        # Check for possible values entered
227        if self.x_is_log:
228            if float(value) > 0:
229                item.setPalette(p_white)
230            else:
231                flag = False
232                item.setPalette(p_pink)
233        return flag
234
235    def floatInvTransform(self, x):
236        """
237        transform a float.It is used to determine the x.View min and x.View
238        max for values not in x.  Also used to properly calculate RgQmin,
239        RgQmax and to update qmin and qmax in the linear range boxes on the
240        panel.
241
242        """
243        # TODO: refactor this. This is just a hack to make the
244        # functionality work without rewritting the whole code
245        # with good design (which really should be done...).
246        if self.xLabel == "x":
247            return x
248        elif self.xLabel == "x^(2)":
249            return numpy.sqrt(x)
250        elif self.xLabel == "x^(4)":
251            return numpy.sqrt(numpy.sqrt(x))
252        elif self.xLabel == "log10(x)":
253            return numpy.power(10.0, x)
254        elif self.xLabel == "ln(x)":
255            return numpy.exp(x)
256        elif self.xLabel == "log10(x^(4))":
257            return numpy.sqrt(numpy.sqrt(numpy.power(10.0, x)))
258        return x
259
260
Note: See TracBrowser for help on using the repository browser.