source: sasview/plottools/src/danse/common/plottools/fitDialog.py @ 10bfeb3

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 10bfeb3 was 10bfeb3, checked in by Mathieu Doucet <doucetm@…>, 12 years ago

Pep-8-ification

  • Property mode set to 100644
File size: 21.3 KB
Line 
1import wx
2from plottables import Theory1D
3import math
4import numpy
5import fittings
6import transform
7import sys
8
9#Linear fit panel size
10if sys.platform.count("win32") > 0:
11    FONT_VARIANT = 0
12    PNL_WIDTH = 400
13else:
14    FONT_VARIANT = 1
15    PNL_WIDTH = 450
16   
17   
18def format_number(value, high=False):
19    """
20    Return a float in a standardized, human-readable formatted string
21    """
22    try:
23        value = float(value)
24    except:
25        output = "NaN"
26        return output.lstrip().rstrip()
27   
28    if high:
29        output = "%-6.4g" % value
30       
31    else:
32        output = "%-5.3g" % value
33    return output.lstrip().rstrip()
34
35
36class LinearFit(wx.Dialog):
37    def __init__(self, parent, plottable, push_data, transform, title):
38        """
39        Dialog window pops- up when select Linear fit on Context menu
40        Displays fitting parameters
41        """
42        wx.Dialog.__init__(self, parent, title=title, size=(PNL_WIDTH, 350))
43        self.parent = parent
44        self.transform = transform
45        #Font
46        self.SetWindowVariant(variant=FONT_VARIANT)
47        # Registered owner for close event
48        self._registered_close = None
49       
50        #dialog panel self call function to plot the fitting function
51        self.push_data = push_data
52        #dialog self plottable
53        self.plottable = plottable
54       
55        # Receive transformations of x and y
56        self.xLabel, self.yLabel, self.Avalue, self.Bvalue,\
57               self.ErrAvalue, self.ErrBvalue, self.Chivalue = self.transform()
58       
59        #Dialog interface
60        vbox  = wx.BoxSizer(wx.VERTICAL)
61        sizer = wx.GridBagSizer(5, 5)
62        _BOX_WIDTH = 100
63 
64        self.tcA      = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
65        self.tcA.SetToolTipString("Fit value for the slope parameter.")
66        self.tcErrA   = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
67        self.tcErrA.SetToolTipString("Error on the slope parameter.")
68        self.tcB      = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
69        self.tcA.SetToolTipString("Fit value for the constant parameter.")
70        self.tcErrB   = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
71        self.tcErrB.SetToolTipString("Error on the constant parameter.")
72        self.tcChi    = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
73        self.tcChi.SetToolTipString("Chi^2 over degrees of freedom.")
74        self.xminFit  = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
75        msg = "Enter the minimum value on "
76        msg += "the x-axis to be included in the fit."
77        self.xminFit.SetToolTipString(msg)
78        self.xmaxFit  = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
79        msg = "Enter the maximum value on "
80        msg += " the x-axis to be included in the fit."
81        self.xmaxFit.SetToolTipString(msg)
82        self.initXmin = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
83        msg = "Minimum value on the x-axis for the plotted data."
84        self.initXmin.SetToolTipString(msg)
85        self.initXmax = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20))
86        msg = "Maximum value on the x-axis for the plotted data."
87        self.initXmax.SetToolTipString(msg)
88
89        # Make the info box not editable
90        #_BACKGROUND_COLOR = '#ffdf85'
91        _BACKGROUND_COLOR = self.GetBackgroundColour()
92        self.initXmin.SetEditable(False)
93        self.initXmin.SetBackgroundColour(_BACKGROUND_COLOR)
94        self.initXmax.SetEditable(False)
95        self.initXmax.SetBackgroundColour(_BACKGROUND_COLOR)
96       
97        # Buttons on the bottom
98        self.static_line_1 = wx.StaticLine(self, -1)
99        self.btFit = wx.Button(self, -1, 'Fit')
100        self.btFit.Bind(wx.EVT_BUTTON, self._onFit)
101        self.btFit.SetToolTipString("Perform fit.")
102        self.btClose =wx.Button(self, wx.ID_CANCEL, 'Close')
103        self.btClose.Bind(wx.EVT_BUTTON, self._on_close)
104       
105        # Intro
106        explanation = "Perform fit for y(x) = Ax + B"
107        vbox.Add(sizer)
108        ix = 0
109        iy = 1
110        sizer.Add(wx.StaticText(self, -1, explanation), (iy, ix),
111                 (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
112        iy += 2
113        sizer.Add(wx.StaticText(self, -1, 'Parameter A'), (iy, ix),
114                 (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
115        ix += 1
116        sizer.Add(self.tcA,(iy, ix),(1, 1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
117        ix += 1
118        sizer.Add(wx.StaticText(self, -1, '+/-'),
119                  (iy, ix), (1, 1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
120        ix += 1
121        sizer.Add(self.tcErrA, (iy, ix), (1, 1),
122                  wx.EXPAND|wx.ADJUST_MINSIZE, 0)
123        iy += 1
124        ix = 0
125        sizer.Add(wx.StaticText(self, -1, 'Parameter B'), (iy, ix),(1, 1),
126                   wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
127        ix += 1
128        sizer.Add(self.tcB, (iy, ix),(1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
129        ix += 1
130        sizer.Add(wx.StaticText(self, -1, '+/-'), (iy, ix),
131                  (1, 1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
132        ix += 1
133        sizer.Add(self.tcErrB, (iy, ix), (1, 1),
134                   wx.EXPAND|wx.ADJUST_MINSIZE, 0)
135        iy += 1
136        ix = 0
137        sizer.Add(wx.StaticText(self, -1, 'Chi2/dof'), (iy, ix), (1, 1),
138                   wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
139        ix += 1
140        sizer.Add(self.tcChi, (iy, ix),(1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
141        iy += 2
142        ix = 1
143        sizer.Add(wx.StaticText(self, -1, 'Min'), (iy, ix), (1, 1),
144                   wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 0)
145        ix += 2
146        sizer.Add(wx.StaticText(self, -1, 'Max'), (iy, ix),
147                  (1, 1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
148        iy += 1
149        ix = 0
150        sizer.Add(wx.StaticText(self, -1, 'Maximum range (linear scale)'),
151                  (iy, ix),(1,1),
152                   wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
153        ix += 1
154        sizer.Add(self.initXmin, (iy, ix), (1,1),
155                   wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 0)
156        ix += 2
157        sizer.Add(self.initXmax, (iy, ix), (1,1), 
158                  wx.EXPAND|wx.ADJUST_MINSIZE, 0)
159       
160        iy += 1
161        ix = 0
162        sizer.Add(wx.StaticText(self, -1, 'Fit range of ' + self.xLabel),
163                  (iy, ix), (1, 1),
164                   wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
165        ix += 1
166        sizer.Add(self.xminFit, (iy, ix), (1, 1),
167                   wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 0)
168        ix += 2
169        sizer.Add(self.xmaxFit, (iy, ix), (1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
170        iy += 1
171        ix = 1
172       
173        vbox.Add(self.static_line_1, 0, wx.EXPAND, 0)
174        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
175        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
176        sizer_button.Add(self.btFit, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
177        sizer_button.Add(self.btClose, 0,
178                          wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
179        vbox.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
180       
181        sizer.Add(self.btFit, (iy, ix), (1,1), wx.LEFT|wx.ADJUST_MINSIZE, 0)
182        #panel.SetSizer(sizer)
183        self.SetSizer(vbox)
184        self.Centre()
185        # Receives the type of model for the fitting
186        from LineModel import LineModel
187        self.model = LineModel()
188        #Display the fittings values
189        self.default_A = self.model.getParam('A')
190        self.default_B = self.model.getParam('B')
191        self.cstA = fittings.Parameter(self.model, 'A', self.default_A)
192        self.cstB = fittings.Parameter(self.model, 'B', self.default_B)
193       
194        # Set default value of parameter in fit dialog
195        if self.Avalue == None:
196            self.tcA.SetValue(format_number(self.default_A))
197        else:
198            self.tcA.SetLabel(format_number(self.Avalue))
199        if self.Bvalue == None:
200            self.tcB.SetValue(format_number(self.default_B))
201        else:
202            self.tcB.SetLabel(format_number(self.Bvalue))
203        if self.ErrAvalue == None:
204            self.tcErrA.SetLabel(format_number(0.0))
205        else:
206            self.tcErrA.SetLabel(format_number(self.ErrAvalue))
207        if self.ErrBvalue == None:
208            self.tcErrB.SetLabel(format_number(0.0))
209        else:
210            self.tcErrB.SetLabel(format_number(self.ErrBvalue))
211        if self.Chivalue == None:
212            self.tcChi.SetLabel(format_number(0.0))
213        else:
214            self.tcChi.SetLabel(format_number(self.Chivalue))
215        if self.plottable.x != []:
216            #store the values of View in self.x,self.y,self.dx,self.dy
217            self.x, self.y, self.dx, \
218                     self.dy = self.plottable.returnValuesOfView()
219            try:
220                self.mini = self.floatForwardTransform(min(self.x))
221            except:
222                self.mini = "Invalid"
223            try:
224                self.maxi = self.floatForwardTransform(max(self.x))
225            except:
226                self.maxi = "Invalid"
227
228            self.initXmin.SetValue(format_number(min(self.plottable.x)))
229            self.initXmax.SetValue(format_number(max(self.plottable.x)))
230            self.mini = min(self.x)
231            self.maxi = max(self.x)
232            self.xminFit.SetValue(format_number(self.mini))
233            self.xmaxFit.SetValue(format_number(self.maxi))
234   
235    def register_close(self, owner):
236        """
237        Method to register the close event to a parent
238        window that needs notification when the dialog
239        is closed
240       
241        :param owner: parent window
242       
243        """
244        self._registered_close = owner
245       
246    def _on_close(self, event):
247        """
248        Close event.
249        Notify registered owner if available.
250        """
251        event.Skip()
252        if self._registered_close is not None:
253            self._registered_close()
254       
255    def _onFit(self, event):
256        """
257        Performs the fit. Receive an event when clicking on
258        the button Fit.Computes chisqr ,
259        A and B parameters of the best linear fit y=Ax +B
260        Push a plottable to
261        """
262        tempx = []
263        tempy = []
264        tempdy = []
265       
266        # Check if View contains a x array .we online fit when x exits
267        # makes transformation for y as a line to fit
268        if self.x != []:
269            if(self.checkFitValues(self.xminFit) == True):
270                #Check if the field of Fit Dialog contain values
271                # and use the x max and min of the user
272                #xminView,xmaxView = self._checkVal(self.xminFit.GetValue(),
273                #self.xmaxFit.GetValue())
274                if not self._checkVal(self.xminFit, self.xmaxFit):
275                    return
276                xminView = float(self.xminFit.GetValue())
277                xmaxView = float(self.xmaxFit.GetValue())
278                #xmin = self.floatInvTransform(xminView)
279                #xmax = self.floatInvTransform(xmaxView)
280                xmin = xminView
281                xmax = xmaxView
282                # Store the transformed values of view x, y,dy
283                # in variables  before the fit
284                if self.yLabel.lower() == "log10(y)":
285                    if (self.xLabel.lower() == "log10(x)"):
286                        for i in range(len(self.x)):
287                            if self.x[i] >= math.log10(xmin):
288                                tempy.append(math.log10(self.y[i]))
289                                tempdy.append(transform.errToLogX(self.y[i],
290                                                        0, self.dy[i], 0))
291                    else:
292                        for i in range(len(self.y)):
293                            tempy.append(math.log10(self.y[i]))
294                            tempdy.append(transform.errToLogX(self.y[i],
295                                                            0, self.dy[i], 0))
296                else:
297                    tempy = self.y
298                    tempdy = self.dy
299               
300                if (self.xLabel.lower() == "log10(x)"):
301                    for x_i in self.x:
302                        if x_i >= math.log10(xmin):
303                            tempx.append(math.log10(x_i))
304                else:
305                    tempx = self.x
306             
307                #Find the fitting parameters
308                # Always use the same defaults, so that fit history
309                #doesn't play a role!
310                self.cstA = fittings.Parameter(self.model, 'A', self.default_A)
311                self.cstB = fittings.Parameter(self.model, 'B', self.default_B)
312               
313                if (self.xLabel.lower() == "log10(x)"):
314                    tempdy = numpy.asarray(tempdy)
315                    tempdy[tempdy == 0] = 1
316                    chisqr, out, cov = fittings.sansfit(self.model,
317                                                        [self.cstA, self.cstB],
318                                                        tempx, tempy,
319                                                        tempdy,
320                                                        math.log10(xmin),
321                                                        math.log10(xmax))
322                else:
323                    tempdy = numpy.asarray(tempdy)
324                    tempdy[tempdy == 0] = 1
325                    chisqr, out, cov = fittings.sansfit(self.model,
326                                                        [self.cstA, self.cstB],
327                                                        tempx, tempy, tempdy,
328                                                        xminView, xmaxView)
329                # Use chi2/dof
330                if len(tempx) > 0:
331                    chisqr = chisqr/len(tempx)
332               
333                #Check that cov and out are iterable before displaying them
334                if cov == None:
335                    errA = 0.0
336                    errB = 0.0
337                else:
338                    errA = math.sqrt(cov[0][0])
339                    errB = math.sqrt(cov[1][1])
340                if out == None:
341                    cstA = 0.0
342                    cstB = 0.0
343                else:
344                    cstA = out[0]
345                    cstB = out[1]
346                # Reset model with the right values of A and B
347                self.model.setParam('A', float(cstA))
348                self.model.setParam('B', float(cstB))
349               
350                tempx = []
351                tempy = []
352                y_model = 0.0
353                # load tempy with the minimum transformation
354               
355                if self.xLabel == "log10(x)":
356                    y_model = self.model.run(math.log10(xmin))
357                    tempx.append(xmin)
358                else:
359                    y_model = self.model.run(xminView)
360                    tempx.append(xminView)
361                   
362                if self.yLabel == "log10(y)":
363                    tempy.append(math.pow(10, y_model))
364                else:
365                    tempy.append(y_model)
366                   
367                # load tempy with the maximum transformation
368                if self.xLabel == "log10(x)":
369                    y_model = self.model.run(math.log10(xmax))
370                    tempx.append(xmax)
371                else:
372                    y_model = self.model.run(xmaxView)
373                    tempx.append(xmaxView)
374                   
375                if self.yLabel == "log10(y)":
376                    tempy.append(math.pow(10, y_model))
377                else:
378                    tempy.append(y_model)
379                #Set the fit parameter display when  FitDialog is opened again
380                self.Avalue = cstB
381                self.Bvalue = cstA
382                self.ErrAvalue = errA
383                self.ErrBvalue = errB
384                self.Chivalue = chisqr
385                self.push_data(tempx, tempy, xminView, xmaxView,
386                               xmin, xmax, self._ongetValues())
387               
388                # Display the fitting value on the Fit Dialog
389                self._onsetValues(cstB, cstA, errA, errB, chisqr)
390               
391    def _onsetValues(self, cstA, cstB, errA, errB, Chi):
392        """
393        Display  the value on fit Dialog
394        """
395        self.tcA.SetValue(format_number(cstA))
396        self.tcB.SetValue(format_number(cstB))
397        self.tcErrA.SetValue(format_number(errA))
398        self.tcErrB.SetValue(format_number(errB))
399        self.tcChi.SetValue(format_number(Chi))
400       
401    def _ongetValues(self):
402        """
403        Display  the value on fit Dialog
404        """
405        return self.Avalue, self.Bvalue, self.ErrAvalue, \
406                            self.ErrBvalue, self.Chivalue
407   
408    def _checkVal(self, usermin, usermax):
409        """
410        Ensure that fields parameter contains a min and a max value
411        within x min and x max range
412        """
413        self.mini = float(self.xminFit.GetValue())
414        self.maxi = float(self.xmaxFit.GetValue())
415        flag = True
416        try:
417            mini = float(usermin.GetValue())
418            maxi = float(usermax.GetValue())
419            if mini < maxi:
420                usermin.SetBackgroundColour(wx.WHITE)
421                usermin.Refresh()
422            else:
423                flag = False
424                usermin.SetBackgroundColour("pink")
425                usermin.Refresh()
426        except:
427            # Check for possible values entered
428            flag = False
429            usermin.SetBackgroundColour("pink")
430            usermin.Refresh()
431     
432        return flag
433           
434    def floatForwardTransform(self, x):
435        """
436        transform a float.
437        """
438        #TODO: refactor this with proper object-oriented design
439        # This code stinks.
440        if(self.xLabel == "x"):
441            return transform.toX(x)
442        if(self.xLabel == "x^(2)"):
443            return transform.toX2(x)
444        if(self.xLabel == "ln(x)"):
445            return transform.toLogX(x)
446        if(self.xLabel == "log10(x)"):
447            return math.log10(x)
448                   
449    def floatTransform(self, x):
450        """
451        transform a float.It is use to determine the x.
452        View min and x.View max for values
453        not in x
454        """
455        #TODO: refactor this with proper object-oriented design
456        # This code stinks.
457        if(self.xLabel == "x"):
458            return transform.toX(x)
459        if(self.xLabel == "x^(2)"):
460            return transform.toX2(x)
461        if(self.xLabel == "ln(x)"):
462            return transform.toLogX(x)
463        if(self.xLabel == "log10(x)"):
464            if x > 0:
465                return x
466            else:
467                raise ValueError, "cannot compute log of a negative number"
468           
469    def floatInvTransform(self, x):
470        """
471        transform a float.It is use to determine the x.View min and x.View
472        max for values not in x
473       
474        """
475        #TODO: refactor this. This is just a hack to make the
476        # functionality work without rewritting the whole code
477        # with good design (which really should be done...).
478        if(self.xLabel == "x^(2)"):
479            return math.sqrt(x)
480       
481        elif(self.xLabel == "log10(x)"):
482            return math.pow(10, x)
483       
484        elif(self.xLabel == "ln(x)"):
485            return math.exp(x)
486        return x
487           
488    def checkFitValues(self, item):
489        """
490            Check the validity of input values
491        """
492        flag = True
493        value = item.GetValue()
494        # Check for possible values entered
495        if (self.xLabel == "log10(x)"):  #or self.xLabel=="ln(x)"):
496            if(float(value) > 0):
497                item.SetBackgroundColour(wx.WHITE)
498                item.Refresh()
499            else:
500                flag = False
501                item.SetBackgroundColour("pink")
502                item.Refresh()
503        return flag
504       
505    def setFitRange(self, xmin, xmax, xminTrans, xmaxTrans):
506        """
507        Set fit parameters
508        """
509        self.xminFit.SetValue(format_number(xmin))
510        self.xmaxFit.SetValue(format_number(xmax))
511       
512    def set_fit_region(self, xmin, xmax):
513        """
514        Set the fit region
515        :param xmin: minimum x-value to be included in fit
516        :param xmax: maximum x-value to be included in fit
517        """
518        # Check values
519        try:
520            float(xmin)
521            float(xmax)
522        except:
523            msg = "LinearFit.set_fit_region: fit range must be floats"
524            raise ValueError, msg
525        self.xminFit.SetValue(format_number(xmin))
526        self.xmaxFit.SetValue(format_number(xmax))
527 
528 
529class MyApp(wx.App):
530    """
531    """
532    def OnInit(self):
533        """
534        """
535        wx.InitAllImageHandlers()
536        plot = Theory1D([], [])
537        dialog = LinearFit(parent=None, plottable=plot,
538                           push_data=self.onFitDisplay,
539                           transform=self.returnTrans,
540                            title='Linear Fit')
541        if dialog.ShowModal() == wx.ID_OK:
542            pass
543        dialog.Destroy()
544        return 1
545   
546    def onFitDisplay(self, tempx, tempy, xminView, xmaxView, xmin, xmax, func):
547        """
548        """
549        pass
550       
551    def returnTrans(self):
552        """
553        """
554        return '', '', 0, 0, 0, 0, 0
Note: See TracBrowser for help on using the repository browser.