source: sasview/guitools/fitDialog.py @ 55fd102

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 55fd102 was 1c94a9f1, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Fixed all sorts of bugs: replotting problems, bad logic in rescaling function, removed buggy field in dialog box, improved usability for linear fit.

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