source: sasview/prview/perspectives/pr/pr.py @ b90decd

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 b90decd was ceaf16e, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

prview: slightly better error handling

  • Property mode set to 100644
File size: 45.3 KB
RevLine 
[f3d51f6]1# Make sure the option of saving each curve is available
2# Use the I(q) curve as input and compare the output to P(r)
3
4import os
[4a5de6f]5import sys
[f3d51f6]6import wx
[aa4b8379]7import logging
[302510b]8import time
[0d88a09]9from sans.guiframe.dataFitting import Data1D
10from danse.common.plottools import Theory1D
[f3d51f6]11from sans.guicomm.events import NewPlotEvent, StatusEvent   
12import math, numpy
13from sans.pr.invertor import Invertor
[aab8ad7]14from DataLoader.loader import Loader
[f3d51f6]15
[0d88a09]16import copy
17
[f1181977]18PR_FIT_LABEL       = r"$P_{fit}(r)$"
19PR_LOADED_LABEL    = r"$P_{loaded}(r)$"
20IQ_DATA_LABEL      = r"$I_{obs}(q)$"
21IQ_FIT_LABEL       = r"$I_{fit}(q)$"
22IQ_SMEARED_LABEL   = r"$I_{smeared}(q)$"
[aa4b8379]23
24import wx.lib
25(NewPrFileEvent, EVT_PR_FILE) = wx.lib.newevent.NewEvent()
26
27
[f3d51f6]28class Plugin:
29   
[b659551]30    DEFAULT_ALPHA = 0.0001
31    DEFAULT_NFUNC = 10
32    DEFAULT_DMAX  = 140.0
33   
[d2ee6f6]34    def __init__(self, standalone=True):
[f3d51f6]35        ## Plug-in name
36        self.sub_menu = "Pr inversion"
37       
38        ## Reference to the parent window
39        self.parent = None
40       
41        ## Simulation window manager
42        self.simview = None
43       
44        ## List of panels for the simulation perspective (names)
45        self.perspective = []
46       
47        ## State data
[b659551]48        self.alpha      = self.DEFAULT_ALPHA
49        self.nfunc      = self.DEFAULT_NFUNC
50        self.max_length = self.DEFAULT_DMAX
[634f1cf]51        self.q_min      = None
52        self.q_max      = None
[a4bd2ac]53        self.has_bck    = False
[2a92852]54        self.slit_height = 0
55        self.slit_width  = 0
[f3d51f6]56        ## Remember last plottable processed
57        self.last_data  = "sphere_60_q0_2.txt"
[0bae207]58        self._current_file_data = None
[f3d51f6]59        ## Time elapsed for last computation [sec]
60        # Start with a good default
61        self.elapsed = 0.022
[aa4b8379]62        self.iq_data_shown = False
[f3d51f6]63       
64        ## Current invertor
65        self.invertor    = None
[3fd1ebc]66        self.pr          = None
[feb21d8]67        # Copy of the last result in case we need to display it.
68        self._last_pr    = None
69        self._last_out   = None
70        self._last_cov   = None
[f3d51f6]71        ## Calculation thread
72        self.calc_thread = None
73        ## Estimation thread
74        self.estimation_thread = None
75        ## Result panel
76        self.control_panel = None
77        ## Currently views plottable
78        self.current_plottable = None
[b659551]79        ## Number of P(r) points to display on the output plot
80        self._pr_npts = 51
[aa4b8379]81        ## Flag to let the plug-in know that it is running standalone
[d2ee6f6]82        self.standalone = standalone
[feb21d8]83        self._normalize_output = False
84        self._scale_output_unity = False
[aa4b8379]85       
[f1181977]86        ## List of added P(r) plots
87        self._added_plots = {}
88        self._default_Iq  = {}
89       
[6f1f129]90        # Associate the inversion state reader with .prv files
91        from DataLoader.loader import Loader
92        from inversion_state import Reader
93         
[0d88a09]94        # Create a CanSAS/Pr reader
95        self.state_reader = Reader(self.set_state)
[6f1f129]96        l = Loader()
[410aad8]97        l.associate_file_reader('.prv', self.state_reader)
[6f1f129]98               
[aa4b8379]99        # Log startup
100        logging.info("Pr(r) plug-in started")
101       
[410aad8]102    def set_state(self, state, datainfo=None):
[6f1f129]103        """
104            Call-back method for the inversion state reader.
105            This method is called when a .prv file is loaded.
106           
107            @param state: InversionState object
[410aad8]108            @param datainfo: Data1D object [optional]
[6f1f129]109        """
[410aad8]110        try:
[0d88a09]111            if datainfo is None:
112                raise RuntimeError, "Pr.set_state: datainfo parameter cannot be None in standalone mode"
[302510b]113
[0d88a09]114            # Ensuring that plots are coordinated correctly
115            t = time.localtime(datainfo.meta_data['prstate'].timestamp)
116            time_str = time.strftime("%b %d %H:%M", t)
117           
118            # Check that no time stamp is already appended
119            max_char = datainfo.meta_data['prstate'].file.find("[")
120            if max_char < 0:
121                max_char = len(datainfo.meta_data['prstate'].file)
122           
123            datainfo.meta_data['prstate'].file = datainfo.meta_data['prstate'].file[0:max_char] +' [' + time_str + ']'
124            datainfo.filename = datainfo.meta_data['prstate'].file
[410aad8]125               
[0d88a09]126            self.current_plottable = datainfo
127            self.current_plottable.group_id = datainfo.meta_data['prstate'].file
128           
[410aad8]129            # Load the P(r) results
130            self.control_panel.set_state(state)
131                       
132            # Make sure the user sees the P(r) panel after loading
133            self.parent.set_perspective(self.perspective)           
134
135        except:
136            logging.error("prview.set_state: %s" % sys.exc_value)
[f3d51f6]137
138    def populate_menu(self, id, owner):
139        """
140            Create a menu for the plug-in
141        """
[7cb0353]142        return []
[f3d51f6]143   
[119a11d]144    def help(self, evt):
145        """
146            Show a general help dialog.
147            TODO: replace the text with a nice image
148        """
149        from inversion_panel import HelpDialog
150        dialog = HelpDialog(None, -1)
151        if dialog.ShowModal() == wx.ID_OK:
152            dialog.Destroy()
153        else:
154            dialog.Destroy()
155   
[f3d51f6]156    def _fit_pr(self, evt):
157        from sans.pr.invertor import Invertor
158        import numpy
159        import pylab
160        import math
161        from sans.guicomm.events import NewPlotEvent           
[ff7acff]162        from danse.common.plottools import Data1D, Theory1D
[f3d51f6]163       
164        # Generate P(r) for sphere
165        radius = 60.0
166        d_max  = 2*radius
167       
168       
169        r = pylab.arange(0.01, d_max, d_max/51.0)
170        M = len(r)
171        y = numpy.zeros(M)
172        pr_err = numpy.zeros(M)
173       
174        sum = 0.0
175        for j in range(M):
176            value = self.pr_theory(r[j], radius)
177            sum += value
178            y[j] = value
179            pr_err[j] = math.sqrt(y[j])
180
181           
182        y = y/sum*d_max/len(r)
183
184
185
186        # Perform fit
187        pr = Invertor()
188        pr.d_max = d_max
189        pr.alpha = 0
190        pr.x = r
191        pr.y = y
192        pr.err = pr_err
193        out, cov = pr.pr_fit()
194        for i in range(len(out)):
195            print "%g +- %g" % (out[i], math.sqrt(cov[i][i]))
196
197
198        # Show input P(r)
[b659551]199        new_plot = Data1D(pr.x, pr.y, dy=pr.err)
[f3d51f6]200        new_plot.name = "P_{obs}(r)"
201        new_plot.xaxis("\\rm{r}", 'A')
202        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
203        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Pr"))
204
205        # Show P(r) fit
206        self.show_pr(out, pr)
207       
208        # Show I(q) fit
209        q = pylab.arange(0.001, 0.1, 0.01/51.0)
210        self.show_iq(out, pr, q)
211       
212       
213    def show_shpere(self, x, radius=70.0, x_range=70.0):
214        import numpy
215        import pylab
216        import math
217        from sans.guicomm.events import NewPlotEvent           
[ff7acff]218        from danse.common.plottools import Data1D, Theory1D
[f3d51f6]219        # Show P(r)
220        y_true = numpy.zeros(len(x))
221
222        sum_true = 0.0
223        for i in range(len(x)):
224            y_true[i] = self.pr_theory(x[i], radius)           
225            sum_true += y_true[i]
226           
227        y_true = y_true/sum_true*x_range/len(x)
228       
229        # Show the theory P(r)
230        new_plot = Theory1D(x, y_true)
231        new_plot.name = "P_{true}(r)"
232        new_plot.xaxis("\\rm{r}", 'A')
233        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
234       
235       
236        #Put this call in plottables/guitools   
237        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Sphere P(r)"))
238       
[b659551]239    def get_npts(self):
240        """
241            Returns the number of points in the I(q) data
242        """
243        try:
244            return len(self.pr.x)
245        except:
246            return 0
247       
[f3d51f6]248    def show_iq(self, out, pr, q=None):
249        import numpy
250        import pylab
251        import math
252        from sans.guicomm.events import NewPlotEvent           
[ff7acff]253        from danse.common.plottools import Data1D, Theory1D
[f3d51f6]254
255        qtemp = pr.x
256        if not q==None:
257            qtemp = q
258
259        # Make a plot
260        maxq = -1
261        for q_i in qtemp:
262            if q_i>maxq:
263                maxq=q_i
264               
[634f1cf]265        minq = 0.001
266       
267        # Check for user min/max
268        if not pr.q_min==None:
269            minq = pr.q_min
270        if not pr.q_max==None:
271            maxq = pr.q_max
272               
273        x = pylab.arange(minq, maxq, maxq/301.0)
[f3d51f6]274        y = numpy.zeros(len(x))
275        err = numpy.zeros(len(x))
276        for i in range(len(x)):
277            value = pr.iq(out, x[i])
278            y[i] = value
279            try:
280                err[i] = math.sqrt(math.fabs(value))
281            except:
282                err[i] = 1.0
283                print "Error getting error", value, x[i]
284               
285        new_plot = Theory1D(x, y)
[2a92852]286        new_plot.name = IQ_FIT_LABEL
[f3d51f6]287        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
288        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
[d2ee6f6]289       
290        title = "I(q)"
291        # If we have a group ID, use it
292        if pr.info.has_key("plot_group_id"):
293            new_plot.group_id = pr.info["plot_group_id"]
294            title = pr.info["plot_group_id"]
295           
296        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
[f3d51f6]297       
[2a92852]298        # If we have used slit smearing, plot the smeared I(q) too
299        if pr.slit_width>0 or pr.slit_height>0:
300            x = pylab.arange(minq, maxq, maxq/301.0)
301            y = numpy.zeros(len(x))
302            err = numpy.zeros(len(x))
303            for i in range(len(x)):
304                value = pr.iq_smeared(out, x[i])
305                y[i] = value
306                try:
307                    err[i] = math.sqrt(math.fabs(value))
308                except:
309                    err[i] = 1.0
310                    print "Error getting error", value, x[i]
311                   
312            new_plot = Theory1D(x, y)
313            new_plot.name = IQ_SMEARED_LABEL
314            new_plot.xaxis("\\rm{Q}", 'A^{-1}')
315            new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
316            #new_plot.group_id = "test group"
317            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="I(q)"))
318       
[f3d51f6]319       
[b659551]320    def _on_pr_npts(self, evt):
321        """
322            Redisplay P(r) with a different number of points
323        """   
324        from inversion_panel import PrDistDialog
325        dialog = PrDistDialog(None, -1)
326        dialog.set_content(self._pr_npts)
327        if dialog.ShowModal() == wx.ID_OK:
328            self._pr_npts= dialog.get_content()
329            dialog.Destroy()
[feb21d8]330            self.show_pr(self._last_out, self._last_pr, self._last_cov)
[b659551]331        else:
332            dialog.Destroy()
[f3d51f6]333       
334       
335    def show_pr(self, out, pr, cov=None):
336        import numpy
337        import pylab
338        import math
339        from sans.guicomm.events import NewPlotEvent           
[ff7acff]340        from danse.common.plottools import Data1D, Theory1D
[f3d51f6]341       
342        # Show P(r)
[b659551]343        x = pylab.arange(0.0, pr.d_max, pr.d_max/self._pr_npts)
[f3d51f6]344   
345        y = numpy.zeros(len(x))
346        dy = numpy.zeros(len(x))
347        y_true = numpy.zeros(len(x))
348
349        sum = 0.0
[feb21d8]350        pmax = 0.0
[dfb58f8]351        cov2 = numpy.ascontiguousarray(cov)
352       
[f3d51f6]353        for i in range(len(x)):
[dfb58f8]354            if cov2==None:
[f3d51f6]355                value = pr.pr(out, x[i])
356            else:
[dfb58f8]357                (value, dy[i]) = pr.pr_err(out, cov2, x[i])
[660b1e6]358            sum += value*pr.d_max/len(x)
[f3d51f6]359           
[feb21d8]360            # keep track of the maximum P(r) value
361            if value>pmax:
362                pmax = value
363               
364            y[i] = value
365               
366        if self._normalize_output==True:
367            y = y/sum
368            dy = dy/sum
369        elif self._scale_output_unity==True:
370            y = y/pmax
371            dy = dy/pmax
[f3d51f6]372       
[dfb58f8]373        if cov2==None:
[f3d51f6]374            new_plot = Theory1D(x, y)
375        else:
376            new_plot = Data1D(x, y, dy=dy)
[f1181977]377        new_plot.name = PR_FIT_LABEL
[f3d51f6]378        new_plot.xaxis("\\rm{r}", 'A')
379        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
[d2ee6f6]380        # Make sure that the plot is linear
381        new_plot.xtransform="x"
382        new_plot.ytransform="y"                 
[f3d51f6]383        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit"))
384       
385        return x, pr.d_max
386       
387       
[6f1f129]388    def choose_file(self, path=None):
[f3d51f6]389        """
390       
391        """
392        #TODO: this should be in a common module
[6f1f129]393        return self.parent.choose_file(path=path)
[f3d51f6]394               
[0bae207]395               
396    def load(self, path):
397        """
398            Load data. This will eventually be replaced
399            by our standard DataLoader class.
400        """
401        class FileData:
402            x = None
403            y = None
404            err = None
405            path = None
406           
407            def __init__(self, path):
408                self.path = path
409               
410        self._current_file_data = FileData(path)
411       
[aab8ad7]412        # Use data loader to load file
413        dataread = Loader().load(path)
[ceaf16e]414       
415        # Notify the user if we could not read the file
416        if dataread is None:
417            raise RuntimeError, "Invalid data"
418           
[aab8ad7]419        x = None
420        y = None
421        err = None
422        if dataread.__class__.__name__ == 'Data1D':
423            x = dataread.x
424            y = dataread.y
425            err = dataread.dy
426        else:
[e43c012]427            if isinstance(dataread, list) and len(dataread)>0:
428                x = dataread[0].x
429                y = dataread[0].y
430                err = dataread[0].dy
431                msg = "PrView only allows a single data set at a time. "
432                msg += "Only the first data set was loaded." 
433                wx.PostEvent(self.parent, StatusEvent(status=msg))
434            else:
[6f1f129]435                if dataread is None:
436                    return x, y, err
[e43c012]437                raise RuntimeError, "This tool can only read 1D data"
[aab8ad7]438       
[0bae207]439        self._current_file_data.x = x
440        self._current_file_data.y = y
441        self._current_file_data.err = err
442        return x, y, err
443               
444    def load_columns(self, path = "sphere_60_q0_2.txt"):
445        """
446            Load 2- or 3- column ascii
447        """
[f3d51f6]448        import numpy, math, sys
449        # Read the data from the data file
450        data_x   = numpy.zeros(0)
451        data_y   = numpy.zeros(0)
452        data_err = numpy.zeros(0)
[b659551]453        scale    = None
454        min_err  = 0.0
[f3d51f6]455        if not path == None:
456            input_f = open(path,'r')
457            buff    = input_f.read()
458            lines   = buff.split('\n')
459            for line in lines:
460                try:
461                    toks = line.split()
462                    x = float(toks[0])
463                    y = float(toks[1])
[b659551]464                    if len(toks)>2:
465                        err = float(toks[2])
466                    else:
467                        if scale==None:
468                            scale = 0.05*math.sqrt(y)
469                            #scale = 0.05/math.sqrt(y)
470                            min_err = 0.01*y
471                        err = scale*math.sqrt(y)+min_err
472                        #err = 0
473                       
[4a5de6f]474                    data_x = numpy.append(data_x, x)
475                    data_y = numpy.append(data_y, y)
[b659551]476                    data_err = numpy.append(data_err, err)
[f3d51f6]477                except:
[b659551]478                    pass
[f3d51f6]479                   
[357b79b]480        if not scale==None:
481            message = "The loaded file had no error bars, statistical errors are assumed."
482            wx.PostEvent(self.parent, StatusEvent(status=message))
483        else:
484            wx.PostEvent(self.parent, StatusEvent(status=''))
485                       
[b659551]486        return data_x, data_y, data_err     
[f3d51f6]487       
[0bae207]488    def load_abs(self, path):
489        """
490            Load an IGOR .ABS reduced file
491            @param path: file path
492            @return: x, y, err vectors
493        """
494        import numpy, math, sys
495        # Read the data from the data file
496        data_x   = numpy.zeros(0)
497        data_y   = numpy.zeros(0)
498        data_err = numpy.zeros(0)
499        scale    = None
500        min_err  = 0.0
501       
502        data_started = False
503        if not path == None:
504            input_f = open(path,'r')
505            buff    = input_f.read()
506            lines   = buff.split('\n')
507            for line in lines:
508                if data_started==True:
509                    try:
510                        toks = line.split()
511                        x = float(toks[0])
512                        y = float(toks[1])
513                        if len(toks)>2:
514                            err = float(toks[2])
515                        else:
516                            if scale==None:
517                                scale = 0.05*math.sqrt(y)
518                                #scale = 0.05/math.sqrt(y)
519                                min_err = 0.01*y
520                            err = scale*math.sqrt(y)+min_err
521                            #err = 0
522                           
523                        data_x = numpy.append(data_x, x)
524                        data_y = numpy.append(data_y, y)
525                        data_err = numpy.append(data_err, err)
526                    except:
527                        pass
528                elif line.find("The 6 columns")>=0:
529                    data_started = True     
530                   
531        if not scale==None:
532            message = "The loaded file had no error bars, statistical errors are assumed."
533            wx.PostEvent(self.parent, StatusEvent(status=message))
534        else:
535            wx.PostEvent(self.parent, StatusEvent(status=''))
536                       
537        return data_x, data_y, data_err     
538       
539       
540       
[f3d51f6]541    def pr_theory(self, r, R):
542        """
543           
544        """
545        if r<=2*R:
546            return 12.0* ((0.5*r/R)**2) * ((1.0-0.5*r/R)**2) * ( 2.0 + 0.5*r/R )
547        else:
548            return 0.0
549
[660b1e6]550    def get_context_menu(self, graph=None):
[f3d51f6]551        """
552            Get the context menu items available for P(r)
[660b1e6]553            @param graph: the Graph object to which we attach the context menu
[f3d51f6]554            @return: a list of menu items with call-back function
555        """
[660b1e6]556        # Look whether this Graph contains P(r) data
[aa4b8379]557        #if graph.selected_plottable==IQ_DATA_LABEL:
[660b1e6]558        for item in graph.plottables:
[aa4b8379]559            if item.name==PR_FIT_LABEL:
[feb21d8]560                m_list = [["Add P(r) data", "Load a data file and display it on this plot", self._on_add_data],
[b659551]561                       ["Change number of P(r) points", "Change the number of points on the P(r) output", self._on_pr_npts]]
[aa4b8379]562
[900c787]563                if self._scale_output_unity==True or self._normalize_output==True:
564                    m_list.append(["Disable P(r) scaling", 
565                                   "Let the output P(r) keep the scale of the data", 
566                                   self._on_disable_scaling])
[feb21d8]567               
568                if self._scale_output_unity==False:
569                    m_list.append(["Scale P_max(r) to unity", 
570                                   "Scale P(r) so that its maximum is 1", 
571                                   self._on_scale_unity])
572                   
573                if self._normalize_output==False:
574                    m_list.append(["Normalize P(r) to unity", 
575                                   "Normalize the integral of P(r) to 1", 
576                                   self._on_normalize])
577                   
578                return m_list
579                #return [["Add P(r) data", "Load a data file and display it on this plot", self._on_add_data],
580                #       ["Change number of P(r) points", "Change the number of points on the P(r) output", self._on_pr_npts]]
581
[aa4b8379]582            elif item.name==graph.selected_plottable:
[d2ee6f6]583                    #TODO: we might want to check that the units are consistent with I(q)
584                    #      before allowing this menu item
[aa4b8379]585                return [["Compute P(r)", "Compute P(r) from distribution", self._on_context_inversion]]     
[660b1e6]586               
[aa4b8379]587        return []
[660b1e6]588
[feb21d8]589    def _on_disable_scaling(self, evt):
590        """
591            Disable P(r) scaling
592            @param evt: Menu event
593        """
594        self._normalize_output = False
595        self._scale_output_unity = False
596        self.show_pr(self._last_out, self._last_pr, self._last_cov)
597       
[f1181977]598        # Now replot the original added data
599        for plot in self._added_plots:
600            self._added_plots[plot].y = numpy.copy(self._default_Iq[plot])
601            wx.PostEvent(self.parent, NewPlotEvent(plot=self._added_plots[plot], 
602                                                   title=self._added_plots[plot].name,
603                                                   update=True))       
604       
605        # Need the update flag in the NewPlotEvent to protect against
606        # the plot no longer being there...
607       
[feb21d8]608    def _on_normalize(self, evt):
609        """
[f1181977]610            Normalize the area under the P(r) curve to 1.
611            This operation is done for all displayed plots.
612           
[feb21d8]613            @param evt: Menu event
614        """
615        self._normalize_output = True
616        self._scale_output_unity = False
617           
618        self.show_pr(self._last_out, self._last_pr, self._last_cov)
619       
[f1181977]620        # Now scale the added plots too
621        for plot in self._added_plots:
622            sum = numpy.sum(self._added_plots[plot].y)
623            npts = len(self._added_plots[plot].x)
624            sum *= self._added_plots[plot].x[npts-1]/npts
625            y = self._added_plots[plot].y/sum
626           
627            new_plot = Theory1D(self._added_plots[plot].x, y)
628            new_plot.name = self._added_plots[plot].name
629            new_plot.xaxis("\\rm{r}", 'A')
630            new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
631           
632            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, update=True,
633                                                   title=self._added_plots[plot].name))
634               
635       
636       
[feb21d8]637    def _on_scale_unity(self, evt):
638        """
[f1181977]639            Scale the maximum P(r) value on each displayed plot to 1.
640           
[feb21d8]641            @param evt: Menu event
642        """
643        self._scale_output_unity = True
644        self._normalize_output = False
645           
646        self.show_pr(self._last_out, self._last_pr, self._last_cov)
647       
[f1181977]648        # Now scale the added plots too
649        for plot in self._added_plots:
650            _max = 0
651            for y in self._added_plots[plot].y:
652                if y>_max: 
653                    _max = y
654            y = self._added_plots[plot].y/_max
655           
656            new_plot = Theory1D(self._added_plots[plot].x, y)
657            new_plot.name = self._added_plots[plot].name
658            new_plot.xaxis("\\rm{r}", 'A')
659            new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
660           
661            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, update=True,
662                                                   title=self._added_plots[plot].name))       
663       
664       
[660b1e6]665    def _on_add_data(self, evt):
666        """
667            Add a data curve to the plot
668            WARNING: this will be removed once guiframe.plotting has its full functionality
669        """
670        path = self.choose_file()
671        if path==None:
672            return
673       
[14d05ba]674        #x, y, err = self.parent.load_ascii_1D(path)
675        # Use data loader to load file
676        try:
677            dataread = Loader().load(path)
678            x = None
679            y = None
680            err = None
681            if dataread.__class__.__name__ == 'Data1D':
682                x = dataread.x
683                y = dataread.y
684                err = dataread.dy
685            else:
[e43c012]686                if isinstance(dataread, list) and len(dataread)>0:
687                    x = dataread[0].x
688                    y = dataread[0].y
689                    err = dataread[0].dy
690                    msg = "PrView only allows a single data set at a time. "
691                    msg += "Only the first data set was loaded." 
692                    wx.PostEvent(self.parent, StatusEvent(status=msg))
693                else:
694                    wx.PostEvent(self.parent, StatusEvent(status="This tool can only read 1D data"))
695                    return
696           
[14d05ba]697        except:
698            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
699            return
[660b1e6]700       
[f1181977]701        filename = os.path.basename(path)
702       
[b659551]703        #new_plot = Data1D(x, y, dy=err)
704        new_plot = Theory1D(x, y)
[f1181977]705        new_plot.name = filename
[660b1e6]706        new_plot.xaxis("\\rm{r}", 'A')
707        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
708           
[f1181977]709        # Store a ref to the plottable for later use
710        self._added_plots[filename] = new_plot
711        self._default_Iq[filename]  = numpy.copy(y)
712       
713        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=filename))
[660b1e6]714       
715       
[f3d51f6]716
717    def start_thread(self):
718        from pr_thread import CalcPr
719        from copy import deepcopy
720       
721        # If a thread is already started, stop it
722        if self.calc_thread != None and self.calc_thread.isrunning():
723            self.calc_thread.stop()
724               
725        pr = self.pr.clone()
726        self.calc_thread = CalcPr(pr, self.nfunc, error_func=self._thread_error, completefn=self._completed, updatefn=None)
727        self.calc_thread.queue()
728        self.calc_thread.ready(2.5)
729   
730    def _thread_error(self, error):
731        wx.PostEvent(self.parent, StatusEvent(status=error))
732   
[32dffae4]733    def _estimate_completed(self, alpha, message, elapsed):
[f3d51f6]734        """
735            Parameter estimation completed,
736            display the results to the user
737            @param alpha: estimated best alpha
738            @param elapsed: computation time
739        """
740        # Save useful info
741        self.elapsed = elapsed
742        self.control_panel.alpha_estimate = alpha
[32dffae4]743        if not message==None:
744            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
[35adaf6]745           
746        self.perform_estimateNT()
747   
748
749   
750    def _estimateNT_completed(self, nterms, alpha, message, elapsed):
751        """
752            Parameter estimation completed,
753            display the results to the user
754            @param alpha: estimated best alpha
755            @param nterms: estimated number of terms
756            @param elapsed: computation time
757        """
758        # Save useful info
759        self.elapsed = elapsed
760        self.control_panel.nterms_estimate = nterms
761        self.control_panel.alpha_estimate = alpha
762        if not message==None:
763            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
[f3d51f6]764   
765    def _completed(self, out, cov, pr, elapsed):
766        """
767            Method called with the results when the inversion
768            is done
769           
770            @param out: output coefficient for the base functions
771            @param cov: covariance matrix
772            @param pr: Invertor instance
773            @param elapsed: time spent computing
774        """
[dfb58f8]775        from copy import deepcopy
[f3d51f6]776        # Save useful info
777        self.elapsed = elapsed
[feb21d8]778        # Keep a copy of the last result
779        self._last_pr  = pr.clone()
780        self._last_out = out
781        self._last_cov = cov
782       
[b659551]783        # Save Pr invertor
784        self.pr = pr
785       
[d6113849]786        #message = "Computation completed in %g seconds [chi2=%g]" % (elapsed, pr.chi2)
787        #wx.PostEvent(self.parent, StatusEvent(status=message))
[f3d51f6]788
[dfb58f8]789        cov = numpy.ascontiguousarray(cov)
790
[f3d51f6]791        # Show result on control panel
792        self.control_panel.chi2 = pr.chi2
793        self.control_panel.elapsed = elapsed
794        self.control_panel.oscillation = pr.oscillations(out)
795        #print "OSCILL", pr.oscillations(out)
[d6113849]796        #print "PEAKS:", pr.get_peaks(out)
[dfb58f8]797        self.control_panel.positive = pr.get_positive(out)
798        self.control_panel.pos_err  = pr.get_pos_err(out, cov)
[a4bd2ac]799        self.control_panel.rg = pr.rg(out)
800        self.control_panel.iq0 = pr.iq0(out)
801        self.control_panel.bck = pr.background
[f3d51f6]802       
[aa4b8379]803        if False:
[0bae207]804            for i in range(len(out)):
805                try:
806                    print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
807                except: 
808                    print sys.exc_value
809                    print "%d: %g +- ?" % (i, out[i])       
810       
811            # Make a plot of I(q) data
[aa4b8379]812            new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
[f1181977]813            new_plot.name = IQ_DATA_LABEL
[aa4b8379]814            new_plot.xaxis("\\rm{Q}", 'A^{-1}')
815            new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
816            #new_plot.group_id = "test group"
817            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
[f3d51f6]818               
819        # Show I(q) fit
820        self.show_iq(out, self.pr)
821       
822        # Show P(r) fit
[dfb58f8]823        x_values, x_range = self.show_pr(out, self.pr, cov) 
[f3d51f6]824       
825        # Popup result panel
826        #result_panel = InversionResults(self.parent, -1, style=wx.RAISED_BORDER)
827       
[2a92852]828    def show_data(self, path=None, reset=False):
829        """
830            Show data read from a file
831            @param path: file path
832            @param reset: if True all other plottables will be cleared
833        """
[0bae207]834        if path is not None:
[ea5551f]835            try:
836                pr = self._create_file_pr(path)
[ceaf16e]837            except:
838                status="Problem reading data: %s" % sys.exc_value
839                wx.PostEvent(self.parent, StatusEvent(status=status))
840                raise RuntimeError, status
[6f1f129]841               
[ceaf16e]842            # If the file contains nothing, just return
843            if pr is None:
844                raise RuntimeError, "Loaded data is invalid"
845           
846            self.pr = pr
[660b1e6]847             
848        # Make a plot of I(q) data
[357b79b]849        if self.pr.err==None:
850            new_plot = Theory1D(self.pr.x, self.pr.y)
851        else:
852            new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
[f1181977]853        new_plot.name = IQ_DATA_LABEL
[660b1e6]854        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
855        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
[aa4b8379]856        new_plot.interactive = True
[660b1e6]857        #new_plot.group_id = "test group"
[2a92852]858        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="I(q)", reset=reset))
[660b1e6]859       
[0d88a09]860        self.current_plottable = new_plot
861        self.current_plottable.group_id = IQ_DATA_LABEL
862       
863       
[aa4b8379]864        # Get Q range
865        self.control_panel.q_min = self.pr.x.min()
866        self.control_panel.q_max = self.pr.x.max()
867           
[410aad8]868    def save_data(self, filepath, prstate=None):
869        """
870            Save data in provided state object.
871            TODO: move the state code away from inversion_panel and move it here.
872                    Then remove the "prstate" input and make this method private.
873                   
874            @param filepath: path of file to write to
875            @param prstate: P(r) inversion state
876        """
877        #TODO: do we need this or can we use DataLoader.loader.save directly?
878       
879        # Add output data and coefficients to state
880        prstate.coefficients = self._last_out
881        prstate.covariance = self._last_cov
882       
883        # Write the output to file
884        self.state_reader.write(filepath, self.current_plottable, prstate)
885       
[660b1e6]886       
[2a92852]887    def setup_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None, 
888                             bck=False, height=0, width=0):
[f3d51f6]889        self.alpha = alpha
890        self.nfunc = nfunc
891        self.max_length = d_max
[634f1cf]892        self.q_min = q_min
893        self.q_max = q_max
[a4bd2ac]894        self.has_bck = bck
[2a92852]895        self.slit_height = height
896        self.slit_width  = width
[f3d51f6]897       
[4a5de6f]898        try:
[2a92852]899            pr = self._create_plot_pr()
900            if not pr==None:
901                self.pr = pr
902                self.perform_inversion()
[4a5de6f]903        except:
904            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
[f3d51f6]905
[2a92852]906    def estimate_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None, 
907                                bck=False, height=0, width=0):
[f3d51f6]908        self.alpha = alpha
909        self.nfunc = nfunc
910        self.max_length = d_max
[634f1cf]911        self.q_min = q_min
912        self.q_max = q_max
[a4bd2ac]913        self.has_bck = bck
[2a92852]914        self.slit_height = height
915        self.slit_width  = width
[f3d51f6]916       
[4a5de6f]917        try:
[2a92852]918            pr = self._create_plot_pr()
919            if not pr==None:
920                self.pr = pr
921                self.perform_estimate()
[4a5de6f]922        except:
923            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))           
[f3d51f6]924
[2a92852]925    def _create_plot_pr(self, estimate=False):
[f3d51f6]926        """
927            Create and prepare invertor instance from
928            a plottable data set.
929            @param path: path of the file to read in
930        """
[ceaf16e]931        # Sanity check
932        if self.current_plottable is None:
933            msg = "Please load a valid data set before proceeding."
934            wx.PostEvent(self.parent, StatusEvent(status=msg)) 
935            return None   
936       
[f3d51f6]937        # Get the data from the chosen data set and perform inversion
938        pr = Invertor()
939        pr.d_max = self.max_length
940        pr.alpha = self.alpha
[634f1cf]941        pr.q_min = self.q_min
942        pr.q_max = self.q_max
[f3d51f6]943        pr.x = self.current_plottable.x
944        pr.y = self.current_plottable.y
[a4bd2ac]945        pr.has_bck = self.has_bck
[2a92852]946        pr.slit_height = self.slit_height
947        pr.slit_width = self.slit_width
[f3d51f6]948       
[d2ee6f6]949        # Keep track of the plot window title to ensure that
950        # we can overlay the plots
951        if hasattr(self.current_plottable, "group_id"):
952            pr.info["plot_group_id"] = self.current_plottable.group_id
953       
[f3d51f6]954        # Fill in errors if none were provided
[d2ee6f6]955        err = self.current_plottable.dy
956        all_zeros = True
957        if err == None:
[d483799]958            err = numpy.zeros(len(pr.y)) 
[d2ee6f6]959        else:   
960            for i in range(len(err)):
961                if err[i]>0:
962                    all_zeros = False
963       
964        if all_zeros:       
965            scale = None
966            min_err = 0.0
[f3d51f6]967            for i in range(len(pr.y)):
[d2ee6f6]968                # Scale the error so that we can fit over several decades of Q
969                if scale==None:
970                    scale = 0.05*math.sqrt(pr.y[i])
971                    min_err = 0.01*pr.y[i]
972                err[i] = scale*math.sqrt( math.fabs(pr.y[i]) ) + min_err
973            message = "The loaded file had no error bars, statistical errors are assumed."
974            wx.PostEvent(self.parent, StatusEvent(status=message))
975
976        pr.err = err
[2a92852]977       
978        return pr
[f3d51f6]979
980         
[2a92852]981    def setup_file_inversion(self, alpha, nfunc, d_max, path, q_min=None, q_max=None, 
982                             bck=False, height=0, width=0):
[f3d51f6]983        self.alpha = alpha
984        self.nfunc = nfunc
985        self.max_length = d_max
[634f1cf]986        self.q_min = q_min
987        self.q_max = q_max
[a4bd2ac]988        self.has_bck = bck
[2a92852]989        self.slit_height = height
990        self.slit_width  = width
[f3d51f6]991       
[4a5de6f]992        try:
[2a92852]993            pr = self._create_file_pr(path)
994            if not pr==None:
995                self.pr = pr
[4a5de6f]996                self.perform_inversion()
997        except:
998            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
[f3d51f6]999         
[2a92852]1000    def estimate_file_inversion(self, alpha, nfunc, d_max, path, q_min=None, q_max=None, 
1001                                bck=False, height=0, width=0):
[f3d51f6]1002        self.alpha = alpha
1003        self.nfunc = nfunc
1004        self.max_length = d_max
[634f1cf]1005        self.q_min = q_min
1006        self.q_max = q_max
[a4bd2ac]1007        self.has_bck = bck
[2a92852]1008        self.slit_height = height
1009        self.slit_width  = width
[f3d51f6]1010       
[4a5de6f]1011        try:
[2a92852]1012            pr = self._create_file_pr(path)
1013            if not pr==None:
1014                self.pr = pr
[4a5de6f]1015                self.perform_estimate()
1016        except:
1017            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
1018               
[f3d51f6]1019         
1020    def _create_file_pr(self, path):
1021        """
1022            Create and prepare invertor instance from
1023            a file data set.
1024            @param path: path of the file to read in
1025        """
1026        # Load data
1027        if os.path.isfile(path):
[357b79b]1028           
[0bae207]1029            if self._current_file_data is not None \
1030                and self._current_file_data.path==path:
[aab8ad7]1031                # Protect against corrupted data from
1032                # previous failed load attempt
1033                if self._current_file_data.x is None:
1034                    return None
[0bae207]1035                x = self._current_file_data.x
1036                y = self._current_file_data.y
1037                err = self._current_file_data.err
1038            else:
[e43c012]1039                # Reset the status bar so that we don't get mixed up
1040                # with old messages.
1041                #TODO: refactor this into a proper status handling
1042                wx.PostEvent(self.parent, StatusEvent(status=''))
[ceaf16e]1043                try:
1044                    x, y, err = self.load(path)
1045                except:
1046                    message = "Could not read the data file: %s" % path
1047                    wx.PostEvent(self.parent, StatusEvent(status=message))
1048                    return None
[6f1f129]1049               
1050                # If the file contains no data, just return
1051                if x is None:
[ceaf16e]1052                    message = "The loaded file contains no data"
1053                    wx.PostEvent(self.parent, StatusEvent(status=message))
[6f1f129]1054                    return None
[357b79b]1055           
1056            # If we have not errors, add statistical errors
[aab8ad7]1057            if err==None and y is not None:
[357b79b]1058                err = numpy.zeros(len(y))
[aab8ad7]1059                scale = None
1060                min_err = 0.0
[357b79b]1061                for i in range(len(y)):
[aab8ad7]1062                    # Scale the error so that we can fit over several decades of Q
1063                    if scale==None:
1064                        scale = 0.05*math.sqrt(y[i])
1065                        min_err = 0.01*y[i]
1066                    err[i] = scale*math.sqrt( math.fabs(y[i]) ) + min_err
[357b79b]1067                message = "The loaded file had no error bars, statistical errors are assumed."
1068                wx.PostEvent(self.parent, StatusEvent(status=message))
[f3d51f6]1069           
[aab8ad7]1070            try:
1071                # Get the data from the chosen data set and perform inversion
1072                pr = Invertor()
1073                pr.d_max = self.max_length
1074                pr.alpha = self.alpha
1075                pr.q_min = self.q_min
1076                pr.q_max = self.q_max
1077                pr.x = x
1078                pr.y = y
1079                pr.err = err
1080                pr.has_bck = self.has_bck
1081                pr.slit_height = self.slit_height
1082                pr.slit_width = self.slit_width
1083                return pr
1084            except:
1085                wx.PostEvent(self.parent, StatusEvent(status="Problem reading data: %s" % sys.exc_value))
[2a92852]1086        return None
[f3d51f6]1087       
1088    def perform_estimate(self):
1089        from pr_thread import EstimatePr
1090        from copy import deepcopy
1091       
1092        # If a thread is already started, stop it
1093        if self.estimation_thread != None and self.estimation_thread.isrunning():
1094            self.estimation_thread.stop()
1095               
1096        pr = self.pr.clone()
1097        self.estimation_thread = EstimatePr(pr, self.nfunc, error_func=self._thread_error, 
1098                                            completefn = self._estimate_completed, 
1099                                            updatefn   = None)
1100        self.estimation_thread.queue()
1101        self.estimation_thread.ready(2.5)
[35adaf6]1102   
1103    def perform_estimateNT(self):
1104        from pr_thread import EstimateNT
1105        from copy import deepcopy
1106       
1107        # If a thread is already started, stop it
1108        if self.estimation_thread != None and self.estimation_thread.isrunning():
1109            self.estimation_thread.stop()
1110               
1111        pr = self.pr.clone()
[2a92852]1112        # Skip the slit settings for the estimation
1113        # It slows down the application and it doesn't change the estimates
1114        pr.slit_height = 0.0
1115        pr.slit_width  = 0.0
[35adaf6]1116        self.estimation_thread = EstimateNT(pr, self.nfunc, error_func=self._thread_error, 
1117                                            completefn = self._estimateNT_completed, 
1118                                            updatefn   = None)
1119        self.estimation_thread.queue()
1120        self.estimation_thread.ready(2.5)
[f3d51f6]1121       
1122    def perform_inversion(self):
1123       
1124        # Time estimate
1125        #estimated = self.elapsed*self.nfunc**2
[2a92852]1126        #message = "Computation time may take up to %g seconds" % self.elapsed
1127        #wx.PostEvent(self.parent, StatusEvent(status=message))
[f3d51f6]1128       
1129        # Start inversion thread
1130        self.start_thread()
1131        return
1132       
1133        out, cov = self.pr.lstsq(self.nfunc)
1134       
1135        # Save useful info
1136        self.elapsed = self.pr.elapsed
1137       
1138        for i in range(len(out)):
1139            try:
1140                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
1141            except: 
1142                print "%d: %g +- ?" % (i, out[i])       
1143       
1144       
1145       
1146        # Make a plot of I(q) data
1147        new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
1148        new_plot.name = "I_{obs}(q)"
1149        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
1150        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
1151        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
1152               
1153        # Show I(q) fit
1154        self.show_iq(out, self.pr)
1155       
1156        # Show P(r) fit
1157        x_values, x_range = self.show_pr(out, self.pr, cov=cov)
1158       
1159       
1160         
1161    def _on_context_inversion(self, event):
1162        panel = event.GetEventObject()
1163
1164        from inversion_panel import InversionDlg
1165       
1166        # If we have more than one displayed plot, make the user choose
[aa4b8379]1167        if len(panel.plots)>1 and panel.graph.selected_plottable in panel.plots:
1168            dataset = panel.graph.selected_plottable
1169            if False:
1170                dialog = InversionDlg(None, -1, "P(r) Inversion", panel.plots, pars=False)
1171                dialog.set_content(self.last_data, self.nfunc, self.alpha, self.max_length)
1172                if dialog.ShowModal() == wx.ID_OK:
1173                    dataset = dialog.get_content()
1174                    dialog.Destroy()
1175                else:
1176                    dialog.Destroy()
1177                    return
[f3d51f6]1178        elif len(panel.plots)==1:
1179            dataset = panel.plots.keys()[0]
1180        else:
1181            print "Error: No data is available"
1182            return
1183       
1184        # Store a reference to the current plottable
[3fd1ebc]1185        # If we have a suggested value, use it.
1186        try:
1187            estimate = float(self.control_panel.alpha_estimate)
1188            self.control_panel.alpha = estimate
1189        except:
1190            self.control_panel.alpha = self.alpha
1191            print "No estimate yet"
1192            pass
[d6113849]1193        try:
1194            estimate = int(self.control_panel.nterms_estimate)
1195            self.control_panel.nfunc = estimate
1196        except:
1197            self.control_panel.nfunc = self.nfunc
1198            print "No estimate yet"
1199            pass
[3fd1ebc]1200       
[f3d51f6]1201        self.current_plottable = panel.plots[dataset]
1202        self.control_panel.plotname = dataset
[d6113849]1203        #self.control_panel.nfunc = self.nfunc
[f3d51f6]1204        self.control_panel.d_max = self.max_length
1205        self.parent.set_perspective(self.perspective)
[aa4b8379]1206        self.control_panel._on_invert(None)
[f3d51f6]1207           
1208    def get_panels(self, parent):
1209        """
1210            Create and return a list of panel objects
1211        """
1212        from inversion_panel import InversionControl
1213       
1214        self.parent = parent
[aa4b8379]1215        self.control_panel = InversionControl(self.parent, -1, 
1216                                              style=wx.RAISED_BORDER,
1217                                              standalone=self.standalone)
[f3d51f6]1218        self.control_panel.set_manager(self)
1219        self.control_panel.nfunc = self.nfunc
1220        self.control_panel.d_max = self.max_length
1221        self.control_panel.alpha = self.alpha
1222       
1223        self.perspective = []
1224        self.perspective.append(self.control_panel.window_name)
[aa4b8379]1225       
1226        self.parent.Bind(EVT_PR_FILE, self._on_new_file)
1227       
[f3d51f6]1228        return [self.control_panel]
1229   
[aa4b8379]1230    def _on_new_file(self, evt):
1231        """
1232            Called when the application manager posted an
1233            EVT_PR_FILE event. Just prompt the control
1234            panel to load a new data file.
1235        """
1236        self.control_panel._change_file(None)
1237   
[f3d51f6]1238    def get_perspective(self):
1239        """
1240            Get the list of panel names for this perspective
1241        """
1242        return self.perspective
1243   
1244    def on_perspective(self, event):
1245        """
1246            Call back function for the perspective menu item.
1247            We notify the parent window that the perspective
1248            has changed.
1249        """
1250        self.parent.set_perspective(self.perspective)
1251   
1252    def post_init(self):
1253        """
1254            Post initialization call back to close the loose ends
1255            [Somehow openGL needs this call]
1256        """
[d2ee6f6]1257        if self.standalone==True:
1258            self.parent.set_perspective(self.perspective)
[35adaf6]1259 
1260if __name__ == "__main__":
1261    i = Plugin()
1262    print i.perform_estimateNT()
1263   
1264   
1265   
[660b1e6]1266   
Note: See TracBrowser for help on using the repository browser.