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

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 fc4ab6e was 4a5de6f, checked in by Mathieu Doucet <doucetm@…>, 17 years ago

Modified for Windows executable.

  • Property mode set to 100644
File size: 19.9 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
230            y[i] = value
231           
232        y = y/sum*pr.d_max/len(x)
233        dy = dy/sum*pr.d_max/len(x)
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, plot_id=None):
295        """
296            Get the context menu items available for P(r)
297            @param plot_id: Unique ID of a plot, so that we can recognize those
298                            that we created
299            @return: a list of menu items with call-back function
300        """
301        return [["Compute P(r)", "Compute P(r) from distribution", self._on_context_inversion]]
302   
303
304    def start_thread(self):
305        from pr_thread import CalcPr
306        from copy import deepcopy
307       
308        # If a thread is already started, stop it
309        if self.calc_thread != None and self.calc_thread.isrunning():
310            self.calc_thread.stop()
311               
312        pr = self.pr.clone()
313        self.calc_thread = CalcPr(pr, self.nfunc, error_func=self._thread_error, completefn=self._completed, updatefn=None)
314        self.calc_thread.queue()
315        self.calc_thread.ready(2.5)
316   
317    def _thread_error(self, error):
318        wx.PostEvent(self.parent, StatusEvent(status=error))
319   
320    def _estimate_completed(self, alpha, message, elapsed):
321        """
322            Parameter estimation completed,
323            display the results to the user
324            @param alpha: estimated best alpha
325            @param elapsed: computation time
326        """
327        # Save useful info
328        self.elapsed = elapsed
329        self.control_panel.alpha_estimate = alpha
330        if not message==None:
331            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
332   
333    def _completed(self, out, cov, pr, elapsed):
334        """
335            Method called with the results when the inversion
336            is done
337           
338            @param out: output coefficient for the base functions
339            @param cov: covariance matrix
340            @param pr: Invertor instance
341            @param elapsed: time spent computing
342        """
343        from copy import deepcopy
344        # Save useful info
345        self.elapsed = elapsed
346        message = "Computation completed in %g seconds [chi2=%g]" % (elapsed, pr.chi2)
347        wx.PostEvent(self.parent, StatusEvent(status=message))
348
349        cov = numpy.ascontiguousarray(cov)
350
351        # Show result on control panel
352        self.control_panel.chi2 = pr.chi2
353        self.control_panel.elapsed = elapsed
354        self.control_panel.oscillation = pr.oscillations(out)
355        #print "OSCILL", pr.oscillations(out)
356        print "PEAKS:", pr.get_peaks(out) 
357        self.control_panel.positive = pr.get_positive(out)
358        self.control_panel.pos_err  = pr.get_pos_err(out, cov)
359       
360        for i in range(len(out)):
361            try:
362                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
363            except: 
364                print "%d: %g +- ?" % (i, out[i])       
365       
366        # Make a plot of I(q) data
367        new_plot = Data1D(self.pr.x, self.pr.y, self.pr.err)
368        new_plot.name = "I_{obs}(q)"
369        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
370        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
371        #new_plot.group_id = "test group"
372        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
373               
374        # Show I(q) fit
375        self.show_iq(out, self.pr)
376       
377        # Show P(r) fit
378        x_values, x_range = self.show_pr(out, self.pr, cov) 
379       
380        # Popup result panel
381        #result_panel = InversionResults(self.parent, -1, style=wx.RAISED_BORDER)
382       
383    def setup_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None):
384        self.alpha = alpha
385        self.nfunc = nfunc
386        self.max_length = d_max
387        self.q_min = q_min
388        self.q_max = q_max
389       
390        try:
391            self._create_plot_pr()
392            self.perform_inversion()
393        except:
394            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
395
396    def estimate_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None):
397        self.alpha = alpha
398        self.nfunc = nfunc
399        self.max_length = d_max
400        self.q_min = q_min
401        self.q_max = q_max
402       
403        try:
404            self._create_plot_pr()
405            self.perform_estimate()
406        except:
407            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))           
408
409    def _create_plot_pr(self):
410        """
411            Create and prepare invertor instance from
412            a plottable data set.
413            @param path: path of the file to read in
414        """
415        # Get the data from the chosen data set and perform inversion
416        pr = Invertor()
417        pr.d_max = self.max_length
418        pr.alpha = self.alpha
419        pr.q_min = self.q_min
420        pr.q_max = self.q_max
421        pr.x = self.current_plottable.x
422        pr.y = self.current_plottable.y
423       
424        # Fill in errors if none were provided
425        if self.current_plottable.dy == None:
426            print "no error", self.current_plottable.name
427            y = numpy.zeros(len(pr.y))
428            for i in range(len(pr.y)):
429                y[i] = math.sqrt(pr.y[i])
430            pr.err = y
431        else:
432            pr.err = self.current_plottable.dy
433           
434        self.pr = pr
435
436         
437    def setup_file_inversion(self, alpha, nfunc, d_max, path, q_min=None, q_max=None):
438        self.alpha = alpha
439        self.nfunc = nfunc
440        self.max_length = d_max
441        self.q_min = q_min
442        self.q_max = q_max
443       
444        try:
445            if self._create_file_pr(path):
446                self.perform_inversion()
447        except:
448            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
449         
450    def estimate_file_inversion(self, alpha, nfunc, d_max, path, q_min=None, q_max=None):
451        self.alpha = alpha
452        self.nfunc = nfunc
453        self.max_length = d_max
454        self.q_min = q_min
455        self.q_max = q_max
456       
457        try:
458            if self._create_file_pr(path):
459                self.perform_estimate()
460        except:
461            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
462               
463         
464    def _create_file_pr(self, path):
465        """
466            Create and prepare invertor instance from
467            a file data set.
468            @param path: path of the file to read in
469        """
470        # Load data
471        if os.path.isfile(path):
472            x, y, err = self.load(path)
473           
474            # Get the data from the chosen data set and perform inversion
475            pr = Invertor()
476            pr.d_max = self.max_length
477            pr.alpha = self.alpha
478            pr.q_min = self.q_min
479            pr.q_max = self.q_max
480            pr.x = x
481            pr.y = y
482            pr.err = err
483           
484            self.pr = pr
485            return True
486        return False
487       
488    def perform_estimate(self):
489        from pr_thread import EstimatePr
490        from copy import deepcopy
491       
492        wx.PostEvent(self.parent, StatusEvent(status=''))
493        # If a thread is already started, stop it
494        if self.estimation_thread != None and self.estimation_thread.isrunning():
495            self.estimation_thread.stop()
496               
497        pr = self.pr.clone()
498        self.estimation_thread = EstimatePr(pr, self.nfunc, error_func=self._thread_error, 
499                                            completefn = self._estimate_completed, 
500                                            updatefn   = None)
501        self.estimation_thread.queue()
502        self.estimation_thread.ready(2.5)
503       
504    def perform_inversion(self):
505       
506        # Time estimate
507        #estimated = self.elapsed*self.nfunc**2
508        message = "Computation time may take up to %g seconds" % self.elapsed
509        wx.PostEvent(self.parent, StatusEvent(status=message))
510       
511        # Start inversion thread
512        self.start_thread()
513        return
514       
515        out, cov = self.pr.lstsq(self.nfunc)
516       
517        # Save useful info
518        self.elapsed = self.pr.elapsed
519       
520        for i in range(len(out)):
521            try:
522                print "%d: %g +- %g" % (i, out[i], math.sqrt(math.fabs(cov[i][i])))
523            except: 
524                print "%d: %g +- ?" % (i, out[i])       
525       
526       
527       
528        # Make a plot of I(q) data
529        new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
530        new_plot.name = "I_{obs}(q)"
531        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
532        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
533        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
534               
535        # Show I(q) fit
536        self.show_iq(out, self.pr)
537       
538        # Show P(r) fit
539        x_values, x_range = self.show_pr(out, self.pr, cov=cov)
540       
541       
542         
543         
544    def _on_context_inversion(self, event):
545        panel = event.GetEventObject()
546
547        from inversion_panel import InversionDlg
548       
549        # If we have more than one displayed plot, make the user choose
550        if len(panel.plots)>1:
551            dialog = InversionDlg(None, -1, "P(r) Inversion", panel.plots, pars=False)
552            dialog.set_content(self.last_data, self.nfunc, self.alpha, self.max_length)
553            if dialog.ShowModal() == wx.ID_OK:
554                dataset = dialog.get_content()
555                dialog.Destroy()
556            else:
557                dialog.Destroy()
558                return
559        elif len(panel.plots)==1:
560            dataset = panel.plots.keys()[0]
561        else:
562            print "Error: No data is available"
563            return
564       
565        # Store a reference to the current plottable
566        self.current_plottable = panel.plots[dataset]
567        self.control_panel.plotname = dataset
568        self.control_panel.nfunc = self.nfunc
569        self.control_panel.d_max = self.max_length
570        self.control_panel.alpha = self.alpha
571        self.parent.set_perspective(self.perspective)
572           
573    def get_panels(self, parent):
574        """
575            Create and return a list of panel objects
576        """
577        from inversion_panel import InversionControl
578       
579        self.parent = parent
580        self.control_panel = InversionControl(self.parent, -1, style=wx.RAISED_BORDER)
581        self.control_panel.set_manager(self)
582        self.control_panel.nfunc = self.nfunc
583        self.control_panel.d_max = self.max_length
584        self.control_panel.alpha = self.alpha
585       
586        self.perspective = []
587        self.perspective.append(self.control_panel.window_name)
588        return [self.control_panel]
589   
590    def get_perspective(self):
591        """
592            Get the list of panel names for this perspective
593        """
594        return self.perspective
595   
596    def on_perspective(self, event):
597        """
598            Call back function for the perspective menu item.
599            We notify the parent window that the perspective
600            has changed.
601        """
602        self.parent.set_perspective(self.perspective)
603   
604    def post_init(self):
605        """
606            Post initialization call back to close the loose ends
607            [Somehow openGL needs this call]
608        """
609        self.parent.set_perspective(self.perspective)
610   
Note: See TracBrowser for help on using the repository browser.