source: sasview/guitools/fitDialog.py @ 73b1c72

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 73b1c72 was ad8bcd6, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Improved transformation so that it always plots something reasonable in log scale. Minor tweak to dragging.

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