source: sasview/prview/perspectives/pr/pr.py @ 694ca1c

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

prview: fixed problem with smeared plot appearing in a new window

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