Changeset dd5bf63 in sasview for src/sas/sasgui/plottools


Ignore:
Timestamp:
Jul 10, 2016 11:40:42 PM (8 years ago)
Author:
butler
Branches:
master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, costrafo411, magnetic_scatt, release-4.1.1, release-4.1.2, release-4.2.2, release_4.0.1, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
Children:
c23f303, 77d92cd, d398285
Parents:
3409a90
Message:

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.

Location:
src/sas/sasgui/plottools
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • src/sas/sasgui/plottools/LineModel.py

    rd7bb526 rdd5bf63  
    11#!/usr/bin/env python 
    22""" 
    3 Provide Line function (y= A + Bx) 
     3Provide Line function (y= Ax + B). Until July 10, 2016 this function provided 
     4(y= A + Bx).  This however was contrary to all the other code using it which  
     5assumed (y= mx+b) or in this nomenclature (y=Ax + B). This lead to some 
     6contortions in the code and worse incorrect calculations until now for at least 
     7some of the functions.  This seemed the easiest to fix particularly since this 
     8function should disappear in a future iteration (see notes in fitDialog) 
     9 
     10                -PDB   July 10, 2016 
    411""" 
    512 
     
    1017    Class that evaluates a linear model. 
    1118 
    12     f(x) = A + Bx 
     19    f(x) = Ax + B 
    1320 
    1421    List of default parameters: 
    15     A = 0.0 
    16     B = 0.0 
     22    A = 1.0 
     23    B = 1.0 
    1724    """ 
    1825 
     
    5360 
    5461        """ 
    55         return  self.params['A'] + (x * self.params['B']) 
     62        return  (self.params['A'] * x) + self.params['B'] 
    5663 
    5764    def run(self, x=0.0): 
    5865        """ 
    5966        Evaluate the model 
     67 
     68        :note: This is the function called by fitDialog to calculate the 
     69        the y(xmin) and y(xmax), but the only difference between this and 
     70        runXY is when the if statement is true. I however cannot see what that 
     71        function is for.  It needs to be documented here or removed. 
     72        -PDB 7/10/16  
    6073 
    6174        :param x: simple value 
     
    7487    def runXY(self, x=0.0): 
    7588        """ 
    76         Evaluate the model 
     89        Evaluate the model. 
     90         
     91        :note: This is to be what is called by fitDialog for the actual fit 
     92        but the only difference between this and run is when the if  
     93        statement is true. I however cannot see what that function 
     94        is for.  It needs to be documented here or removed. -PDB 7/10/16  
    7795 
    7896        :param x: simple value 
  • src/sas/sasgui/plottools/PlotPanel.py

    r16b769b rdd5bf63  
    646646                dlg.setFitRange(self.xminView, self.xmaxView, 
    647647                                self.xmin, self.xmax) 
     648            # It would be nice for this to NOT be modal (i.e. Show). 
     649            # Not sure about other ramifications - for example 
     650            # if a second linear fit is started before the first is closed. 
     651            # consider for future - being able to work on the plot while 
     652            # seing the fit values would be very nice  -- PDB 7/10/16 
    648653            dlg.ShowModal() 
    649654 
  • src/sas/sasgui/plottools/fitDialog.py

    r7c1d04a rdd5bf63  
    2020def format_number(value, high=False): 
    2121    """ 
    22     Return a float in a standardized, human-readable formatted string 
     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. 
    2324    """ 
    2425    try: 
     
    4041        """ 
    4142        Dialog window pops- up when select Linear fit on Context menu 
    42         Displays fitting parameters 
     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 
    4363        """ 
    4464        wx.Dialog.__init__(self, parent, title=title, 
     
    5070        # Registered owner for close event 
    5171        self._registered_close = None 
    52  
    5372        # dialog panel self call function to plot the fitting function 
     73        # calls the calling PlotPanel method onFitDisplay 
    5474        self.push_data = push_data 
    55         # dialog self plottable 
     75        # dialog self plottable - basically the plot we are working with 
     76        # passed in by the caller 
    5677        self.plottable = plottable 
     78        # is this a Guinier fit 
    5779        self.rg_on = False 
    58         # Receive transformations of x and y 
     80        # Receive transformations of x and y - basically transform is passed 
     81        # as caller method that returns its current value for these 
    5982        self.xLabel, self.yLabel, self.Avalue, self.Bvalue, \ 
    6083               self.ErrAvalue, self.ErrBvalue, self.Chivalue = self.transform() 
    6184 
    62         # Dialog interface 
     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 
    63150        vbox = wx.BoxSizer(wx.VERTICAL) 
    64151        sizer = wx.GridBagSizer(5, 5) 
     152        sizer_button = wx.BoxSizer(wx.HORIZONTAL) 
     153         
     154        #size of string boxes in pixels 
    65155        _BOX_WIDTH = 100 
    66  
    67         self.tcA = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     156        _BOX_HEIGHT = 20 
     157        #now set up all the text fields 
     158        self.tcA = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    68159        self.tcA.SetToolTipString("Fit value for the slope parameter.") 
    69         self.tcErrA = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     160        self.tcErrA = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    70161        self.tcErrA.SetToolTipString("Error on the slope parameter.") 
    71         self.tcB = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     162        self.tcB = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    72163        self.tcA.SetToolTipString("Fit value for the constant parameter.") 
    73         self.tcErrB = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     164        self.tcErrB = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    74165        self.tcErrB.SetToolTipString("Error on the constant parameter.") 
    75         self.tcChi = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     166        self.tcChi = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    76167        self.tcChi.SetToolTipString("Chi^2 over degrees of freedom.") 
    77         self.xminFit = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     168        self.xminFit = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    78169        msg = "Enter the minimum value on " 
    79170        msg += "the x-axis to be included in the fit." 
    80171        self.xminFit.SetToolTipString(msg) 
    81         self.xmaxFit = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     172        self.xmaxFit = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    82173        msg = "Enter the maximum value on " 
    83174        msg += " the x-axis to be included in the fit." 
    84175        self.xmaxFit.SetToolTipString(msg) 
    85         self.initXmin = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     176        self.initXmin = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    86177        msg = "Minimum value on the x-axis for the plotted data." 
    87178        self.initXmin.SetToolTipString(msg) 
    88         self.initXmax = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, 20)) 
     179        self.initXmax = wx.TextCtrl(self, -1, size=(_BOX_WIDTH, _BOX_HEIGHT)) 
    89180        msg = "Maximum value on the x-axis for the plotted data." 
    90181        self.initXmax.SetToolTipString(msg) 
     
    98189        self.initXmax.SetBackgroundColour(_BACKGROUND_COLOR) 
    99190 
    100         # Buttons on the bottom 
     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 
    101194        self.bg_on = False 
    102         self.static_line_1 = wx.StaticLine(self, -1) 
    103         self.btFit = wx.Button(self, -1, 'Fit') 
    104         self.btFit.Bind(wx.EVT_BUTTON, self._onFit) 
    105         self.btFit.SetToolTipString("Perform fit.") 
    106         self.btClose = wx.Button(self, wx.ID_CANCEL, 'Close') 
    107         self.btClose.Bind(wx.EVT_BUTTON, self._on_close) 
    108195        if RG_ON: 
    109196            if (self.yLabel == "ln(y)" or self.yLabel == "ln(y*x)") and \ 
     
    112199            if (self.xLabel == "x^(4)") and (self.yLabel == "y*x^(4)"): 
    113200                self.bg_on = True 
    114         # Intro 
    115         explanation = "Perform fit for y(x) = ax + b" 
     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" 
    116208        if self.bg_on: 
    117209            param_a = 'Background (= Parameter a)' 
    118210        else: 
    119211            param_a = 'Parameter a' 
    120         vbox.Add(sizer) 
     212 
     213 
     214        #Now set this all up in the GridBagSizer sizer 
    121215        ix = 0 
    122         iy = 1 
     216        iy = 0 
     217        sizer.Add(self.textwarn, (iy, ix), 
     218                  (2, 3), wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15) 
     219        iy += 2 
    123220        sizer.Add(wx.StaticText(self, -1, explanation), (iy, ix), 
    124221                  (1, 1), wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15) 
    125         iy += 2 
     222        iy += 1 
    126223        sizer.Add(wx.StaticText(self, -1, param_a), (iy, ix), 
    127224                  (1, 1), wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15) 
     
    281378                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0) 
    282379 
     380        #Now add some space before the separation line 
    283381        iy += 1 
    284         ix = 1 
    285  
    286         vbox.Add(self.static_line_1, 0, wx.EXPAND, 0) 
    287         sizer_button = wx.BoxSizer(wx.HORIZONTAL) 
     382        ix = 0 
     383        sizer.Add((20,20), (iy, ix), (1, 1), 
     384                      wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 0) 
     385 
     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) 
    288392        sizer_button.Add((20, 20), 1, wx.EXPAND | wx.ADJUST_MINSIZE, 0) 
    289         sizer_button.Add(self.btFit, 0, wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10) 
     393        sizer_button.Add(self.btFit, 0, 
     394                         wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10) 
    290395        sizer_button.Add(self.btClose, 0, 
    291396                         wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE, 10) 
     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) 
    292401        vbox.Add(sizer_button, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 10) 
    293402 
    294         sizer.Add(self.btFit, (iy, ix), (1, 1), wx.LEFT | wx.ADJUST_MINSIZE, 0) 
    295403        # panel.SetSizer(sizer) 
    296404        self.SetSizer(vbox) 
    297405        self.Centre() 
    298         # Receives the type of model for the fitting 
    299         from LineModel import LineModel 
    300         self.model = LineModel() 
    301         # Display the fittings values 
    302         self.default_A = self.model.getParam('A') 
    303         self.default_B = self.model.getParam('B') 
    304         self.cstA = fittings.Parameter(self.model, 'A', self.default_A) 
    305         self.cstB = fittings.Parameter(self.model, 'B', self.default_B) 
    306  
    307         # Set default value of parameter in fit dialog 
    308         if self.Avalue == None: 
    309             self.tcA.SetValue(format_number(self.default_A)) 
    310         else: 
    311             self.tcA.SetLabel(format_number(self.Avalue)) 
    312         if self.Bvalue == None: 
    313             self.tcB.SetValue(format_number(self.default_B)) 
    314         else: 
    315             self.tcB.SetLabel(format_number(self.Bvalue)) 
    316         if self.ErrAvalue == None: 
    317             self.tcErrA.SetLabel(format_number(0.0)) 
    318         else: 
    319             self.tcErrA.SetLabel(format_number(self.ErrAvalue)) 
    320         if self.ErrBvalue == None: 
    321             self.tcErrB.SetLabel(format_number(0.0)) 
    322         else: 
    323             self.tcErrB.SetLabel(format_number(self.ErrBvalue)) 
    324         if self.Chivalue == None: 
    325             self.tcChi.SetLabel(format_number(0.0)) 
    326         else: 
    327             self.tcChi.SetLabel(format_number(self.Chivalue)) 
    328         if self.plottable.x != []: 
    329             # store the values of View in self.x,self.y,self.dx,self.dy 
    330             self.x, self.y, self.dx, \ 
    331                      self.dy = self.plottable.returnValuesOfView() 
    332             try: 
    333                 self.mini = self.floatForwardTransform(min(self.x)) 
    334             except: 
    335                 self.mini = "Invalid" 
    336             try: 
    337                 self.maxi = self.floatForwardTransform(max(self.x)) 
    338             except: 
    339                 self.maxi = "Invalid" 
    340  
    341             self.initXmin.SetValue(format_number(min(self.plottable.x))) 
    342             self.initXmax.SetValue(format_number(max(self.plottable.x))) 
    343             self.mini = min(self.x) 
    344             self.maxi = max(self.x) 
    345             self.xminFit.SetValue(format_number(self.mini)) 
    346             self.xmaxFit.SetValue(format_number(self.maxi)) 
    347406 
    348407    def register_close(self, owner): 
     
    371430        the button Fit.Computes chisqr , 
    372431        A and B parameters of the best linear fit y=Ax +B 
    373         Push a plottable to 
     432        Push a plottable to the caller 
    374433        """ 
    375434        tempx = [] 
     
    389448                xmin = xminView 
    390449                xmax = xmaxView 
     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))) 
    391454                # Store the transformed values of view x, y,dy 
    392455                # in variables  before the fit 
     
    485548                    tempy.append(y_model) 
    486549                # Set the fit parameter display when  FitDialog is opened again 
    487                 self.Avalue = cstB 
    488                 self.Bvalue = cstA 
     550                self.Avalue = cstA 
     551                self.Bvalue = cstB 
    489552                self.ErrAvalue = errA 
    490553                self.ErrBvalue = errB 
     
    494557 
    495558                # Display the fitting value on the Fit Dialog 
    496                 self._onsetValues(cstB, cstA, errA, errB, chisqr) 
     559                self._onsetValues(cstA, cstB, errA, errB, chisqr) 
    497560 
    498561    def _onsetValues(self, cstA, cstB, errA, errB, Chi): 
     
    501564        """ 
    502565        rg = None 
     566        _diam = None 
    503567        self.tcA.SetValue(format_number(cstA)) 
    504568        self.tcB.SetValue(format_number(cstB)) 
     
    524588                    self.I0err_tctr.SetValue(format_number(val)) 
    525589            if self.Diameter_tctr.IsShown(): 
    526                 rg = 4 * numpy.sqrt(-float(cstA)) 
    527                 value = format_number(rg) 
     590                rg = numpy.sqrt(-2 * float(cstA)) 
     591                _diam = 4 * numpy.sqrt(-float(cstA)) 
     592                value = format_number(_diam) 
    528593                self.Diameter_tctr.SetValue(value) 
    529594            if self.Diametererr_tctr.IsShown(): 
    530595                if rg != None and rg != 0: 
    531                     value = format_number(8 * float(cstA) / rg) 
     596                    value = format_number(8 * float(errA) / _diam) 
    532597                else: 
    533598                    value = '' 
     
    610675    def floatInvTransform(self, x): 
    611676        """ 
    612         transform a float.It is use to determine the x.View min and x.View 
    613         max for values not in x 
     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. 
    614681 
    615682        """ 
     
    617684        # functionality work without rewritting the whole code 
    618685        # with good design (which really should be done...). 
    619         if self.xLabel == "x^(2)": 
     686        if self.xLabel == "x": 
     687            return x 
     688        elif self.xLabel == "x^(2)": 
    620689            return math.sqrt(x) 
     690        elif self.xLabel == "x^(4)": 
     691            return math.sqrt(math.sqrt(x)) 
    621692        elif self.xLabel == "log10(x)": 
    622693            return math.pow(10, x) 
    623694        elif self.xLabel == "ln(x)": 
    624695            return math.exp(x) 
     696        elif self.xLabel == "log10(x^(4))": 
     697            return math.sqrt(math.sqrt(math.pow(10, x))) 
    625698        return x 
    626699 
  • src/sas/sasgui/plottools/fittings.py

    rd7bb526 rdd5bf63  
    11""" 
     2This module is used to fit a set of x,y data to a model passed to it. It is 
     3used to calculate the slope and intercepts for the linearized fits.  Two things 
     4should be noted: 
     5 
     6First, this fitting module uses the NLLSQ module of SciPy rather than a linear 
     7fit.  This along with a few other modules could probably be removed if we 
     8move to a linear regression approach. 
     9 
     10Second, this infrastructure does not allow for resolution smearing of the  
     11the models.  Hence the results are not that accurate even for pinhole 
     12collimation of SANS but may be good for SAXS.  It is completely wrong for  
     13slit smeared data.  
     14 
    215""" 
    316from scipy import optimize 
     
    619class Parameter(object): 
    720    """ 
    8     Class to handle model parameters 
     21    Class to handle model parameters - sets the parameters and their 
     22    initial value from the model based to it. 
    923    """ 
    1024    def __init__(self, model, name, value=None): 
Note: See TracChangeset for help on using the changeset viewer.