source: sasview/prview/perspectives/pr/pr.py @ 660b1e6

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

Loaded data is now shown before hitting the inversion button.

  • Property mode set to 100644
File size: 21.3 KB
Line 
1#TODO: Use simview to generate P(r) and I(q) pairs in sansview.
2# Make sure the option of saving each curve is available
3# Use the I(q) curve as input and compare the output to P(r)
4
5import os
6import sys
7import wx
8from sans.guitools.plottables import Data1D, Theory1D
9from sans.guicomm.events import NewPlotEvent, StatusEvent   
10import math, numpy
11from sans.pr.invertor import Invertor
12
13class Plugin:
14   
15    def __init__(self):
16        ## Plug-in name
17        self.sub_menu = "Pr inversion"
18       
19        ## Reference to the parent window
20        self.parent = None
21       
22        ## Simulation window manager
23        self.simview = None
24       
25        ## List of panels for the simulation perspective (names)
26        self.perspective = []
27       
28        ## State data
29        self.alpha      = 0.0001
30        self.nfunc      = 10
31        self.max_length = 140.0
32        self.q_min      = None
33        self.q_max      = None
34        ## Remember last plottable processed
35        self.last_data  = "sphere_60_q0_2.txt"
36        ## Time elapsed for last computation [sec]
37        # Start with a good default
38        self.elapsed = 0.022
39       
40        ## Current invertor
41        self.invertor    = None
42        ## Calculation thread
43        self.calc_thread = None
44        ## Estimation thread
45        self.estimation_thread = None
46        ## Result panel
47        self.control_panel = None
48        ## Currently views plottable
49        self.current_plottable = None
50
51    def populate_menu(self, id, owner):
52        """
53            Create a menu for the plug-in
54        """
55        return []
56        import wx
57        shapes = wx.Menu()
58
59        id = wx.NewId()
60        shapes.Append(id, '&Sphere test')
61        wx.EVT_MENU(owner, id, self._fit_pr)
62       
63        return [(id, shapes, "P(r)")]
64   
65    def help(self, evt):
66        """
67            Show a general help dialog.
68            TODO: replace the text with a nice image
69        """
70        from inversion_panel import HelpDialog
71        dialog = HelpDialog(None, -1)
72        if dialog.ShowModal() == wx.ID_OK:
73            dialog.Destroy()
74        else:
75            dialog.Destroy()
76   
77    def _fit_pr(self, evt):
78        from sans.pr.invertor import Invertor
79        import numpy
80        import pylab
81        import math
82        from sans.guicomm.events import NewPlotEvent           
83        from sans.guitools.plottables import Data1D, Theory1D
84       
85        # Generate P(r) for sphere
86        radius = 60.0
87        d_max  = 2*radius
88       
89       
90        r = pylab.arange(0.01, d_max, d_max/51.0)
91        M = len(r)
92        y = numpy.zeros(M)
93        pr_err = numpy.zeros(M)
94       
95        sum = 0.0
96        for j in range(M):
97            value = self.pr_theory(r[j], radius)
98            sum += value
99            y[j] = value
100            pr_err[j] = math.sqrt(y[j])
101
102           
103        y = y/sum*d_max/len(r)
104
105
106
107        # Perform fit
108        pr = Invertor()
109        pr.d_max = d_max
110        pr.alpha = 0
111        pr.x = r
112        pr.y = y
113        pr.err = pr_err
114        out, cov = pr.pr_fit()
115        for i in range(len(out)):
116            print "%g +- %g" % (out[i], math.sqrt(cov[i][i]))
117
118
119        # Show input P(r)
120        new_plot = Data1D(pr.x, pr.y, pr.err)
121        new_plot.name = "P_{obs}(r)"
122        new_plot.xaxis("\\rm{r}", 'A')
123        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
124        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Pr"))
125
126        # Show P(r) fit
127        self.show_pr(out, pr)
128       
129        # Show I(q) fit
130        q = pylab.arange(0.001, 0.1, 0.01/51.0)
131        self.show_iq(out, pr, q)
132       
133       
134    def show_shpere(self, x, radius=70.0, x_range=70.0):
135        import numpy
136        import pylab
137        import math
138        from sans.guicomm.events import NewPlotEvent           
139        from sans.guitools.plottables import Data1D, Theory1D
140        # Show P(r)
141        y_true = numpy.zeros(len(x))
142
143        sum_true = 0.0
144        for i in range(len(x)):
145            y_true[i] = self.pr_theory(x[i], radius)           
146            sum_true += y_true[i]
147           
148        y_true = y_true/sum_true*x_range/len(x)
149       
150        # Show the theory P(r)
151        new_plot = Theory1D(x, y_true)
152        new_plot.name = "P_{true}(r)"
153        new_plot.xaxis("\\rm{r}", 'A')
154        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
155       
156       
157        #Put this call in plottables/guitools   
158        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Sphere P(r)"))
159       
160    def show_iq(self, out, pr, q=None):
161        import numpy
162        import pylab
163        import math
164        from sans.guicomm.events import NewPlotEvent           
165        from sans.guitools.plottables import Data1D, Theory1D
166
167        qtemp = pr.x
168        if not q==None:
169            qtemp = q
170
171        # Make a plot
172        maxq = -1
173        for q_i in qtemp:
174            if q_i>maxq:
175                maxq=q_i
176               
177        minq = 0.001
178       
179        # Check for user min/max
180        if not pr.q_min==None:
181            minq = pr.q_min
182        if not pr.q_max==None:
183            maxq = pr.q_max
184               
185        x = pylab.arange(minq, maxq, maxq/301.0)
186        y = numpy.zeros(len(x))
187        err = numpy.zeros(len(x))
188        for i in range(len(x)):
189            value = pr.iq(out, x[i])
190            y[i] = value
191            try:
192                err[i] = math.sqrt(math.fabs(value))
193            except:
194                err[i] = 1.0
195                print "Error getting error", value, x[i]
196               
197        new_plot = Theory1D(x, y)
198        new_plot.name = "I_{fit}(q)"
199        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
200        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
201        #new_plot.group_id = "test group"
202        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
203       
204       
205       
206       
207    def show_pr(self, out, pr, cov=None):
208        import numpy
209        import pylab
210        import math
211        from sans.guicomm.events import NewPlotEvent           
212        from sans.guitools.plottables import Data1D, Theory1D
213       
214        # Show P(r)
215        x = pylab.arange(0.0, pr.d_max, pr.d_max/51.0)
216   
217        y = numpy.zeros(len(x))
218        dy = numpy.zeros(len(x))
219        y_true = numpy.zeros(len(x))
220
221        sum = 0.0
222        cov2 = numpy.ascontiguousarray(cov)
223       
224        for i in range(len(x)):
225            if cov2==None:
226                value = pr.pr(out, x[i])
227            else:
228                (value, dy[i]) = pr.pr_err(out, cov2, x[i])
229            sum += value*pr.d_max/len(x)
230            y[i] = value
231           
232        y = y/sum
233        dy = dy/sum
234       
235        if cov2==None:
236            new_plot = Theory1D(x, y)
237        else:
238            new_plot = Data1D(x, y, dy=dy)
239        new_plot.name = "P_{fit}(r)"
240        new_plot.xaxis("\\rm{r}", 'A')
241        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
242           
243        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit"))
244       
245        return x, pr.d_max
246       
247       
248    def choose_file(self):
249        """
250       
251        """
252        #TODO: this should be in a common module
253        return self.parent.choose_file()
254               
255    def load(self, path = "sphere_test_data.txt"):
256        import numpy, math, sys
257        # Read the data from the data file
258        data_x   = numpy.zeros(0)
259        data_y   = numpy.zeros(0)
260        data_err = numpy.zeros(0)
261        if not path == None:
262            input_f = open(path,'r')
263            buff    = input_f.read()
264            lines   = buff.split('\n')
265            for line in lines:
266                try:
267                    toks = line.split()
268                    x = float(toks[0])
269                    y = float(toks[1])
270                    try:
271                        scale = 0.05/math.sqrt(data_x[0])
272                    except:
273                        scale = 1.0
274                    #data_err = numpy.append(data_err, 10.0*math.sqrt(y)+1000.0)
275                    data_x = numpy.append(data_x, x)
276                    data_y = numpy.append(data_y, y)
277                    data_err = numpy.append(data_err, scale*math.sqrt(math.fabs(y)))
278                except:
279                    print "Error reading line: ", line
280                    print sys.exc_value
281                   
282        print "Lines read:", len(data_x)
283        return data_x, data_y, data_err
284       
285    def pr_theory(self, r, R):
286        """
287           
288        """
289        if r<=2*R:
290            return 12.0* ((0.5*r/R)**2) * ((1.0-0.5*r/R)**2) * ( 2.0 + 0.5*r/R )
291        else:
292            return 0.0
293
294    def get_context_menu(self, graph=None):
295        """
296            Get the context menu items available for P(r)
297            @param graph: the Graph object to which we attach the context menu
298            @return: a list of menu items with call-back function
299        """
300        # Look whether this Graph contains P(r) data
301        for item in graph.plottables:
302            if item.name=="P_{fit}(r)":
303               
304                return [["Compute P(r)", "Compute P(r) from distribution", self._on_context_inversion],
305                       ["Add P(r) data", "Load a data file and display it on this plot", self._on_add_data]]
306               
307        return [["Compute P(r)", "Compute P(r) from distribution", self._on_context_inversion]]
308
309    def _on_add_data(self, evt):
310        """
311            Add a data curve to the plot
312            WARNING: this will be removed once guiframe.plotting has its full functionality
313        """
314        path = self.choose_file()
315        if path==None:
316            return
317       
318        x, y, err = self.parent.load_ascii_1D(path)
319       
320        new_plot = Data1D(x, y, dy=err)
321        new_plot.name = "P_{loaded}(r)"
322        new_plot.xaxis("\\rm{r}", 'A')
323        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
324           
325        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit"))
326       
327       
328
329    def start_thread(self):
330        from pr_thread import CalcPr
331        from copy import deepcopy
332       
333        # If a thread is already started, stop it
334        if self.calc_thread != None and self.calc_thread.isrunning():
335            self.calc_thread.stop()
336               
337        pr = self.pr.clone()
338        self.calc_thread = CalcPr(pr, self.nfunc, error_func=self._thread_error, completefn=self._completed, updatefn=None)
339        self.calc_thread.queue()
340        self.calc_thread.ready(2.5)
341   
342    def _thread_error(self, error):
343        wx.PostEvent(self.parent, StatusEvent(status=error))
344   
345    def _estimate_completed(self, alpha, message, elapsed):
346        """
347            Parameter estimation completed,
348            display the results to the user
349            @param alpha: estimated best alpha
350            @param elapsed: computation time
351        """
352        # Save useful info
353        self.elapsed = elapsed
354        self.control_panel.alpha_estimate = alpha
355        if not message==None:
356            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
357   
358    def _completed(self, out, cov, pr, elapsed):
359        """
360            Method called with the results when the inversion
361            is done
362           
363            @param out: output coefficient for the base functions
364            @param cov: covariance matrix
365            @param pr: Invertor instance
366            @param elapsed: time spent computing
367        """
368        from copy import deepcopy
369        # Save useful info
370        self.elapsed = elapsed
371        message = "Computation completed in %g seconds [chi2=%g]" % (elapsed, pr.chi2)
372        wx.PostEvent(self.parent, StatusEvent(status=message))
373
374        cov = numpy.ascontiguousarray(cov)
375
376        # Show result on control panel
377        self.control_panel.chi2 = pr.chi2
378        self.control_panel.elapsed = elapsed
379        self.control_panel.oscillation = pr.oscillations(out)
380        #print "OSCILL", pr.oscillations(out)
381        print "PEAKS:", pr.get_peaks(out) 
382        self.control_panel.positive = pr.get_positive(out)
383        self.control_panel.pos_err  = pr.get_pos_err(out, cov)
384       
385        for i in range(len(out)):
386            try:
387                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
388            except: 
389                print "%d: %g +- ?" % (i, out[i])       
390       
391        # Make a plot of I(q) data
392        new_plot = Data1D(self.pr.x, self.pr.y, self.pr.err)
393        new_plot.name = "I_{obs}(q)"
394        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
395        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
396        #new_plot.group_id = "test group"
397        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
398               
399        # Show I(q) fit
400        self.show_iq(out, self.pr)
401       
402        # Show P(r) fit
403        x_values, x_range = self.show_pr(out, self.pr, cov) 
404       
405        # Popup result panel
406        #result_panel = InversionResults(self.parent, -1, style=wx.RAISED_BORDER)
407       
408    def show_data(self, path=None):
409        if not path==None:
410            self._create_file_pr(path) 
411             
412        # Make a plot of I(q) data
413        new_plot = Data1D(self.pr.x, self.pr.y, self.pr.err)
414        new_plot.name = "I_{obs}(q)"
415        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
416        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
417        #new_plot.group_id = "test group"
418        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
419       
420       
421    def setup_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None):
422        self.alpha = alpha
423        self.nfunc = nfunc
424        self.max_length = d_max
425        self.q_min = q_min
426        self.q_max = q_max
427       
428        try:
429            self._create_plot_pr()
430            self.perform_inversion()
431        except:
432            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
433
434    def estimate_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None):
435        self.alpha = alpha
436        self.nfunc = nfunc
437        self.max_length = d_max
438        self.q_min = q_min
439        self.q_max = q_max
440       
441        try:
442            self._create_plot_pr()
443            self.perform_estimate()
444        except:
445            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))           
446
447    def _create_plot_pr(self):
448        """
449            Create and prepare invertor instance from
450            a plottable data set.
451            @param path: path of the file to read in
452        """
453        # Get the data from the chosen data set and perform inversion
454        pr = Invertor()
455        pr.d_max = self.max_length
456        pr.alpha = self.alpha
457        pr.q_min = self.q_min
458        pr.q_max = self.q_max
459        pr.x = self.current_plottable.x
460        pr.y = self.current_plottable.y
461       
462        # Fill in errors if none were provided
463        if self.current_plottable.dy == None:
464            print "no error", self.current_plottable.name
465            y = numpy.zeros(len(pr.y))
466            for i in range(len(pr.y)):
467                y[i] = math.sqrt(pr.y[i])
468            pr.err = y
469        else:
470            pr.err = self.current_plottable.dy
471           
472        self.pr = pr
473
474         
475    def setup_file_inversion(self, alpha, nfunc, d_max, path, q_min=None, q_max=None):
476        self.alpha = alpha
477        self.nfunc = nfunc
478        self.max_length = d_max
479        self.q_min = q_min
480        self.q_max = q_max
481       
482        try:
483            if self._create_file_pr(path):
484                self.perform_inversion()
485        except:
486            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
487         
488    def estimate_file_inversion(self, alpha, nfunc, d_max, path, q_min=None, q_max=None):
489        self.alpha = alpha
490        self.nfunc = nfunc
491        self.max_length = d_max
492        self.q_min = q_min
493        self.q_max = q_max
494       
495        try:
496            if self._create_file_pr(path):
497                self.perform_estimate()
498        except:
499            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
500               
501         
502    def _create_file_pr(self, path):
503        """
504            Create and prepare invertor instance from
505            a file data set.
506            @param path: path of the file to read in
507        """
508        # Load data
509        if os.path.isfile(path):
510            x, y, err = self.load(path)
511           
512            # Get the data from the chosen data set and perform inversion
513            pr = Invertor()
514            pr.d_max = self.max_length
515            pr.alpha = self.alpha
516            pr.q_min = self.q_min
517            pr.q_max = self.q_max
518            pr.x = x
519            pr.y = y
520            pr.err = err
521           
522            self.pr = pr
523            return True
524        return False
525       
526    def perform_estimate(self):
527        from pr_thread import EstimatePr
528        from copy import deepcopy
529       
530        wx.PostEvent(self.parent, StatusEvent(status=''))
531        # If a thread is already started, stop it
532        if self.estimation_thread != None and self.estimation_thread.isrunning():
533            self.estimation_thread.stop()
534               
535        pr = self.pr.clone()
536        self.estimation_thread = EstimatePr(pr, self.nfunc, error_func=self._thread_error, 
537                                            completefn = self._estimate_completed, 
538                                            updatefn   = None)
539        self.estimation_thread.queue()
540        self.estimation_thread.ready(2.5)
541       
542    def perform_inversion(self):
543       
544        # Time estimate
545        #estimated = self.elapsed*self.nfunc**2
546        message = "Computation time may take up to %g seconds" % self.elapsed
547        wx.PostEvent(self.parent, StatusEvent(status=message))
548       
549        # Start inversion thread
550        self.start_thread()
551        return
552       
553        out, cov = self.pr.lstsq(self.nfunc)
554       
555        # Save useful info
556        self.elapsed = self.pr.elapsed
557       
558        for i in range(len(out)):
559            try:
560                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
561            except: 
562                print "%d: %g +- ?" % (i, out[i])       
563       
564       
565       
566        # Make a plot of I(q) data
567        new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
568        new_plot.name = "I_{obs}(q)"
569        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
570        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
571        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
572               
573        # Show I(q) fit
574        self.show_iq(out, self.pr)
575       
576        # Show P(r) fit
577        x_values, x_range = self.show_pr(out, self.pr, cov=cov)
578       
579       
580         
581    def _on_context_inversion(self, event):
582        panel = event.GetEventObject()
583
584        from inversion_panel import InversionDlg
585       
586        # If we have more than one displayed plot, make the user choose
587        if len(panel.plots)>1:
588            dialog = InversionDlg(None, -1, "P(r) Inversion", panel.plots, pars=False)
589            dialog.set_content(self.last_data, self.nfunc, self.alpha, self.max_length)
590            if dialog.ShowModal() == wx.ID_OK:
591                dataset = dialog.get_content()
592                dialog.Destroy()
593            else:
594                dialog.Destroy()
595                return
596        elif len(panel.plots)==1:
597            dataset = panel.plots.keys()[0]
598        else:
599            print "Error: No data is available"
600            return
601       
602        # Store a reference to the current plottable
603        self.current_plottable = panel.plots[dataset]
604        self.control_panel.plotname = dataset
605        self.control_panel.nfunc = self.nfunc
606        self.control_panel.d_max = self.max_length
607        self.control_panel.alpha = self.alpha
608        self.parent.set_perspective(self.perspective)
609           
610    def get_panels(self, parent):
611        """
612            Create and return a list of panel objects
613        """
614        from inversion_panel import InversionControl
615       
616        self.parent = parent
617        self.control_panel = InversionControl(self.parent, -1, style=wx.RAISED_BORDER)
618        self.control_panel.set_manager(self)
619        self.control_panel.nfunc = self.nfunc
620        self.control_panel.d_max = self.max_length
621        self.control_panel.alpha = self.alpha
622       
623        self.perspective = []
624        self.perspective.append(self.control_panel.window_name)
625        return [self.control_panel]
626   
627    def get_perspective(self):
628        """
629            Get the list of panel names for this perspective
630        """
631        return self.perspective
632   
633    def on_perspective(self, event):
634        """
635            Call back function for the perspective menu item.
636            We notify the parent window that the perspective
637            has changed.
638        """
639        self.parent.set_perspective(self.perspective)
640   
641    def post_init(self):
642        """
643            Post initialization call back to close the loose ends
644            [Somehow openGL needs this call]
645        """
646        self.parent.set_perspective(self.perspective)
647   
Note: See TracBrowser for help on using the repository browser.