source: sasview/plottools/src/danse/common/plottools/fitDialog.py @ 26b0a7c

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 26b0a7c was 82a54b8, checked in by Mathieu Doucet <doucetm@…>, 13 years ago

adding plottools Part 2

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