source: sasview/src/sas/sasgui/plottools/fitDialog.py @ 61a2a0c

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 61a2a0c was dd5bf63, checked in by butler, 8 years ago

close #590. A lot of problems were caused by LineModel? and LinerFit?
having different equations (ax+b vs a+bx). Further errors in
calculations, particularly of uncertainties were fixed. The fact that
the fits to not account for smearing was verified and a warning added.
Code was also modified to update the qmin and qmax to match changes in
the transformed xmin xmax. Lots of documentation was added and the
fitdialog layout was cleaned up considerably. This is now usable though
the design of the user interface (and the whole design of linear fits)
could use a rethink.

  • Property mode set to 100644
File size: 31.3 KB
RevLine 
[a9d5684]1import wx
2from plottables import Theory1D
3import math
4import numpy
5import fittings
6import transform
7import sys
8
[2df0b74]9# Linear fit panel size
[a9d5684]10if sys.platform.count("win32") > 0:
11    FONT_VARIANT = 0
12    PNL_WIDTH = 450
13    PNL_HEIGHT = 500
14else:
15    FONT_VARIANT = 1
16    PNL_WIDTH = 500
17    PNL_HEIGHT = 500
[2df0b74]18RG_ON = True
19
[a9d5684]20def format_number(value, high=False):
21    """
[dd5bf63]22    Return a float in a standardized, human-readable formatted string.
23    This is used to output readable (e.g. x.xxxe-y) values to the panel.
[a9d5684]24    """
25    try:
26        value = float(value)
27    except:
28        output = "NaN"
29        return output.lstrip().rstrip()
[2df0b74]30
[a9d5684]31    if high:
32        output = "%-6.4g" % value
[2df0b74]33
[a9d5684]34    else:
35        output = "%-5.3g" % value
36    return output.lstrip().rstrip()
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
[dd5bf63]43        Displays fitting parameters. This class handles the linearized
44        fitting and derives and displays specialized output parameters based
45        on the scale choice of the plot calling it.
46       
47        :note1: The fitting is currently a bit convoluted as besides using
48        plottools.transform.py to handle all the conversions, it uses
49        LineModel to define a linear model and calculate a number of
50        things like residuals etc as well as the function itself given an x
51        value. It also uses fittings.py to set up the defined LineModel for
52        fitting and then send it to the SciPy NLLSQ method.  As these are by
53        definition "linear nodels" it would make more sense to just call
54        a linear solver such as scipy.stats.linregress or bumps.wsolve directly.
55        This would considerably simplify the code and remove the need I think
56        for LineModel.py and possibly fittins.py altogether.   -PDB 7/10/16
57       
58        :note2: The linearized fits do not take resolution into account. This
59        means that for poor resolution such as slit smearing the answers will
60        be completely wrong --- Rg would be OK but I0 would be orders of
61        magnitude off.  Eventually we should fix this to account properly for
62        resolution.   -PDB  7/10/16
[a9d5684]63        """
[2df0b74]64        wx.Dialog.__init__(self, parent, title=title,
[a9d5684]65                           size=(PNL_WIDTH, 350))
66        self.parent = parent
67        self.transform = transform
[2df0b74]68        # Font
[a9d5684]69        self.SetWindowVariant(variant=FONT_VARIANT)
70        # Registered owner for close event
71        self._registered_close = None
[2df0b74]72        # dialog panel self call function to plot the fitting function
[dd5bf63]73        # calls the calling PlotPanel method onFitDisplay
[a9d5684]74        self.push_data = push_data
[dd5bf63]75        # dialog self plottable - basically the plot we are working with
76        # passed in by the caller
[a9d5684]77        self.plottable = plottable
[dd5bf63]78        # is this a Guinier fit
[a9d5684]79        self.rg_on = False
[dd5bf63]80        # Receive transformations of x and y - basically transform is passed
81        # as caller method that returns its current value for these
[2df0b74]82        self.xLabel, self.yLabel, self.Avalue, self.Bvalue, \
[a9d5684]83               self.ErrAvalue, self.ErrBvalue, self.Chivalue = self.transform()
[2df0b74]84
[dd5bf63]85        # Now set up the dialog interface
86        self.layout()
87        # Receives the type of model for the fitting
88        from LineModel import LineModel
89        self.model = LineModel()
90        # Display the fittings values
91        self.default_A = self.model.getParam('A')
92        self.default_B = self.model.getParam('B')
93        self.cstA = fittings.Parameter(self.model, 'A', self.default_A)
94        self.cstB = fittings.Parameter(self.model, 'B', self.default_B)
95
96        # Set default value of parameter in the dialog panel
97        if self.Avalue == None:
98            self.tcA.SetValue(format_number(self.default_A))
99        else:
100            self.tcA.SetLabel(format_number(self.Avalue))
101        if self.Bvalue == None:
102            self.tcB.SetValue(format_number(self.default_B))
103        else:
104            self.tcB.SetLabel(format_number(self.Bvalue))
105        if self.ErrAvalue == None:
106            self.tcErrA.SetLabel(format_number(0.0))
107        else:
108            self.tcErrA.SetLabel(format_number(self.ErrAvalue))
109        if self.ErrBvalue == None:
110            self.tcErrB.SetLabel(format_number(0.0))
111        else:
112            self.tcErrB.SetLabel(format_number(self.ErrBvalue))
113        if self.Chivalue == None:
114            self.tcChi.SetLabel(format_number(0.0))
115        else:
116            self.tcChi.SetLabel(format_number(self.Chivalue))
117        if self.plottable.x != []:
118            # store the values of View in self.x,self.y,self.dx,self.dy
119            self.x, self.y, self.dx, \
120                     self.dy = self.plottable.returnValuesOfView()
121            try:
122                self.mini = self.floatForwardTransform(min(self.x))
123            except:
124                self.mini = "Invalid"
125            try:
126                self.maxi = self.floatForwardTransform(max(self.x))
127            except:
128                self.maxi = "Invalid"
129
130            self.initXmin.SetValue(format_number(min(self.plottable.x)))
131            self.initXmax.SetValue(format_number(max(self.plottable.x)))
132            self.mini = min(self.x)
133            self.maxi = max(self.x)
134            self.xminFit.SetValue(format_number(self.mini))
135            self.xmaxFit.SetValue(format_number(self.maxi))
136
137    def layout(self):
138        """
139        Sets up the panel layout for the linear fit including all the
140        labels, text entry boxes, and buttons.
141
142        """
143
144        # set up sizers first.
145        # vbox is the panel sizer and is a vertical sizer
146        # The first element of the panel is sizer which is a gridbagsizer
147        # and contains most of the text fields
148        # this is followed by a line separator added to vbox
149        # and finally the sizer_button (a horizontal sizer) adds the buttons
[2df0b74]150        vbox = wx.BoxSizer(wx.VERTICAL)
[a9d5684]151        sizer = wx.GridBagSizer(5, 5)
[dd5bf63]152        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
153       
154        #size of string boxes in pixels
[a9d5684]155        _BOX_WIDTH = 100
[dd5bf63]156        _BOX_HEIGHT = 20
157        #now set up all the text fields
158        self.tcA = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]159        self.tcA.SetToolTipString("Fit value for the slope parameter.")
[dd5bf63]160        self.tcErrA = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]161        self.tcErrA.SetToolTipString("Error on the slope parameter.")
[dd5bf63]162        self.tcB = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]163        self.tcA.SetToolTipString("Fit value for the constant parameter.")
[dd5bf63]164        self.tcErrB = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]165        self.tcErrB.SetToolTipString("Error on the constant parameter.")
[dd5bf63]166        self.tcChi = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]167        self.tcChi.SetToolTipString("Chi^2 over degrees of freedom.")
[dd5bf63]168        self.xminFit = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]169        msg = "Enter the minimum value on "
170        msg += "the x-axis to be included in the fit."
171        self.xminFit.SetToolTipString(msg)
[dd5bf63]172        self.xmaxFit = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]173        msg = "Enter the maximum value on "
174        msg += " the x-axis to be included in the fit."
175        self.xmaxFit.SetToolTipString(msg)
[dd5bf63]176        self.initXmin = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]177        msg = "Minimum value on the x-axis for the plotted data."
178        self.initXmin.SetToolTipString(msg)
[dd5bf63]179        self.initXmax = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT))
[a9d5684]180        msg = "Maximum value on the x-axis for the plotted data."
181        self.initXmax.SetToolTipString(msg)
182
183        # Make the info box not editable
[2df0b74]184        # _BACKGROUND_COLOR = '#ffdf85'
[a9d5684]185        _BACKGROUND_COLOR = self.GetBackgroundColour()
186        self.initXmin.SetEditable(False)
187        self.initXmin.SetBackgroundColour(_BACKGROUND_COLOR)
188        self.initXmax.SetEditable(False)
189        self.initXmax.SetBackgroundColour(_BACKGROUND_COLOR)
[2df0b74]190
[dd5bf63]191        #set some flags for specific types of fits like Guinier (Rg) and
192        #Porod (bg) -- this will determine WHAT boxes show up in the
193        #sizer layout and depends on the active axis transform
[a9d5684]194        self.bg_on = False
195        if RG_ON:
196            if (self.yLabel == "ln(y)" or self.yLabel == "ln(y*x)") and \
197                    (self.xLabel == "x^(2)"):
198                self.rg_on = True
199            if (self.xLabel == "x^(4)") and (self.yLabel == "y*x^(4)"):
200                self.bg_on = True
[dd5bf63]201
202        # Finally set up static text strings
203        warning = "WARNING! Resolution is NOT accounted for. \n"
204        warning += "Thus slit smeared data will give very wrong answers!"
205        self.textwarn = wx.StaticText(self, -1, warning)
206        self.textwarn.SetForegroundColour(wx.RED)
207        explanation = "Perform fit for y(x) = ax + b \n"
[a9d5684]208        if self.bg_on:
209            param_a = 'Background (= Parameter a)'
210        else:
211            param_a = 'Parameter a'
[dd5bf63]212
213
214        #Now set this all up in the GridBagSizer sizer
[a9d5684]215        ix = 0
[dd5bf63]216        iy = 0
217        sizer.Add(self.textwarn, (iy, ix),
218                  (2, 3), wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
219        iy += 2
[a9d5684]220        sizer.Add(wx.StaticText(self, -1, explanation), (iy, ix),
[2df0b74]221                  (1, 1), wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[dd5bf63]222        iy += 1
[a9d5684]223        sizer.Add(wx.StaticText(self, -1, param_a), (iy, ix),
[2df0b74]224                  (1, 1), wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]225        ix += 1
[2df0b74]226        sizer.Add(self.tcA, (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]227        ix += 1
228        sizer.Add(wx.StaticText(self, -1, '+/-'),
[2df0b74]229                  (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]230        ix += 1
231        sizer.Add(self.tcErrA, (iy, ix), (1, 1),
[2df0b74]232                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]233        iy += 1
234        ix = 0
[2df0b74]235        sizer.Add(wx.StaticText(self, -1, 'Parameter b'), (iy, ix), (1, 1),
236                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]237        ix += 1
[2df0b74]238        sizer.Add(self.tcB, (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]239        ix += 1
240        sizer.Add(wx.StaticText(self, -1, '+/-'), (iy, ix),
[2df0b74]241                  (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]242        ix += 1
243        sizer.Add(self.tcErrB, (iy, ix), (1, 1),
[2df0b74]244                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]245        iy += 1
246        ix = 0
247        sizer.Add(wx.StaticText(self, -1, 'Chi2/dof'), (iy, ix), (1, 1),
[2df0b74]248                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]249        ix += 1
[2df0b74]250        sizer.Add(self.tcChi, (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]251        iy += 2
252        ix = 1
253        sizer.Add(wx.StaticText(self, -1, 'Min'), (iy, ix), (1, 1),
[2df0b74]254                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]255        ix += 2
256        sizer.Add(wx.StaticText(self, -1, 'Max'), (iy, ix),
[2df0b74]257                  (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]258
259        iy += 1
260        ix = 0
261        sizer.Add(wx.StaticText(self, -1, 'Maximum range (linear scale)'),
[2df0b74]262                  (iy, ix), (1, 1),
263                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]264        ix += 1
[2df0b74]265        sizer.Add(self.initXmin, (iy, ix), (1, 1),
266                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]267        ix += 2
[2df0b74]268        sizer.Add(self.initXmax, (iy, ix), (1, 1),
269                  wx.EXPAND | wx.ADJUST_MINSIZE, 0)
270
[a9d5684]271        iy += 1
272        ix = 0
273        sizer.Add(wx.StaticText(self, -1, 'Fit range of ' + self.xLabel),
274                  (iy, ix), (1, 1),
[2df0b74]275                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]276        ix += 1
277        sizer.Add(self.xminFit, (iy, ix), (1, 1),
[2df0b74]278                  wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]279        ix += 2
[2df0b74]280        sizer.Add(self.xmaxFit, (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]281        if self.rg_on:
282            self.SetSize((PNL_WIDTH, PNL_HEIGHT))
283            I0_stxt = wx.StaticText(self, -1, 'I(q=0)')
284            self.I0_tctr = wx.TextCtrl(self, -1, '')
285            self.I0_tctr.SetEditable(False)
286            self.I0_tctr.SetBackgroundColour(_BACKGROUND_COLOR)
287            self.I0err_tctr = wx.TextCtrl(self, -1, '')
288            self.I0err_tctr.SetEditable(False)
289            self.I0err_tctr.SetBackgroundColour(_BACKGROUND_COLOR)
290            Rg_stxt = wx.StaticText(self, -1, 'Rg [A]')
[2df0b74]291            Rg_stxt.Show(self.yLabel == "ln(y)")
[a9d5684]292            self.Rg_tctr = wx.TextCtrl(self, -1, '')
293            self.Rg_tctr.SetEditable(False)
294            self.Rg_tctr.SetBackgroundColour(_BACKGROUND_COLOR)
[2df0b74]295            self.Rg_tctr.Show(self.yLabel == "ln(y)")
[a9d5684]296            self.Rgerr_tctr = wx.TextCtrl(self, -1, '')
297            self.Rgerr_tctr.SetEditable(False)
298            self.Rgerr_tctr.SetBackgroundColour(_BACKGROUND_COLOR)
[2df0b74]299            self.Rgerr_tctr.Show(self.yLabel == "ln(y)")
[a9d5684]300            self.Rgerr_pm = wx.StaticText(self, -1, '+/-')
[2df0b74]301            self.Rgerr_pm.Show(self.yLabel == "ln(y)")
[a9d5684]302            Diameter_stxt = wx.StaticText(self, -1, 'Rod Diameter [A]')
303            Diameter_stxt.Show(self.yLabel == "ln(y*x)")
304            self.Diameter_tctr = wx.TextCtrl(self, -1, '')
305            self.Diameter_tctr.SetEditable(False)
306            self.Diameter_tctr.SetBackgroundColour(_BACKGROUND_COLOR)
307            self.Diameter_tctr.Show(self.yLabel == "ln(y*x)")
308            self.Diameter_pm = wx.StaticText(self, -1, '+/-')
309            self.Diameter_pm.Show(self.yLabel == "ln(y*x)")
310            self.Diametererr_tctr = wx.TextCtrl(self, -1, '')
311            self.Diametererr_tctr.SetEditable(False)
312            self.Diametererr_tctr.SetBackgroundColour(_BACKGROUND_COLOR)
313            self.Diametererr_tctr.Show(self.yLabel == "ln(y*x)")
314            RgQmin_stxt = wx.StaticText(self, -1, 'Rg*Qmin')
315            self.RgQmin_tctr = wx.TextCtrl(self, -1, '')
316            self.RgQmin_tctr.SetEditable(False)
317            self.RgQmin_tctr.SetBackgroundColour(_BACKGROUND_COLOR)
318            RgQmax_stxt = wx.StaticText(self, -1, 'Rg*Qmax')
319            self.RgQmax_tctr = wx.TextCtrl(self, -1, '')
320            self.RgQmax_tctr.SetEditable(False)
321            self.RgQmax_tctr.SetBackgroundColour(_BACKGROUND_COLOR)
322
323            iy += 2
324            ix = 0
[2df0b74]325            sizer.Add(I0_stxt, (iy, ix), (1, 1),
326                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]327            ix += 1
[2df0b74]328            sizer.Add(self.I0_tctr, (iy, ix), (1, 1),
329                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]330            ix += 1
331            sizer.Add(wx.StaticText(self, -1, '+/-'), (iy, ix),
[2df0b74]332                      (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]333            ix += 1
[2df0b74]334            sizer.Add(self.I0err_tctr, (iy, ix), (1, 1),
335                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
336
[a9d5684]337            iy += 1
338            ix = 0
[2df0b74]339            sizer.Add(Rg_stxt, (iy, ix), (1, 1),
340                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]341            ix += 1
[2df0b74]342            sizer.Add(self.Rg_tctr, (iy, ix), (1, 1),
343                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
344
[a9d5684]345            ix += 1
346            sizer.Add(self.Rgerr_pm, (iy, ix),
[2df0b74]347                      (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]348            ix += 1
[2df0b74]349            sizer.Add(self.Rgerr_tctr, (iy, ix), (1, 1),
350                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]351            iy += 1
352            ix = 0
[2df0b74]353            sizer.Add(Diameter_stxt, (iy, ix), (1, 1),
354                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]355            ix += 1
[2df0b74]356            sizer.Add(self.Diameter_tctr, (iy, ix), (1, 1),
357                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
358
[a9d5684]359            ix += 1
360            sizer.Add(self.Diameter_pm, (iy, ix),
[2df0b74]361                      (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]362            ix += 1
[2df0b74]363            sizer.Add(self.Diametererr_tctr, (iy, ix), (1, 1),
364                      wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]365            iy += 1
366            ix = 0
[2df0b74]367            sizer.Add(RgQmin_stxt, (iy, ix), (1, 1),
368                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]369            ix += 1
[2df0b74]370            sizer.Add(self.RgQmin_tctr, (iy, ix), (1, 1),
371                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[a9d5684]372            iy += 1
373            ix = 0
[2df0b74]374            sizer.Add(RgQmax_stxt, (iy, ix), (1, 1),
375                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
[a9d5684]376            ix += 1
[2df0b74]377            sizer.Add(self.RgQmax_tctr, (iy, ix), (1, 1),
378                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
379
[dd5bf63]380        #Now add some space before the separation line
[a9d5684]381        iy += 1
[dd5bf63]382        ix = 0
383        sizer.Add((20,20), (iy, ix), (1, 1),
384                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[2df0b74]385
[dd5bf63]386        # Buttons on the bottom
387        self.btFit = wx.Button(self, -1, 'Fit')
388        self.btFit.Bind(wx.EVT_BUTTON, self._onFit)
389        self.btFit.SetToolTipString("Perform fit.")
390        self.btClose = wx.Button(self, wx.ID_CANCEL, 'Close')
391        self.btClose.Bind(wx.EVT_BUTTON, self._on_close)
[2df0b74]392        sizer_button.Add((20, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0)
[dd5bf63]393        sizer_button.Add(self.btFit, 0,
394                         wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10)
[a9d5684]395        sizer_button.Add(self.btClose, 0,
[2df0b74]396                         wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10)
[dd5bf63]397       
398        vbox.Add(sizer)
399        self.static_line_1 = wx.StaticLine(self, -1)       
400        vbox.Add(self.static_line_1, 0, wx.EXPAND, 0)
[2df0b74]401        vbox.Add(sizer_button, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 10)
402
403        # panel.SetSizer(sizer)
[a9d5684]404        self.SetSizer(vbox)
405        self.Centre()
[2df0b74]406
[a9d5684]407    def register_close(self, owner):
408        """
409        Method to register the close event to a parent
410        window that needs notification when the dialog
411        is closed
[2df0b74]412
[a9d5684]413        :param owner: parent window
[2df0b74]414
[a9d5684]415        """
416        self._registered_close = owner
[2df0b74]417
[a9d5684]418    def _on_close(self, event):
419        """
420        Close event.
421        Notify registered owner if available.
422        """
423        event.Skip()
424        if self._registered_close is not None:
425            self._registered_close()
[2df0b74]426
[a9d5684]427    def _onFit(self, event):
428        """
429        Performs the fit. Receive an event when clicking on
430        the button Fit.Computes chisqr ,
431        A and B parameters of the best linear fit y=Ax +B
[dd5bf63]432        Push a plottable to the caller
[a9d5684]433        """
434        tempx = []
435        tempy = []
436        tempdy = []
[2df0b74]437
[a9d5684]438        # Check if View contains a x array .we online fit when x exits
439        # makes transformation for y as a line to fit
440        if self.x != []:
[2df0b74]441            if self.checkFitValues(self.xminFit) == True:
442                # Check if the field of Fit Dialog contain values
[a9d5684]443                # and use the x max and min of the user
444                if not self._checkVal(self.xminFit, self.xmaxFit):
445                    return
446                xminView = float(self.xminFit.GetValue())
447                xmaxView = float(self.xmaxFit.GetValue())
448                xmin = xminView
449                xmax = xmaxView
[dd5bf63]450                # Set the qmin and qmax in the panel that matches the
451                # transformed min and max
452                self.initXmin.SetValue(format_number(self.floatInvTransform(xmin)))
453                self.initXmax.SetValue(format_number(self.floatInvTransform(xmax)))
[a9d5684]454                # Store the transformed values of view x, y,dy
455                # in variables  before the fit
456                if self.yLabel.lower() == "log10(y)":
[2df0b74]457                    if self.xLabel.lower() == "log10(x)":
[a9d5684]458                        for i in range(len(self.x)):
459                            if self.x[i] >= math.log10(xmin):
460                                tempy.append(math.log10(self.y[i]))
[2df0b74]461                                tempdy.append(transform.errToLogX(self.y[i], 0, self.dy[i], 0))
[a9d5684]462                    else:
463                        for i in range(len(self.y)):
464                            tempy.append(math.log10(self.y[i]))
[2df0b74]465                            tempdy.append(transform.errToLogX(self.y[i], 0, self.dy[i], 0))
[a9d5684]466                else:
467                    tempy = self.y
468                    tempdy = self.dy
[2df0b74]469
470                if self.xLabel.lower() == "log10(x)":
[a9d5684]471                    for x_i in self.x:
472                        if x_i >= math.log10(xmin):
473                            tempx.append(math.log10(x_i))
474                else:
475                    tempx = self.x
[2df0b74]476
477                # Find the fitting parameters
[a9d5684]478                # Always use the same defaults, so that fit history
[2df0b74]479                # doesn't play a role!
[a9d5684]480                self.cstA = fittings.Parameter(self.model, 'A', self.default_A)
481                self.cstB = fittings.Parameter(self.model, 'B', self.default_B)
[2df0b74]482
483                if self.xLabel.lower() == "log10(x)":
[a9d5684]484                    tempdy = numpy.asarray(tempdy)
485                    tempdy[tempdy == 0] = 1
[b9a5f0e]486                    chisqr, out, cov = fittings.sasfit(self.model,
[2df0b74]487                                                       [self.cstA, self.cstB],
488                                                       tempx, tempy,
489                                                       tempdy,
490                                                       math.log10(xmin),
491                                                       math.log10(xmax))
[a9d5684]492                else:
493                    tempdy = numpy.asarray(tempdy)
494                    tempdy[tempdy == 0] = 1
[b9a5f0e]495                    chisqr, out, cov = fittings.sasfit(self.model,
[2df0b74]496                                                       [self.cstA, self.cstB],
497                                                       tempx, tempy, tempdy,
498                                                       xminView, xmaxView)
[a9d5684]499                # Use chi2/dof
500                if len(tempx) > 0:
[2df0b74]501                    chisqr = chisqr / len(tempx)
502
503                # Check that cov and out are iterable before displaying them
[a9d5684]504                if cov == None:
505                    errA = 0.0
506                    errB = 0.0
507                else:
508                    errA = math.sqrt(cov[0][0])
509                    errB = math.sqrt(cov[1][1])
510                if out == None:
511                    cstA = 0.0
512                    cstB = 0.0
513                else:
514                    cstA = out[0]
515                    cstB = out[1]
516                # Reset model with the right values of A and B
517                self.model.setParam('A', float(cstA))
518                self.model.setParam('B', float(cstB))
[2df0b74]519
[a9d5684]520                tempx = []
521                tempy = []
522                y_model = 0.0
523                # load tempy with the minimum transformation
[2df0b74]524
[a9d5684]525                if self.xLabel == "log10(x)":
526                    y_model = self.model.run(math.log10(xmin))
527                    tempx.append(xmin)
528                else:
529                    y_model = self.model.run(xminView)
530                    tempx.append(xminView)
[2df0b74]531
[a9d5684]532                if self.yLabel == "log10(y)":
533                    tempy.append(math.pow(10, y_model))
534                else:
535                    tempy.append(y_model)
[2df0b74]536
[a9d5684]537                # load tempy with the maximum transformation
538                if self.xLabel == "log10(x)":
539                    y_model = self.model.run(math.log10(xmax))
540                    tempx.append(xmax)
541                else:
542                    y_model = self.model.run(xmaxView)
543                    tempx.append(xmaxView)
[2df0b74]544
[a9d5684]545                if self.yLabel == "log10(y)":
546                    tempy.append(math.pow(10, y_model))
547                else:
548                    tempy.append(y_model)
[2df0b74]549                # Set the fit parameter display when  FitDialog is opened again
[dd5bf63]550                self.Avalue = cstA
551                self.Bvalue = cstB
[a9d5684]552                self.ErrAvalue = errA
553                self.ErrBvalue = errB
554                self.Chivalue = chisqr
555                self.push_data(tempx, tempy, xminView, xmaxView,
556                               xmin, xmax, self._ongetValues())
[2df0b74]557
[a9d5684]558                # Display the fitting value on the Fit Dialog
[dd5bf63]559                self._onsetValues(cstA, cstB, errA, errB, chisqr)
[2df0b74]560
[a9d5684]561    def _onsetValues(self, cstA, cstB, errA, errB, Chi):
562        """
563        Display  the value on fit Dialog
564        """
565        rg = None
[dd5bf63]566        _diam = None
[a9d5684]567        self.tcA.SetValue(format_number(cstA))
568        self.tcB.SetValue(format_number(cstB))
569        self.tcErrA.SetValue(format_number(errA))
570        self.tcErrB.SetValue(format_number(errB))
571        self.tcChi.SetValue(format_number(Chi))
572        if self.rg_on:
573            if self.Rg_tctr.IsShown():
574                rg = numpy.sqrt(-3 * float(cstA))
575                value = format_number(rg)
576                self.Rg_tctr.SetValue(value)
577                if self.I0_tctr.IsShown():
578                    val = numpy.exp(cstB)
579                    self.I0_tctr.SetValue(format_number(val))
580            if self.Rgerr_tctr.IsShown():
581                if rg != None and rg != 0:
[7c1d04a]582                    value = format_number(3 * float(errA) / (2 * rg))
[a9d5684]583                else:
[2df0b74]584                    value = ''
[a9d5684]585                self.Rgerr_tctr.SetValue(value)
586                if self.I0err_tctr.IsShown():
[7c1d04a]587                    val = numpy.abs(numpy.exp(cstB) * errB)
[a9d5684]588                    self.I0err_tctr.SetValue(format_number(val))
589            if self.Diameter_tctr.IsShown():
[dd5bf63]590                rg = numpy.sqrt(-2 * float(cstA))
591                _diam = 4 * numpy.sqrt(-float(cstA))
592                value = format_number(_diam)
[a9d5684]593                self.Diameter_tctr.SetValue(value)
594            if self.Diametererr_tctr.IsShown():
595                if rg != None and rg != 0:
[dd5bf63]596                    value = format_number(8 * float(errA) / _diam)
[a9d5684]597                else:
[2df0b74]598                    value = ''
[a9d5684]599                self.Diametererr_tctr.SetValue(value)
600            if self.RgQmin_tctr.IsShown():
[7c1d04a]601                value = format_number(rg * self.floatInvTransform(self.mini))
[a9d5684]602                self.RgQmin_tctr.SetValue(value)
603            if self.RgQmax_tctr.IsShown():
[7c1d04a]604                value = format_number(rg * self.floatInvTransform(self.maxi))
[a9d5684]605                self.RgQmax_tctr.SetValue(value)
[2df0b74]606
[a9d5684]607    def _ongetValues(self):
608        """
609        Display  the value on fit Dialog
610        """
611        return self.Avalue, self.Bvalue, self.ErrAvalue, \
612                            self.ErrBvalue, self.Chivalue
[2df0b74]613
[a9d5684]614    def _checkVal(self, usermin, usermax):
615        """
616        Ensure that fields parameter contains a min and a max value
617        within x min and x max range
618        """
619        self.mini = float(self.xminFit.GetValue())
620        self.maxi = float(self.xmaxFit.GetValue())
621        flag = True
622        try:
623            mini = float(usermin.GetValue())
624            maxi = float(usermax.GetValue())
625            if mini < maxi:
626                usermin.SetBackgroundColour(wx.WHITE)
627                usermin.Refresh()
628            else:
629                flag = False
630                usermin.SetBackgroundColour("pink")
631                usermin.Refresh()
632        except:
633            # Check for possible values entered
634            flag = False
635            usermin.SetBackgroundColour("pink")
636            usermin.Refresh()
[2df0b74]637
[a9d5684]638        return flag
[2df0b74]639
[a9d5684]640    def floatForwardTransform(self, x):
641        """
642        transform a float.
643        """
[2df0b74]644        # TODO: refactor this with proper object-oriented design
[a9d5684]645        # This code stinks.
[2df0b74]646        if self.xLabel == "x":
[a9d5684]647            return transform.toX(x)
[2df0b74]648        if self.xLabel == "x^(2)":
[a9d5684]649            return transform.toX2(x)
[2df0b74]650        if self.xLabel == "ln(x)":
[a9d5684]651            return transform.toLogX(x)
[2df0b74]652        if self.xLabel == "log10(x)":
[a9d5684]653            return math.log10(x)
[2df0b74]654
[a9d5684]655    def floatTransform(self, x):
656        """
657        transform a float.It is use to determine the x.
658        View min and x.View max for values
659        not in x
660        """
[2df0b74]661        # TODO: refactor this with proper object-oriented design
[a9d5684]662        # This code stinks.
[2df0b74]663        if self.xLabel == "x":
[a9d5684]664            return transform.toX(x)
[2df0b74]665        if self.xLabel == "x^(2)":
[a9d5684]666            return transform.toX2(x)
[2df0b74]667        if self.xLabel == "ln(x)":
[a9d5684]668            return transform.toLogX(x)
[2df0b74]669        if self.xLabel == "log10(x)":
[a9d5684]670            if x > 0:
671                return x
672            else:
673                raise ValueError, "cannot compute log of a negative number"
[2df0b74]674
[a9d5684]675    def floatInvTransform(self, x):
676        """
[dd5bf63]677        transform a float.It is used to determine the x.View min and x.View
678        max for values not in x.  Also used to properly calculate RgQmin,
679        RgQmax and to update qmin and qmax in the linear range boxes on the
680        panel.
[2df0b74]681
[a9d5684]682        """
[2df0b74]683        # TODO: refactor this. This is just a hack to make the
[a9d5684]684        # functionality work without rewritting the whole code
685        # with good design (which really should be done...).
[dd5bf63]686        if self.xLabel == "x":
687            return x
688        elif self.xLabel == "x^(2)":
[a9d5684]689            return math.sqrt(x)
[dd5bf63]690        elif self.xLabel == "x^(4)":
691            return math.sqrt(math.sqrt(x))
[2df0b74]692        elif self.xLabel == "log10(x)":
[a9d5684]693            return math.pow(10, x)
[2df0b74]694        elif self.xLabel == "ln(x)":
[a9d5684]695            return math.exp(x)
[dd5bf63]696        elif self.xLabel == "log10(x^(4))":
697            return math.sqrt(math.sqrt(math.pow(10, x)))
[a9d5684]698        return x
[2df0b74]699
[a9d5684]700    def checkFitValues(self, item):
701        """
702            Check the validity of input values
703        """
704        flag = True
705        value = item.GetValue()
706        # Check for possible values entered
[2df0b74]707        if self.xLabel == "log10(x)":
708            if float(value) > 0:
[a9d5684]709                item.SetBackgroundColour(wx.WHITE)
710                item.Refresh()
711            else:
712                flag = False
713                item.SetBackgroundColour("pink")
714                item.Refresh()
715        return flag
[2df0b74]716
[a9d5684]717    def setFitRange(self, xmin, xmax, xminTrans, xmaxTrans):
718        """
719        Set fit parameters
720        """
721        self.xminFit.SetValue(format_number(xmin))
722        self.xmaxFit.SetValue(format_number(xmax))
[2df0b74]723
[a9d5684]724    def set_fit_region(self, xmin, xmax):
725        """
726        Set the fit region
727        :param xmin: minimum x-value to be included in fit
728        :param xmax: maximum x-value to be included in fit
729        """
730        # Check values
731        try:
732            float(xmin)
733            float(xmax)
734        except:
735            msg = "LinearFit.set_fit_region: fit range must be floats"
736            raise ValueError, msg
737        self.xminFit.SetValue(format_number(xmin))
738        self.xmaxFit.SetValue(format_number(xmax))
[2df0b74]739
740
[a9d5684]741class MyApp(wx.App):
742    """
[2df0b74]743        Test application
[a9d5684]744    """
745    def OnInit(self):
746        """
[2df0b74]747            Test application initialization
[a9d5684]748        """
749        wx.InitAllImageHandlers()
750        plot = Theory1D([], [])
751        dialog = LinearFit(parent=None, plottable=plot,
752                           push_data=self.onFitDisplay,
753                           transform=self.returnTrans,
[2df0b74]754                           title='Linear Fit')
[a9d5684]755        if dialog.ShowModal() == wx.ID_OK:
756            pass
757        dialog.Destroy()
758        return 1
[2df0b74]759
[a9d5684]760    def onFitDisplay(self, tempx, tempy, xminView, xmaxView, xmin, xmax, func):
761        """
[2df0b74]762            Test application dummy method
[a9d5684]763        """
764        pass
[2df0b74]765
[a9d5684]766    def returnTrans(self):
767        """
[2df0b74]768            Test application dummy method
[a9d5684]769        """
770        return '', '', 0, 0, 0, 0, 0
Note: See TracBrowser for help on using the repository browser.