source: sasview/prview/perspectives/pr/pr.py @ 4d5005e

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

prview: standalone mode: pop up error message when file can't be loaded

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