Index: prview/src/sans/perspectives/pr/__init__.py
===================================================================
--- prview/src/sans/perspectives/pr/__init__.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/__init__.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,2 @@
+PLUGIN_ID = "P(r) plug-in 1.0"
+from pr import *
Index: prview/src/sans/perspectives/pr/explore_dialog.py
===================================================================
--- prview/src/sans/perspectives/pr/explore_dialog.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/explore_dialog.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,463 @@
+
+################################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#
+#See the license text in license.txt
+#
+#copyright 2009, University of Tennessee
+################################################################################
+
+"""
+Dialog panel to explore the P(r) inversion results for a range
+of D_max value. User picks a number of points and a range of
+distances, then can toggle between inversion outputs and see
+their distribution as a function of D_max.
+"""
+    
+
+import wx
+import numpy
+import math
+import logging
+import sys
+
+# Avoid Matplotlib complaining about the lack of legend on the plot 
+import warnings
+warnings.simplefilter("ignore")
+
+# Import plotting classes
+from danse.common.plottools.PlotPanel import PlotPanel
+from danse.common.plottools import Theory1D as Model1D
+from danse.common.plottools.plottables import Graph
+
+from pr_widgets import PrTextCtrl
+
+# Default number of points on the output plot
+DEFAULT_NPTS = 10
+# Default output parameter to plot
+DEFAULT_OUTPUT = 'Chi2/dof'
+
+class OutputPlot(PlotPanel):
+    """
+    Plot panel used to show the selected results as a function
+    of D_max
+    """
+    def __init__(self, d_min, d_max, parent, id = -1, color = None,\
+        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
+        """
+        Initialization. The parameters added to PlotPanel are:
+        
+        :param d_min: Minimum value of D_max to explore
+        :param d_max: Maximum value of D_max to explore
+        
+        """
+        PlotPanel.__init__(self, parent, id = id, style = style, **kwargs)
+
+        self.parent = parent
+        self.min = d_min
+        self.max = d_max
+        self.npts = DEFAULT_NPTS
+
+        step = (self.max-self.min)/(self.npts-1)
+        self.x = numpy.arange(self.min, self.max+step*0.01, step)
+        dx = numpy.zeros(len(self.x))
+        y = numpy.ones(len(self.x))
+        dy = numpy.zeros(len(self.x))
+ 
+        # Plot area
+        self.plot = Model1D(self.x, y=y, dy=dy)
+        self.plot.name = DEFAULT_OUTPUT
+
+        # Graph        
+        self.graph = Graph()
+        self.graph.xaxis("\\rm{D_{max}}", 'A')
+        self.graph.yaxis("\\rm{%s}" % DEFAULT_OUTPUT,"")
+        self.graph.add(self.plot)
+        self.graph.render(self)
+
+    def onContextMenu(self, event):
+        """
+        Default context menu for the plot panel
+        
+        :TODO: Would be nice to add printing and log/linear scales.
+            The current verison of plottools no longer plays well with 
+            plots outside of guiframe. Guiframe team needs to fix this. 
+        """
+        # Slicer plot popup menu
+        id = wx.NewId()
+        slicerpop = wx.Menu()
+        slicerpop.Append(id, '&Save image', 'Save image as PNG')
+        wx.EVT_MENU(self, id, self.onSaveImage)
+        
+        id = wx.NewId()
+        slicerpop.AppendSeparator()
+        slicerpop.Append(id, '&Reset Graph')
+        wx.EVT_MENU(self, id, self.onResetGraph)
+       
+        pos = event.GetPosition()
+        pos = self.ScreenToClient(pos)
+        self.PopupMenu(slicerpop, pos)
+
+class Results:
+    """
+    Class to hold the inversion output parameters
+    as a function of D_max
+    """
+    def __init__(self):
+        """
+        Initialization. Create empty arrays
+        and dictionary of labels.
+        """
+        # Array of output for each inversion
+        self.chi2 = []
+        self.osc = []
+        self.pos = []
+        self.pos_err = []
+        self.rg = []
+        self.iq0 = []
+        self.bck = []
+        self.d_max = []
+        
+        # Dictionary of outputs
+        self.outputs = {}
+        self.outputs['Chi2/dof'] = ["\chi^2/dof", "a.u.", self.chi2]    
+        self.outputs['Oscillation parameter'] = ["Osc", "a.u.", self.osc]    
+        self.outputs['Positive fraction'] = ["P^+", "a.u.", self.pos]    
+        self.outputs['1-sigma positive fraction'] = ["P^+_{1\ \sigma}", 
+                                                     "a.u.", self.pos_err]    
+        self.outputs['Rg'] = ["R_g", "A", self.rg]    
+        self.outputs['I(q=0)'] = ["I(q=0)", "1/A", self.iq0]    
+        self.outputs['Background'] = ["Bck", "1/A", self.bck] 
+        
+class ExploreDialog(wx.Dialog):
+    """
+    The explorer dialog box. This dialog is meant to be
+    invoked by the InversionControl class.
+    """
+    
+    def __init__(self, pr_state, nfunc, *args, **kwds):
+        """
+        Initialization. The parameters added to Dialog are:
+        
+        :param pr_state: sans.pr.invertor.Invertor object
+        :param nfunc: Number of terms in the expansion
+        
+        """
+        kwds["style"] = wx.RESIZE_BORDER|wx.DEFAULT_DIALOG_STYLE
+        wx.Dialog.__init__(self, *args, **kwds)
+        
+        # Initialize Results object
+        self.results = Results()
+        
+        self.pr_state  = pr_state
+        self._default_min = 0.9*self.pr_state.d_max
+        self._default_max = 1.1*self.pr_state.d_max
+        self.nfunc     = nfunc
+        
+        # Control for number of points
+        self.npts_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                   size=(60,20))
+        # Control for the minimum value of D_max
+        self.dmin_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                   size=(60,20))
+        # Control for the maximum value of D_max
+        self.dmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                   size=(60,20))
+
+        # Output selection box for the y axis
+        self.output_box = None
+
+        # Create the plot object
+        self.plotpanel = OutputPlot(self._default_min, self._default_max,
+                                    self, -1, style=wx.RAISED_BORDER)
+        
+        # Create the layout of the dialog
+        self.__do_layout()
+        self.Fit()
+        
+        # Calculate exploration results
+        self._recalc()
+        # Graph the default output curve
+        self._plot_output()
+        
+    class Event:
+        """
+        Class that holds the content of the form
+        """
+        ## Number of points to be plotted
+        npts   = 0
+        ## Minimum value of D_max
+        dmin   = 0
+        ## Maximum value of D_max
+        dmax   = 0
+                
+    def _get_values(self, event=None):
+        """
+        Invoked when the user changes a value of the form.
+        Check that the values are of the right type.
+        
+        :return: ExploreDialog.Event object if the content is good, 
+            None otherwise
+        """
+        # Flag to make sure that all values are good
+        flag = True
+        
+        # Empty ExploreDialog.Event content
+        content_event = self.Event()
+        
+        # Read each text control and make sure the type is valid
+        # Let the user know if a type is invalid by changing the
+        # background color of the control.
+        try:
+            content_event.npts = int(self.npts_ctl.GetValue())
+            self.npts_ctl.SetBackgroundColour(wx.WHITE)
+            self.npts_ctl.Refresh()
+        except:
+            flag = False
+            self.npts_ctl.SetBackgroundColour("pink")
+            self.npts_ctl.Refresh()
+            
+        try:
+            content_event.dmin = float(self.dmin_ctl.GetValue())
+            self.dmin_ctl.SetBackgroundColour(wx.WHITE)
+            self.dmin_ctl.Refresh()
+        except:
+            flag = False
+            self.dmin_ctl.SetBackgroundColour("pink")
+            self.dmin_ctl.Refresh()
+        
+        try:
+            content_event.dmax = float(self.dmax_ctl.GetValue())
+            self.dmax_ctl.SetBackgroundColour(wx.WHITE)
+            self.dmax_ctl.Refresh()
+        except:
+            flag = False
+            self.dmax_ctl.SetBackgroundColour("pink")
+            self.dmax_ctl.Refresh()
+        
+        # If the content of the form is valid, return the content,
+        # otherwise return None
+        if flag:
+            if event is not None:
+                event.Skip(True)
+            return content_event
+        else:
+            return None
+    
+    def _plot_output(self, event=None):
+        """
+        Invoked when a new output type is selected for plotting, 
+        or when a new computation is finished.
+        """
+        # Get the output type selection
+        output_type = self.output_box.GetString(self.output_box.GetSelection())
+        
+        # If the selected output type is part of the results ojbect,
+        # display the results. 
+        # Note: by design, the output type should always be part of the
+        #       results object.
+        if self.results.outputs.has_key(output_type): 
+            self.plotpanel.plot.x = self.results.d_max
+            self.plotpanel.plot.y = self.results.outputs[output_type][2]
+            self.plotpanel.plot.name = '_nolegend_'
+            y_label = "\\rm{%s}" % self.results.outputs[output_type][0]
+            self.plotpanel.graph.yaxis(y_label,
+                                       self.results.outputs[output_type][1])
+            
+            # Redraw
+            self.plotpanel.graph.render(self.plotpanel)
+            self.plotpanel.subplot.figure.canvas.draw_idle()
+        else:
+            msg =  "ExploreDialog: the Results object's dictionary "
+            msg += "does not contain "
+            msg += "the [%s] output type. This must be indicative of "
+            msg += "a change in the " % str(output_type)
+            msg += "ExploreDialog code."
+            logging.error(msg)
+        
+    def __do_layout(self):
+        """
+        Do the layout of the dialog
+        """
+        # Dialog box properties
+        self.SetTitle("D_max Explorer")
+        self.SetSize((600, 595))
+        
+        sizer_main = wx.BoxSizer(wx.VERTICAL)
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        sizer_params = wx.GridBagSizer(5, 5)
+
+        iy = 0
+        ix = 0
+        label_npts  = wx.StaticText(self, -1, "Npts")
+        sizer_params.Add(label_npts, (iy, ix), (1, 1), 
+                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_params.Add(self.npts_ctl,   (iy, ix), (1, 1), 
+                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        self.npts_ctl.SetValue("%g" % DEFAULT_NPTS)
+        self.npts_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
+        
+        ix += 1
+        label_dmin   = wx.StaticText(self, -1, "Min Distance [A]")
+        sizer_params.Add(label_dmin, (iy, ix), (1, 1),
+                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_params.Add(self.dmin_ctl, (iy, ix), (1, 1),
+                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        self.dmin_ctl.SetValue(str(self._default_min))
+        self.dmin_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
+        
+        ix += 1
+        label_dmax = wx.StaticText(self, -1, "Max Distance [A]")
+        sizer_params.Add(label_dmax, (iy, ix), (1, 1),
+                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        ix += 1
+        sizer_params.Add(self.dmax_ctl,   (iy, ix), (1, 1),
+                         wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        self.dmax_ctl.SetValue(str(self._default_max))
+        self.dmax_ctl.Bind(wx.EVT_KILL_FOCUS, self._recalc)
+
+
+        # Ouput selection box
+        selection_msg = wx.StaticText(self, -1, "Select a dependent variable:")
+        self.output_box = wx.ComboBox(self, -1)
+        for item in self.results.outputs.keys():
+            self.output_box.Append(item, "")
+        self.output_box.SetStringSelection(DEFAULT_OUTPUT)
+        
+        output_sizer = wx.GridBagSizer(5, 5)
+        output_sizer.Add(selection_msg, (0, 0), (1, 1),
+                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
+        output_sizer.Add(self.output_box, (0, 1), (1, 2),
+                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 10)
+        
+        wx.EVT_COMBOBOX(self.output_box, -1, self._plot_output) 
+        sizer_main.Add(output_sizer, 0, wx.EXPAND | wx.ALL, 10)
+        
+        sizer_main.Add(self.plotpanel, 0, wx.EXPAND | wx.ALL, 10)
+        sizer_main.SetItemMinSize(self.plotpanel, 400, 400)
+        
+        sizer_main.Add(sizer_params, 0, wx.EXPAND|wx.ALL, 10)
+        static_line_3 = wx.StaticLine(self, -1)
+        sizer_main.Add(static_line_3, 0, wx.EXPAND, 0)
+        
+        # Bottom area with the close button
+        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        button_OK = wx.Button(self, wx.ID_OK, "Close")
+        sizer_button.Add(button_OK, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
+        
+        sizer_main.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
+        self.SetAutoLayout(True)
+        self.SetSizer(sizer_main)
+        self.Layout()
+        self.Centre()
+        
+        # Bind the Enter key to recalculation
+        self.Bind(wx.EVT_TEXT_ENTER, self._recalc)
+
+    def _recalc(self, event=None):
+        """
+        Invoked when the user changed a value on the form.
+        Process the form and compute the output to be plottted.
+        """
+        # Get the content of the form
+        content = self._get_values()
+        # If the content of the form is invalid, return and do nothing
+        if content is None:
+            return
+        
+        # Results object to store the computation outputs.
+        results = Results()
+        
+        # Loop over d_max values
+        for i in range(content.npts): 
+            temp = (content.dmax - content.dmin)/(content.npts - 1.0)   
+            d = content.dmin + i * temp
+                
+            self.pr_state.d_max = d
+            try:
+                out, cov = self.pr_state.invert(self.nfunc)   
+            
+                # Store results
+                iq0 = self.pr_state.iq0(out)
+                rg = self.pr_state.rg(out)
+                pos = self.pr_state.get_positive(out)
+                pos_err = self.pr_state.get_pos_err(out, cov)
+                osc = self.pr_state.oscillations(out)
+            
+                results.d_max.append(self.pr_state.d_max)
+                results.bck.append(self.pr_state.background)
+                results.chi2.append(self.pr_state.chi2)
+                results.iq0.append(iq0)
+                results.rg.append(rg)
+                results.pos.append(pos)
+                results.pos_err.append(pos_err)
+                results.osc.append(osc)           
+            except:
+                # This inversion failed, skip this D_max value
+                msg = "ExploreDialog: inversion failed "
+                msg += "for D_max=%s\n%s" % (str(d), sys.exc_value)
+                logging.error(msg)
+            
+        self.results = results            
+         
+        # Plot the selected output
+        self._plot_output()   
+            
+##### testing code ############################################################
+"""
+Example: ::
+
+class MyApp(wx.App):
+    
+    #Test application used to invoke the ExploreDialog for testing
+   
+    def OnInit(self):
+        from inversion_state import Reader
+        from sans.pr.invertor import Invertor
+        wx.InitAllImageHandlers()
+        
+        def call_back(state, datainfo=None):
+            
+            #Dummy call-back method used by the P(r) 
+            #file reader.
+            
+            print state
+            
+        # Load test data
+        # This would normally be loaded by the application 
+        # of the P(r) plug-in.
+        r = Reader(call_back)
+        data = r.read("test_pr_data.prv")
+        pr_state = data.meta_data['prstate']
+        
+        # Create Invertor object for the data
+        # This would normally be assembled by the P(r) plug-in
+        pr = Invertor()
+        pr.d_max = pr_state.d_max
+        pr.alpha = pr_state.alpha
+        pr.q_min = pr_state.qmin
+        pr.q_max = pr_state.qmax
+        pr.x = data.x
+        pr.y = data.y
+        pr.err = data.y*0.1
+        pr.has_bck = False
+        pr.slit_height = pr_state.height
+        pr.slit_width = pr_state.width
+        
+        # Create the dialog
+        dialog = ExploreDialog(pr, 10, None, -1, "")
+        self.SetTopWindow(dialog)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+        return 1
+
+if __name__ == "__main__":
+    app = MyApp(0)
+    app.MainLoop()
+    
+""" 
Index: prview/src/sans/perspectives/pr/inversion_panel.py
===================================================================
--- prview/src/sans/perspectives/pr/inversion_panel.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/inversion_panel.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,1089 @@
+#!/usr/bin/env python
+
+# version
+__id__ = "$Id: aboutdialog.py 1193 2007-05-03 17:29:59Z dmitriy $"
+__revision__ = "$Revision: 1193 $"
+
+import wx
+import os
+import sys
+import logging
+from wx.lib.scrolledpanel import ScrolledPanel
+from sans.guiframe.events import StatusEvent   
+from sans.guiframe.panel_base import PanelBase 
+from inversion_state import InversionState
+from pr_widgets import PrTextCtrl
+from pr_widgets import DataFileTextCtrl
+from pr_widgets import OutputTextCtrl
+
+if sys.platform.count("darwin")==0:
+    FONT_VARIANT = 0
+else:
+    FONT_VARIANT = 1
+
+class InversionControl(ScrolledPanel, PanelBase):
+    """
+    """
+    window_name = 'pr_control'
+    window_caption = "P(r) control panel"
+    CENTER_PANE = True
+    
+    # Figure of merit parameters [default]
+    
+    ## Oscillation parameters (sin function = 1.1)
+    oscillation_max = 1.5
+    
+    def __init__(self, parent, id=-1, plots=None, 
+                 standalone=False, **kwargs):
+        """
+        """
+        ScrolledPanel.__init__(self, parent, id=id, **kwargs)
+        PanelBase.__init__(self, parent)
+        self.SetupScrolling()
+        #Set window's font size 
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        
+        self.plots = plots
+        self.radio_buttons = {}
+        self.parent = parent
+        
+        ## Data file TextCtrl
+        self.data_file  = None
+        self.plot_data  = None
+        self.nfunc_ctl  = None
+        self.alpha_ctl  = None
+        self.dmax_ctl   = None
+        self.time_ctl   = None
+        self.chi2_ctl   = None
+        self.osc_ctl    = None
+        self.file_radio = None
+        self.plot_radio = None
+        self.label_sugg = None
+        self.qmin_ctl   = None
+        self.qmax_ctl   = None
+        self.swidth_ctl = None
+        self.sheight_ctl = None
+        
+        self.rg_ctl     = None
+        self.iq0_ctl    = None
+        self.bck_chk    = None
+        self.bck_ctl    = None
+        
+        # TextCtrl for fraction of positive P(r)
+        self.pos_ctl = None
+        
+        # TextCtrl for fraction of 1 sigma positive P(r)
+        self.pos_err_ctl = None 
+        
+        ## Estimates
+        self.alpha_estimate_ctl = None
+        self.nterms_estimate_ctl = None
+        ## D_max distance explorator
+        self.distance_explorator_ctl = None
+        ## Data manager
+        self._manager   = None
+        ## Standalone flage
+        self.standalone = standalone
+        ## Default file location for save
+        self._default_save_location = os.getcwd()
+        # Default width
+        self._default_width = 350
+        self._do_layout()
+        
+    def __setattr__(self, name, value):
+        """
+        Allow direct hooks to text boxes
+        """
+        if name == 'nfunc':
+            self.nfunc_ctl.SetValue(str(int(value)))
+        elif name == 'd_max':
+            self.dmax_ctl.SetValue(str(value))
+        elif name == 'alpha':
+            self.alpha_ctl.SetValue(str(value))
+        elif name == 'chi2':
+            self.chi2_ctl.SetValue("%-5.2g" % value)
+        elif name == 'bck':
+            self.bck_ctl.SetValue("%-5.2g" % value)
+        elif name == 'q_min':
+            self.qmin_ctl.SetValue("%-5.2g" % value)
+        elif name == 'q_max':
+            self.qmax_ctl.SetValue("%-5.2g" % value)
+        elif name == 'elapsed':
+            self.time_ctl.SetValue("%-5.2g" % value)
+        elif name =='rg':
+            self.rg_ctl.SetValue("%-5.2g" % value)
+        elif name == 'iq0':
+            self.iq0_ctl.SetValue("%-5.2g" % value)
+        elif name == 'oscillation':
+            self.osc_ctl.SetValue("%-5.2g" % value)
+        elif name == 'slit_width':
+            self.swidth_ctl.SetValue("%-5.2g" % value)
+        elif name == 'slit_height':
+            self.sheight_ctl.SetValue("%-5.2g" % value)
+        elif name == 'positive':
+            self.pos_ctl.SetValue("%-5.2g" % value)
+        elif name == 'pos_err':
+            self.pos_err_ctl.SetValue("%-5.2g" % value)
+        elif name == 'alpha_estimate':
+            self.alpha_estimate_ctl.SetToolTipString("Click to accept value.")
+            self.alpha_estimate_ctl.Enable(True)
+            self.alpha_estimate_ctl.SetLabel("%-3.1g" % value)
+            #self.alpha_estimate_ctl.Show()
+            #self.label_sugg.Show()
+        elif name == 'nterms_estimate':
+            self.nterms_estimate_ctl.SetToolTipString("Click to accept value.")
+            self.nterms_estimate_ctl.Enable(True)
+            self.nterms_estimate_ctl.SetLabel("%-g" % value)
+        elif name == 'plotname':
+            self.plot_data.SetValue(str(value))
+            self._on_pars_changed(None)
+        elif name == 'datafile':
+            self.plot_data.SetValue(str(value))
+            self._on_pars_changed(None)
+        else:
+            wx.Panel.__setattr__(self, name, value)
+        
+    def __getattr__(self, name):
+        """
+        Allow direct hooks to text boxes
+        """
+        if name == 'nfunc':
+            try:
+                return int(self.nfunc_ctl.GetValue())
+            except:
+                return -1
+        elif name == 'd_max':
+            try:
+                return self.dmax_ctl.GetValue()
+            except:
+                return -1.0
+        elif name == 'alpha':
+            try:
+                return self.alpha_ctl.GetValue()
+            except:
+                return -1.0
+        elif name == 'chi2':
+            try:
+                return float(self.chi2_ctl.GetValue())
+            except:
+                return None
+        elif name == 'bck':
+            try:
+                return float(self.bck_ctl.GetValue())
+            except:
+                return None
+        elif name == 'q_min':
+            try:
+                return float(self.qmin_ctl.GetValue())
+            except:
+                return 0.0
+        elif name=='q_max':
+            try:
+                return float(self.qmax_ctl.GetValue())
+            except:
+                return 0.0
+        elif name == 'elapsed':
+            try:
+                return float(self.time_ctl.GetValue())
+            except:
+                return None
+        elif name == 'rg':
+            try:
+                return float(self.rg_ctl.GetValue())
+            except:
+                return None
+        elif name=='iq0':
+            try:
+                return float(self.iq0_ctl.GetValue())
+            except:
+                return None
+        elif name == 'oscillation':
+            try:
+                return float(self.osc_ctl.GetValue())
+            except:
+                return None
+        elif name == 'slit_width':
+            try:
+                return float(self.swidth_ctl.GetValue())
+            except:
+                return None
+        elif name=='slit_height':
+            try:
+                return float(self.sheight_ctl.GetValue())
+            except:
+                return None
+        elif name == 'pos':
+            try:
+                return float(self.pos_ctl.GetValue())
+            except:
+                return None
+        elif name == 'pos_err':
+            try:
+                return float(self.pos_err_ctl.GetValue())
+            except:
+                return None
+        elif name == 'alpha_estimate':
+            try:
+                return float(self.alpha_estimate_ctl.GetLabel())
+            except:
+                return None
+        elif name == 'nterms_estimate':
+            try:
+                return int(self.nterms_estimate_ctl.GetLabel())
+            except:
+                return None
+        elif name == 'plotname':
+            return self.plot_data.GetValue()
+        elif name == 'datafile':
+            return self.plot_data.GetValue()
+        else:
+            return wx.Panel.__getattribute__(self, name)
+        
+    def save_project(self, doc=None):
+        """
+        return an xml node containing state of the panel
+         that guiframe can write to file
+        """
+        data = self.get_data()
+        state = self.get_state()
+        if data is not None:
+            new_doc = self._manager.state_reader.write_toXML(data, state)
+            if new_doc is not None:
+                if doc is not None and hasattr(doc, "firstChild"):
+                    child = new_doc.firstChild.firstChild
+                    doc.firstChild.appendChild(child)  
+                else:
+                    doc = new_doc
+        return doc   
+    
+    def on_save(self, evt=None):
+        """
+        Method used to create a memento of the current state
+            
+        :return: state object 
+        """
+        # Ask the user the location of the file to write to.
+        path = None
+        dlg = wx.FileDialog(self, "Choose a file",
+                            self._default_save_location, "", "*.prv", wx.SAVE)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            self._default_save_location = os.path.dirname(path)
+        else:
+            return None
+        
+        dlg.Destroy()
+        
+        state = self.get_state()
+        
+        # MAC always needs the extension for saving
+        extens = ".prv"
+        # Make sure the ext included in the file name
+        fName = os.path.splitext(path)[0] + extens
+        self._manager.save_data(filepath=fName, prstate=state)
+        
+        return state
+    
+    def get_data(self):
+        """
+        """
+        return self._manager.get_data()
+    
+    def get_state(self):
+        """
+        Get the current state
+        
+        : return: state object
+        """
+        # Construct the state object    
+        state = InversionState()
+        
+        # Read the panel's parameters
+        flag, alpha, dmax, nfunc, qmin, \
+        qmax, height, width = self._read_pars()
+        
+        state.nfunc = nfunc
+        state.d_max = dmax
+        state.alpha = alpha
+        state.qmin  = qmin
+        state.qmax  = qmax
+        state.width = width
+        state.height = height
+        
+        # Data file
+        state.file = self.plot_data.GetValue()
+        
+        # Background evaluation checkbox
+        state.estimate_bck = self.bck_chk.IsChecked()
+        
+        # Estimates
+        state.nterms_estimate = self.nterms_estimate
+        state.alpha_estimate = self.alpha_estimate
+        
+        # Read the output values
+        state.chi2    = self.chi2
+        state.elapsed = self.elapsed
+        state.osc     = self.oscillation
+        state.pos     = self.pos
+        state.pos_err = self.pos_err
+        state.rg      = self.rg
+        state.iq0     = self.iq0
+        state.bck     = self.bck
+        
+        return state
+    
+    def set_state(self, state):
+        """
+        Set the state of the panel and inversion problem to
+        the state passed as a parameter.
+        Execute the inversion immediately after filling the 
+        controls.
+        
+        :param state: InversionState object
+        """
+        if state.nfunc is not None:
+            self.nfunc = state.nfunc
+        if state.d_max is not None:
+            self.d_max = state.d_max
+        if state.alpha is not None:
+            self.alpha = state.alpha
+        if state.qmin is not None:
+            self.q_min  = state.qmin
+        if state.qmax is not None:
+            self.q_max  = state.qmax
+        if state.width is not None:
+            self.slit_width = state.width
+        if state.height is not None:
+            self.slit_height = state.height
+        
+        # Data file
+        self.plot_data.SetValue(str(state.file))
+    
+        # Background evaluation checkbox
+        self.bck_chk.SetValue(state.estimate_bck)
+        
+        # Estimates
+        if state.nterms_estimate is not None:
+            self.nterms_estimate = state.nterms_estimate
+        if state.alpha_estimate is not None: 
+            self.alpha_estimate = state.alpha_estimate 
+    
+        
+        # Read the output values
+        if state.chi2 is not None:
+            self.chi2    = state.chi2
+        if state.elapsed is not None:
+            self.elapsed = state.elapsed
+        if state.osc is not None:
+            self.oscillation = state.osc
+        if state.pos is not None:
+            self.positive = state.pos
+        if state.pos_err is not None:
+            self.pos_err = state.pos_err
+        if state.rg is not None:
+            self.rg      = state.rg
+        if state.iq0 is not None:
+            self.iq0     = state.iq0
+        if state.bck is not None:
+            self.bck     = state.bck
+
+        # Perform inversion
+        self._on_invert(None)    
+        
+    def set_manager(self, manager):
+        self._manager = manager
+        
+    def _do_layout(self):
+        vbox = wx.GridBagSizer(0,0)
+        iy_vb = 0
+
+        # ----- I(q) data -----
+        databox = wx.StaticBox(self, -1, "I(q) data source")
+        
+        boxsizer1 = wx.StaticBoxSizer(databox, wx.VERTICAL)
+        boxsizer1.SetMinSize((self._default_width, 50))
+        pars_sizer = wx.GridBagSizer(5, 5)
+
+        iy = 0
+        self.file_radio = wx.StaticText(self, -1, "Name:")
+        pars_sizer.Add(self.file_radio, (iy, 0), (1, 1),
+                       wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        
+        self.plot_data = DataFileTextCtrl(self, -1, size=(260,20))
+        
+        pars_sizer.Add(self.plot_data, (iy, 1), (1, 1),
+                       wx.EXPAND|wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 15)
+        
+        self.bck_chk = wx.CheckBox(self, -1, "Estimate background level")
+        hint_msg = "Check box to let the fit estimate "
+        hint_msg += "the constant background level."
+        self.bck_chk.SetToolTipString(hint_msg)
+        self.bck_chk.Bind(wx.EVT_CHECKBOX, self._on_pars_changed)
+        iy += 1
+        pars_sizer.Add(self.bck_chk, (iy, 0), (1, 2),
+                       wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        boxsizer1.Add(pars_sizer, 0, wx.EXPAND)  
+        vbox.Add(boxsizer1, (iy_vb, 0), (1, 1),
+                 wx.LEFT|wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE|wx.TOP, 5)
+        
+        # ----- Add slit parameters -----
+        if True:
+            sbox = wx.StaticBox(self, -1, "Slit parameters")
+            sboxsizer = wx.StaticBoxSizer(sbox, wx.VERTICAL)
+            sboxsizer.SetMinSize((self._default_width, 20))
+            
+            sizer_slit = wx.GridBagSizer(5, 5)
+    
+            label_sheight = wx.StaticText(self, -1, "Height", size=(40, 20))
+            label_swidth = wx.StaticText(self, -1, "Width", size=(40, 20))
+            #label_sunits1 = wx.StaticText(self, -1, "[A^(-1)]")
+            label_sunits2 = wx.StaticText(self, -1, "[A^(-1)]", size=(55,20))
+            self.sheight_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                          size=(60,20))
+            self.swidth_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                         size=(60,20))
+            hint_msg = "Enter slit height in units of Q or leave blank."
+            self.sheight_ctl.SetToolTipString(hint_msg)
+            hint_msg = "Enter slit width in units of Q or leave blank."
+            self.swidth_ctl.SetToolTipString(hint_msg)
+            #self.sheight_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
+            #self.swidth_ctl.Bind(wx.EVT_TEXT,  self._on_pars_changed)
+            
+            iy = 0
+            sizer_slit.Add(label_sheight,    (iy, 0), (1, 1),
+                           wx.LEFT|wx.EXPAND, 5)
+            sizer_slit.Add(self.sheight_ctl, (iy, 1), (1, 1),
+                           wx.LEFT|wx.EXPAND, 5)
+    
+            sizer_slit.Add(label_swidth,     (iy, 2), (1, 1),
+                           wx.LEFT|wx.EXPAND, 5)
+            sizer_slit.Add(self.swidth_ctl,  (iy, 3), (1, 1),
+                           wx.LEFT|wx.EXPAND, 5)
+            sizer_slit.Add(label_sunits2,    (iy, 4), (1, 1),
+                           wx.LEFT|wx.EXPAND, 5)
+            
+            sboxsizer.Add(sizer_slit, wx.TOP, 15)
+            iy_vb += 1
+            vbox.Add(sboxsizer, (iy_vb, 0), (1, 1),
+                     wx.LEFT|wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 5)
+        
+        
+        # ----- Q range -----
+        qbox = wx.StaticBox(self, -1, "Q range")
+        qboxsizer = wx.StaticBoxSizer(qbox, wx.VERTICAL)
+        qboxsizer.SetMinSize((self._default_width, 20))
+        
+        sizer_q = wx.GridBagSizer(5, 5)
+
+        label_qmin = wx.StaticText(self, -1, "Q min", size=(40, 20))
+        label_qmax = wx.StaticText(self, -1, "Q max", size=(40, 20))
+        #label_qunits1 = wx.StaticText(self, -1, "[A^(-1)]")
+        label_qunits2 = wx.StaticText(self, -1, "[A^(-1)]", size=(55, 20))
+        self.qmin_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                   size=(60,20))
+        self.qmax_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                   size=(60,20))
+        hint_msg = "Select a lower bound for Q or leave blank."
+        self.qmin_ctl.SetToolTipString(hint_msg)
+        hint_msg = "Select an upper bound for Q or leave blank."
+        self.qmax_ctl.SetToolTipString(hint_msg)
+        self.qmin_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
+        self.qmax_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
+        
+        iy = 0
+        sizer_q.Add(label_qmin,    (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 5)
+        sizer_q.Add(self.qmin_ctl, (iy, 1), (1, 1), wx.LEFT|wx.EXPAND, 5)
+        #sizer_q.Add(label_qunits1, (iy,2), (1,1), wx.LEFT|wx.EXPAND, 15)
+        sizer_q.Add(label_qmax,    (iy, 2), (1, 1), wx.LEFT|wx.EXPAND, 5)
+        sizer_q.Add(self.qmax_ctl, (iy, 3), (1, 1), wx.LEFT|wx.EXPAND, 5)
+        sizer_q.Add(label_qunits2, (iy, 4), (1, 1), wx.LEFT|wx.EXPAND, 5)
+        qboxsizer.Add(sizer_q, wx.TOP, 15)
+
+        iy_vb += 1
+        vbox.Add(qboxsizer, (iy_vb,0), (1,1),
+                 wx.LEFT|wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 5)
+    
+        # ----- Parameters -----
+        parsbox = wx.StaticBox(self, -1, "Parameters")
+        boxsizer2 = wx.StaticBoxSizer(parsbox, wx.VERTICAL)
+        boxsizer2.SetMinSize((self._default_width,50))
+        
+        explanation  = "P(r) is found by fitting a set of base functions"
+        explanation += " to I(Q). The minimization involves"
+        explanation += " a regularization term to ensure a smooth P(r)."
+        explanation += " The regularization constant gives the size of that "  
+        explanation += "term. The suggested value is the value above which the"
+        explanation += " output P(r) will have only one peak."
+        label_explain = wx.StaticText(self, -1, explanation, size=(280,90))
+        boxsizer2.Add(label_explain,  wx.LEFT|wx.BOTTOM, 5)
+        
+        
+        
+        label_nfunc = wx.StaticText(self, -1, "Number of terms")
+        label_nfunc.SetMinSize((120, 20))
+        label_alpha = wx.StaticText(self, -1, "Regularization constant")
+        label_dmax  = wx.StaticText(self, -1, "Max distance [A]")
+        self.label_sugg  = wx.StaticText(self, -1, "Suggested value")
+        #self.label_sugg.Hide()
+        
+        self.nfunc_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                    size=(60,20))
+        self.nfunc_ctl.SetToolTipString("Number of terms in the expansion.")
+        self.alpha_ctl = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                    size=(60,20))
+        hint_msg = "Control parameter for the size of the regularization term."
+        self.alpha_ctl.SetToolTipString(hint_msg)
+        self.dmax_ctl  = PrTextCtrl(self, -1, style=wx.TE_PROCESS_ENTER,
+                                    size=(60,20))
+        hint_msg = "Maximum distance between any two points in the system."
+        self.dmax_ctl.SetToolTipString(hint_msg)
+        id = wx.NewId()
+        self.alpha_estimate_ctl  = wx.Button(self, id, "")
+        #self.alpha_estimate_ctl.Hide()
+        self.Bind(wx.EVT_BUTTON, self._on_accept_alpha, id = id)   
+        self.alpha_estimate_ctl.Enable(False)
+        #self.alpha_estimate_ctl.SetBackgroundColour('#ffdf85')
+        #self.alpha_estimate_ctl.SetBackgroundColour(self.GetBackgroundColour())
+        self.alpha_estimate_ctl.SetToolTipString("Waiting for estimate...")
+        
+        id = wx.NewId()
+        self.nterms_estimate_ctl  = wx.Button(self, id, "")
+        #self.nterms_estimate_ctl.Hide()
+        self.Bind(wx.EVT_BUTTON, self._on_accept_nterms, id = id)   
+        self.nterms_estimate_ctl.Enable(False)
+      
+        self.nterms_estimate_ctl.SetToolTipString("Waiting for estimate...")
+        
+        self.nfunc_ctl.Bind(wx.EVT_TEXT, self._read_pars)
+        self.alpha_ctl.Bind(wx.EVT_TEXT, self._read_pars)
+        self.dmax_ctl.Bind(wx.EVT_TEXT, self._on_pars_changed)
+        
+        # Distance explorator
+        id = wx.NewId()
+        self.distance_explorator_ctl = wx.Button(self, id, "Explore")
+        self.Bind(wx.EVT_BUTTON, self._on_explore, id = id)           
+        
+        
+        sizer_params = wx.GridBagSizer(5,5)
+
+        iy = 0
+        sizer_params.Add(self.label_sugg, (iy, 2), (1, 1), wx.LEFT, 15)
+        iy += 1
+        sizer_params.Add(label_nfunc,      (iy, 0), (1, 1), wx.LEFT, 15)
+        sizer_params.Add(self.nfunc_ctl,   (iy, 1), (1, 1), wx.RIGHT, 0)
+        sizer_params.Add(self.nterms_estimate_ctl, (iy, 2), (1, 1), wx.LEFT, 15)
+        iy += 1
+        sizer_params.Add(label_alpha,      (iy, 0), (1, 1), wx.LEFT, 15)
+        sizer_params.Add(self.alpha_ctl,   (iy, 1), (1, 1), wx.RIGHT, 0)
+        sizer_params.Add(self.alpha_estimate_ctl, (iy, 2), (1, 1), wx.LEFT, 15)
+        iy += 1
+        sizer_params.Add(label_dmax, (iy, 0), (1, 1), wx.LEFT, 15)
+        sizer_params.Add(self.dmax_ctl,   (iy, 1), (1, 1), wx.RIGHT, 0)
+        sizer_params.Add(self.distance_explorator_ctl, (iy, 2),
+                         (1, 1), wx.LEFT, 15)
+
+        boxsizer2.Add(sizer_params, 0)
+        
+        iy_vb += 1
+        vbox.Add(boxsizer2, (iy_vb, 0), (1, 1),
+                 wx.LEFT|wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 5)
+
+
+        # ----- Results -----
+        resbox = wx.StaticBox(self, -1, "Outputs")
+        ressizer = wx.StaticBoxSizer(resbox, wx.VERTICAL)
+        ressizer.SetMinSize((self._default_width, 50))
+        
+        label_rg       = wx.StaticText(self, -1, "Rg")
+        label_rg_unit  = wx.StaticText(self, -1, "[A]")
+        label_iq0      = wx.StaticText(self, -1, "I(Q=0)")
+        label_iq0_unit = wx.StaticText(self, -1, "[A^(-1)]")
+        label_bck      = wx.StaticText(self, -1, "Background")
+        label_bck_unit = wx.StaticText(self, -1, "[A^(-1)]")
+        self.rg_ctl    = OutputTextCtrl(self, -1, size=(60,20))
+        hint_msg = "Radius of gyration for the computed P(r)."
+        self.rg_ctl.SetToolTipString(hint_msg)
+        self.iq0_ctl   = OutputTextCtrl(self, -1, size=(60, 20))
+        hint_msg = "Scattering intensity at Q=0 for the computed P(r)."
+        self.iq0_ctl.SetToolTipString(hint_msg)
+        self.bck_ctl   = OutputTextCtrl(self, -1, size=(60, 20))
+        self.bck_ctl.SetToolTipString("Value of estimated constant background.")
+        
+        label_time = wx.StaticText(self, -1, "Computation time")
+        label_time_unit = wx.StaticText(self, -1, "secs")
+        label_time.SetMinSize((120,20))
+        label_chi2 = wx.StaticText(self, -1, "Chi2/dof")
+        label_osc = wx.StaticText(self, -1, "Oscillations")
+        label_pos = wx.StaticText(self, -1, "Positive fraction")
+        label_pos_err = wx.StaticText(self, -1, "1-sigma positive fraction")
+        
+        self.time_ctl = OutputTextCtrl(self, -1, size=(60, 20))
+        hint_msg = "Computation time for the last inversion, in seconds."
+        self.time_ctl.SetToolTipString(hint_msg)
+        
+        self.chi2_ctl = OutputTextCtrl(self, -1, size=(60, 20))
+        self.chi2_ctl.SetToolTipString("Chi^2 over degrees of freedom.")
+        
+        # Oscillation parameter
+        self.osc_ctl = OutputTextCtrl(self, -1, size=(60, 20))
+        hint_msg = "Oscillation parameter. P(r) for a sphere has an "
+        hint_msg += " oscillation parameter of 1.1."
+        self.osc_ctl.SetToolTipString(hint_msg)
+        
+        # Positive fraction figure of merit
+        self.pos_ctl = OutputTextCtrl(self, -1, size=(60, 20))
+        hint_msg = "Fraction of P(r) that is positive. "
+        hint_msg += "Theoretically, P(r) is defined positive."
+        self.pos_ctl.SetToolTipString(hint_msg)
+        
+        # 1-simga positive fraction figure of merit
+        self.pos_err_ctl = OutputTextCtrl(self, -1, size=(60, 20))
+        message  = "Fraction of P(r) that is at least 1 standard deviation"
+        message += " greater than zero.\n"
+        message += "This figure of merit tells you about the size of the "
+        message += "P(r) errors.\n"
+        message += "If it is close to 1 and the other figures of merit are bad,"
+        message += " consider changing the maximum distance."
+        self.pos_err_ctl.SetToolTipString(message)
+        
+        sizer_res = wx.GridBagSizer(5, 5)
+
+        iy = 0
+        sizer_res.Add(label_rg, (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 15)
+        sizer_res.Add(self.rg_ctl,   (iy, 1), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        sizer_res.Add(label_rg_unit,   (iy, 2), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        iy += 1
+        sizer_res.Add(label_iq0, (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 15)
+        sizer_res.Add(self.iq0_ctl,   (iy, 1), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        sizer_res.Add(label_iq0_unit, (iy, 2), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        iy += 1
+        sizer_res.Add(label_bck, (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 15)
+        sizer_res.Add(self.bck_ctl, (iy, 1), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        sizer_res.Add(label_bck_unit, (iy, 2), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        iy += 1
+        sizer_res.Add(label_time, (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 15)
+        sizer_res.Add(self.time_ctl,   (iy, 1), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        sizer_res.Add(label_time_unit, (iy, 2), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        iy += 1
+        sizer_res.Add(label_chi2, (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 15)
+        sizer_res.Add(self.chi2_ctl,   (iy, 1), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+        iy += 1
+        sizer_res.Add(label_osc, (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 15)
+        sizer_res.Add(self.osc_ctl,   (iy, 1), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+
+        iy += 1
+        sizer_res.Add(label_pos, (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 15)
+        sizer_res.Add(self.pos_ctl, (iy, 1), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+
+        iy += 1
+        sizer_res.Add(label_pos_err, (iy, 0), (1, 1), wx.LEFT|wx.EXPAND, 15)
+        sizer_res.Add(self.pos_err_ctl,  (iy,1), (1, 1), wx.RIGHT|wx.EXPAND, 15)
+
+        ressizer.Add(sizer_res, 0)
+        iy_vb += 1
+        vbox.Add(ressizer, (iy_vb, 0), (1, 1),
+                 wx.LEFT|wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 5)
+
+        # ----- Buttons -----
+        id = wx.NewId()
+        button_OK = wx.Button(self, id, "Compute")
+        button_OK.SetToolTipString("Perform P(r) inversion.")
+        self.Bind(wx.EVT_BUTTON, self._on_invert, id = id)   
+        
+        self._set_reset_flag(True)
+        self._set_save_flag(True)
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        sizer_button.Add(button_OK, 0, wx.LEFT|wx.ADJUST_MINSIZE, 10)
+       
+        iy_vb += 1
+        vbox.Add(sizer_button, (iy_vb, 0), (1, 1),
+                 wx.EXPAND|wx.BOTTOM|wx.TOP|wx.RIGHT, 10)
+
+        self.Bind(wx.EVT_TEXT_ENTER, self._on_invert)
+
+        self.SetSizer(vbox)
+        
+    def _on_accept_alpha(self, evt):
+        """
+        User has accepted the estimated alpha, 
+        set it as part of the input parameters
+        """
+        try:
+            alpha = self.alpha_estimate_ctl.GetLabel()
+            tmp = float(alpha)
+            self.alpha_ctl.SetValue(alpha)
+        except:
+            # No estimate or bad estimate, either do nothing
+            #import sys
+            print "InversionControl._on_accept_alpha: %s" % sys.exc_value
+            pass
+    
+    def _on_accept_nterms(self, evt):
+        """
+        User has accepted the estimated number of terms, 
+        set it as part of the input parameters
+        """
+        try:
+            nterms = self.nterms_estimate_ctl.GetLabel()
+            tmp = float(nterms)
+            self.nfunc_ctl.SetValue(nterms)
+        except:
+            # No estimate or bad estimate, either do nothing
+            import sys
+            print "InversionControl._on_accept_nterms: %s" % sys.exc_value
+            pass
+        
+    def clear_panel(self):
+        """
+        """
+        self.plot_data.SetValue("")
+        self.on_reset(event=None)
+        
+    def on_reset(self, event=None):
+        """
+        Resets inversion parameters
+        """
+        self.nfunc = self._manager.DEFAULT_NFUNC
+        self.d_max = self._manager.DEFAULT_DMAX
+        self.alpha = self._manager.DEFAULT_ALPHA
+        self.qmin_ctl.SetValue("")
+        self.qmax_ctl.SetValue("")
+        self.time_ctl.SetValue("")
+        self.rg_ctl.SetValue("")
+        self.iq0_ctl.SetValue("")
+        self.bck_ctl.SetValue("")
+        self.chi2_ctl.SetValue("")
+        self.osc_ctl.SetValue("")
+        self.pos_ctl.SetValue("")
+        self.pos_err_ctl.SetValue("")
+        self.alpha_estimate_ctl.Enable(False)
+        self.alpha_estimate_ctl.SetLabel("")
+        self.nterms_estimate_ctl.Enable(False)
+        self.nterms_estimate_ctl.SetLabel("")
+        self._on_pars_changed()
+        
+    def _on_pars_changed(self, evt=None):
+        """
+        Called when an input parameter has changed
+        We will estimate the alpha parameter behind the
+        scenes. 
+        """
+        flag, alpha, dmax, nfunc, qmin, qmax, height, width = self._read_pars()
+        has_bck = self.bck_chk.IsChecked()
+        
+        # If the pars are valid, estimate alpha
+        if flag:
+            self.nterms_estimate_ctl.Enable(False)
+            self.alpha_estimate_ctl.Enable(False)
+            
+            dataset = self.plot_data.GetValue()
+            if dataset is not None and dataset.strip() != "":
+                self._manager.estimate_plot_inversion(alpha=alpha, nfunc=nfunc, 
+                                                 d_max=dmax,
+                                                 q_min=qmin, q_max=qmax,
+                                                 bck=has_bck, 
+                                                 height=height,
+                                                 width=width)
+        
+    def _read_pars(self, evt=None):
+        """
+        """    
+        alpha = 0
+        nfunc = 5
+        dmax  = 120
+        qmin  = 0
+        qmax  = 0
+        height = 0
+        width  = 0
+        flag = True
+        # Read slit height
+        try:
+            height_str = self.sheight_ctl.GetValue()
+            if len(height_str.lstrip().rstrip()) == 0:
+                height = 0
+            else:
+                height = float(height_str)
+                self.sheight_ctl.SetBackgroundColour(wx.WHITE)
+                self.sheight_ctl.Refresh()
+        except:
+            flag = False
+            self.sheight_ctl.SetBackgroundColour("pink")
+            self.sheight_ctl.Refresh()
+            
+        # Read slit width
+        try:
+            width_str = self.swidth_ctl.GetValue()
+            if len(width_str.lstrip().rstrip())==0:
+                width = 0
+            else:
+                width = float(width_str)
+                self.swidth_ctl.SetBackgroundColour(wx.WHITE)
+                self.swidth_ctl.Refresh()
+        except:
+            flag = False
+            self.swidth_ctl.SetBackgroundColour("pink")
+            self.swidth_ctl.Refresh()
+        
+        # Read alpha
+        try:
+            alpha = float(self.alpha_ctl.GetValue())
+            self.alpha_ctl.SetBackgroundColour(wx.WHITE)
+            self.alpha_ctl.Refresh()
+        except:
+            flag = False
+            self.alpha_ctl.SetBackgroundColour("pink")
+            self.alpha_ctl.Refresh()
+        
+        # Read d_max    
+        try:
+            dmax = float(self.dmax_ctl.GetValue())
+            self.dmax_ctl.SetBackgroundColour(wx.WHITE)
+            self.dmax_ctl.Refresh()
+        except:
+            flag = False
+            self.dmax_ctl.SetBackgroundColour("pink")
+            self.dmax_ctl.Refresh()
+            
+        # Read nfunc
+        try:
+            nfunc = int(self.nfunc_ctl.GetValue())
+            npts = self._manager.get_npts()
+            if npts > 0 and nfunc > npts:
+                message = "Number of function terms should be smaller "
+                message += "than the number of points"
+                wx.PostEvent(self._manager.parent, StatusEvent(status=message))
+                raise ValueError, message
+            self.nfunc_ctl.SetBackgroundColour(wx.WHITE)
+            self.nfunc_ctl.Refresh()
+        except:
+            flag = False
+            self.nfunc_ctl.SetBackgroundColour("pink")
+            self.nfunc_ctl.Refresh()
+        
+        # Read qmin
+        try:
+            qmin_str = self.qmin_ctl.GetValue()
+            if len(qmin_str.lstrip().rstrip()) == 0:
+                qmin = None
+            else:
+                qmin = float(qmin_str)
+                self.qmin_ctl.SetBackgroundColour(wx.WHITE)
+                self.qmin_ctl.Refresh()
+        except:
+            flag = False
+            self.qmin_ctl.SetBackgroundColour("pink")
+            self.qmin_ctl.Refresh()
+        
+        # Read qmax
+        try:
+            qmax_str = self.qmax_ctl.GetValue()
+            if len(qmax_str.lstrip().rstrip()) == 0:
+                qmax = None
+            else:
+                qmax = float(qmax_str)
+                self.qmax_ctl.SetBackgroundColour(wx.WHITE)
+                self.qmax_ctl.Refresh()
+        except:
+            flag = False
+            self.qmax_ctl.SetBackgroundColour("pink")
+            self.qmax_ctl.Refresh()
+        
+        return flag, alpha, dmax, nfunc, qmin, qmax, height, width
+    
+    def _on_explore(self, evt):
+        """
+        Invoke the d_max exploration dialog
+        """
+        from explore_dialog import ExploreDialog
+        if self._manager._last_pr is not None:
+            pr = self._manager._create_plot_pr()
+            dialog = ExploreDialog(pr, 10, None, -1, "")
+            dialog.ShowModal()
+        else:
+            message = "No data to analyze. Please load a data set to proceed."
+            wx.PostEvent(self._manager.parent, StatusEvent(status=message))
+            
+    def _on_invert(self, evt):
+        """
+        Perform inversion
+        
+        :param silent: when True, there will be no output for the user 
+        
+        """
+        # Get the data from the form
+        # Push it to the manager
+        
+        flag, alpha, dmax, nfunc, qmin, qmax, height, width = self._read_pars()
+        has_bck = self.bck_chk.IsChecked()
+        
+        if flag:
+            dataset = self.plot_data.GetValue()
+            if dataset==None or len(dataset.strip())==0:
+                message = "No data to invert. Select a data set before"
+                message += " proceeding with P(r) inversion."
+                wx.PostEvent(self._manager.parent, StatusEvent(status=message))
+            else:
+                self._manager.setup_plot_inversion(alpha=alpha, nfunc=nfunc, 
+                                                  d_max=dmax,
+                                                  q_min=qmin, q_max=qmax,
+                                                  bck=has_bck,
+                                                  height=height,
+                                                  width=width)
+        else:
+            message = "The P(r) form contains invalid values: "
+            message += "please submit it again."
+            wx.PostEvent(self.parent, StatusEvent(status=message))
+        
+    def _change_file(self, evt=None, filepath=None, data=None):
+        """
+        Choose a new input file for I(q)
+        """
+        if not self._manager is None:
+            self.plot_data.SetValue(str(data.name))
+            try:
+                self._manager.show_data(data=data, reset=True)
+                self._on_pars_changed(None)
+                self._on_invert(None)
+            except:
+                msg = "InversionControl._change_file: %s" % sys.exc_value
+                logging.error(msg)                    
+
+class HelpDialog(wx.Dialog):
+    """
+    """
+    def __init__(self, parent, id):
+        """
+        """
+        from sans.pr.invertor import help
+        wx.Dialog.__init__(self, parent, id, size=(400, 420))
+        self.SetTitle("P(r) help") 
+        
+
+        vbox = wx.BoxSizer(wx.VERTICAL)
+
+        explanation = help()
+           
+        label_explain = wx.StaticText(self, -1, explanation, size=(350, 320))
+            
+        vbox.Add(label_explain, 0, wx.ALL|wx.EXPAND, 15)
+
+
+        static_line = wx.StaticLine(self, -1)
+        vbox.Add(static_line, 0, wx.EXPAND, 0)
+        
+        button_OK = wx.Button(self, wx.ID_OK, "OK")
+        #button_Cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
+        
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        sizer_button.Add(button_OK, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
+        
+        vbox.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
+
+        self.SetSizer(vbox)
+        self.SetAutoLayout(True)
+        
+        self.Layout()
+        self.Centre()
+
+class PrDistDialog(wx.Dialog):
+    """
+    Property dialog to let the user change the number
+    of points on the P(r) plot.
+    """
+    def __init__(self, parent, id):
+        from sans.pr.invertor import help
+        wx.Dialog.__init__(self, parent, id, size=(250, 120))
+        self.SetTitle("P(r) distribution") 
+        
+
+        vbox = wx.BoxSizer(wx.VERTICAL)
+        
+        label_npts = wx.StaticText(self, -1, "Number of points")
+        self.npts_ctl = PrTextCtrl(self, -1, size=(100, 20))
+                 
+        pars_sizer = wx.GridBagSizer(5, 5)
+        iy = 0
+        pars_sizer.Add(label_npts, (iy, 0), (1, 1), wx.LEFT, 15)
+        pars_sizer.Add(self.npts_ctl, (iy, 1), (1, 1), wx.RIGHT, 0)
+        
+        vbox.Add(pars_sizer, 0, wx.ALL|wx.EXPAND, 15)
+
+        static_line = wx.StaticLine(self, -1)
+        vbox.Add(static_line, 0, wx.EXPAND, 0)
+        
+        button_OK = wx.Button(self, wx.ID_OK, "OK")
+        self.Bind(wx.EVT_BUTTON, self._checkValues, button_OK)
+        button_Cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
+        
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        sizer_button.Add(button_OK, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
+        sizer_button.Add(button_Cancel, 0,
+                         wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)        
+        vbox.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
+
+        self.SetSizer(vbox)
+        self.SetAutoLayout(True)
+        
+        self.Layout()
+        self.Centre()
+
+    def _checkValues(self, event):
+        """
+        Check the dialog content.
+        """
+        flag = True
+        try:
+            int(self.npts_ctl.GetValue())
+            self.npts_ctl.SetBackgroundColour(wx.WHITE)
+            self.npts_ctl.Refresh()
+        except:
+            flag = False
+            self.npts_ctl.SetBackgroundColour("pink")
+            self.npts_ctl.Refresh()
+        if flag:
+            event.Skip(True)
+
+    def get_content(self):
+        """
+        Return the content of the dialog.
+        At this point the values have already been
+        checked.
+        """
+        value = int(self.npts_ctl.GetValue())
+        return value
+    
+    def set_content(self, npts):
+        """
+        Initialize the content of the dialog.
+        """
+        self.npts_ctl.SetValue("%i" % npts)
+
+##### testing code ############################################################
+"""
+Example: ::
+
+class TestPlot:
+    def __init__(self, text):
+        self.name = text
+    
+class MyApp(wx.App):
+    def OnInit(self):
+        wx.InitAllImageHandlers()
+        dialog = PrDistDialog(None, -1)
+        if dialog.ShowModal() == wx.ID_OK:
+            pass
+        dialog.Destroy()
+        
+        return 1
+
+# end of class MyApp
+
+if __name__ == "__main__":
+    app = MyApp(0)
+    app.MainLoop()
+    
+"""
+##### end of testing code #####################################################    
Index: prview/src/sans/perspectives/pr/inversion_state.py
===================================================================
--- prview/src/sans/perspectives/pr/inversion_state.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/inversion_state.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,533 @@
+
+################################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#
+#See the license text in license.txt
+#
+#copyright 2009, University of Tennessee
+################################################################################
+
+
+import time
+import os
+import sys
+import logging
+from xml.dom.minidom import parse
+from lxml import etree
+from sans.guiframe.dataFitting import Data1D
+from DataLoader.readers.cansas_reader import Reader as CansasReader
+from DataLoader.readers.cansas_reader import get_content
+
+PRNODE_NAME = 'pr_inversion'
+CANSAS_NS = "cansas1d/1.0"
+
+# Translation of names between stored and object data
+## List of P(r) inversion inputs 
+in_list =  [["nterms",       "nfunc"],
+           ["d_max",        "d_max"],
+           ["alpha",        "alpha"],
+           ["slit_width",   "width"],
+           ["slit_height",  "height"],
+           ["qmin",         "qmin"],
+           ["qmax",         "qmax"]]                      
+
+## List of P(r) inversion outputs
+out_list = [["elapsed", "elapsed"],
+           ["rg",      "rg"],
+           ["iq0",     "iq0"],
+           ["bck",     "bck"],
+           ["chi2",    "chi2"],
+           ["osc",     "osc"],
+           ["pos",     "pos"],
+           ["pos_err", "pos_err"],
+           ["alpha_estimate", "alpha_estimate"],
+           ["nterms_estimate", "nterms_estimate"]]
+
+class InversionState(object):
+    """
+    Class to hold the state information of the InversionControl panel.
+    """
+    def __init__(self):
+        """
+        Default values
+        """
+        # Input 
+        self.file  = None
+        self.estimate_bck = False
+        self.timestamp = time.time()
+        
+        # Inversion parameters
+        self.nfunc = None
+        self.d_max = None
+        self.alpha = None
+        
+        # Slit parameters
+        self.height = None
+        self.width  = None
+        
+        # Q range
+        self.qmin  = None
+        self.qmax  = None
+        
+        # Outputs
+        self.elapsed = None
+        self.rg    = None
+        self.iq0   = None
+        self.bck   = None
+        self.chi2  = None
+        self.osc   = None
+        self.pos   = None
+        self.pos_err = None
+        
+        # Estimates
+        self.alpha_estimate = None
+        self.nterms_estimate = None
+        
+        # Data
+        self.q       = None
+        self.iq_obs  = None
+        self.iq_calc = None
+        
+        # Coefficients
+        self.coefficients = None
+        self.covariance = None
+    
+    def __str__(self):
+        """
+        Pretty print
+        
+        :return: string representing the state
+        
+        """
+        state  = "File:         %s\n" % self.file
+        state += "Timestamp:    %s\n" % self.timestamp
+        state += "Estimate bck: %s\n" % str(self.estimate_bck)
+        state += "No. terms:    %s\n" % str(self.nfunc)
+        state += "D_max:        %s\n" % str(self.d_max)
+        state += "Alpha:        %s\n" % str(self.alpha)
+        
+        state += "Slit height:  %s\n" % str(self.height)
+        state += "Slit width:   %s\n" % str(self.width)
+        
+        state += "Qmin:         %s\n" % str(self.qmin)
+        state += "Qmax:         %s\n" % str(self.qmax)
+        
+        state += "\nEstimates:\n"
+        state += "  Alpha:      %s\n" % str(self.alpha_estimate)
+        state += "  Nterms:     %s\n" % str(self.nterms_estimate)
+        
+        state += "\nOutputs:\n"
+        state += "  Elapsed:    %s\n" % str(self.elapsed)
+        state += "  Rg:         %s\n" % str(self.rg)
+        state += "  I(q=0):     %s\n" % str(self.iq0)
+        state += "  Bck:        %s\n" % str(self.bck)
+        state += "  Chi^2:      %s\n" % str(self.chi2)
+        state += "  Oscillation:%s\n" % str(self.osc)
+        state += "  Positive:   %s\n" % str(self.pos)
+        state += "  1-sigma pos:%s\n" % str(self.pos_err)
+        
+        return state
+        
+    def toXML(self, file="pr_state.prv", doc=None, entry_node=None):
+        """
+        Writes the state of the InversionControl panel to file, as XML.
+        
+        Compatible with standalone writing, or appending to an
+        already existing XML document. In that case, the XML document
+        is required. An optional entry node in the XML document 
+        may also be given.
+        
+        :param file: file to write to
+        :param doc: XML document object [optional]
+        :param entry_node: XML node within the XML document at which 
+            we will append the data [optional]
+        
+        """
+        from xml.dom.minidom import getDOMImplementation
+
+        # Check whether we have to write a standalone XML file
+        if doc is None:
+            impl = getDOMImplementation()
+        
+            doc_type = impl.createDocumentType(PRNODE_NAME, "1.0", "1.0")     
+        
+            newdoc = impl.createDocument(None, PRNODE_NAME, doc_type)
+            top_element = newdoc.documentElement
+        else:
+            # We are appending to an existing document
+            newdoc = doc
+            top_element = newdoc.createElement(PRNODE_NAME)
+            if entry_node is None:
+                newdoc.documentElement.appendChild(top_element)
+            else:
+                entry_node.appendChild(top_element)
+            
+        attr = newdoc.createAttribute("version")
+        attr.nodeValue = '1.0'
+        top_element.setAttributeNode(attr)
+        
+        # File name
+        element = newdoc.createElement("filename")
+        if self.file is not None:
+            element.appendChild(newdoc.createTextNode(str(self.file)))
+        else:
+            element.appendChild(newdoc.createTextNode(str(file)))
+        top_element.appendChild(element)
+        
+        element = newdoc.createElement("timestamp")
+        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
+        attr = newdoc.createAttribute("epoch")
+        attr.nodeValue = str(self.timestamp)
+        element.setAttributeNode(attr)
+        top_element.appendChild(element)
+        
+        # Inputs
+        inputs = newdoc.createElement("inputs")
+        top_element.appendChild(inputs)
+        
+        for item in in_list:
+            element = newdoc.createElement(item[0])
+            cmd = "element.appendChild(newdoc.createTextNode(str(self.%s)))"
+            exec  cmd % item[1]
+            inputs.appendChild(element)
+              
+        # Outputs
+        outputs = newdoc.createElement("outputs")
+        top_element.appendChild(outputs)
+        
+        for item in out_list:
+            element = newdoc.createElement(item[0])
+            cmd = "element.appendChild(newdoc.createTextNode(str(self.%s)))"
+            exec  cmd % item[1]
+            outputs.appendChild(element)
+                    
+        # Save output coefficients and its covariance matrix
+        element = newdoc.createElement("coefficients")
+        element.appendChild(newdoc.createTextNode(str(self.coefficients)))
+        outputs.appendChild(element)
+        element = newdoc.createElement("covariance")
+        element.appendChild(newdoc.createTextNode(str(self.covariance)))
+        outputs.appendChild(element)
+                    
+        # Save the file
+        if doc is None:
+            fd = open(file, 'w')
+            fd.write(newdoc.toprettyxml())
+            fd.close()
+            return None
+        else:
+            return newdoc.toprettyxml()
+
+    def fromXML(self, file=None, node=None):
+        """
+        Load a P(r) inversion state from a file
+        
+        :param file: .prv file
+        :param node: node of a XML document to read from
+        
+        """
+        if file is not None:
+            msg = "InversionState no longer supports non-CanSAS"
+            msg += " format for P(r) files"
+            raise RuntimeError, msg
+            
+        if node.get('version') and node.get('version') == '1.0':
+            
+            # Get file name
+            entry = get_content('ns:filename', node)
+            if entry is not None:
+                self.file = entry.text.strip()
+            
+            # Get time stamp
+            entry = get_content('ns:timestamp', node)
+            if entry is not None and entry.get('epoch'):
+                try:
+                    self.timestamp = float(entry.get('epoch'))
+                except:
+                    msg = "InversionState.fromXML: Could not read "
+                    msg += "timestamp\n %s" % sys.exc_value
+                    logging.error(msg)
+            
+            # Parse inversion inputs
+            entry = get_content('ns:inputs', node)
+            if entry is not None:
+                for item in in_list:
+                    input_field = get_content('ns:%s' % item[0], entry)
+                    if input_field is not None:
+                        try:
+                            cmd = 'self.%s = float(input_field.text.strip())' 
+                            exec  cmd % item[1]
+                        except:
+                            exec 'self.%s = None' % item[1]
+                input_field = get_content('ns:estimate_bck', entry)
+                if input_field is not None:
+                    try:
+                        self.estimate_bck = input_field.text.strip()=='True'
+                    except:
+                        self.estimate_bck = False
+                    
+            # Parse inversion outputs
+            entry = get_content('ns:outputs', node)
+            if entry is not None:
+                # Output parameters (scalars)
+                for item in out_list:
+                    input_field = get_content('ns:%s' % item[0], entry)
+                    if input_field is not None:
+                        try:
+                            cmd = 'self.%s = float(input_field.text.strip())'
+                            exec  cmd % item[1]
+                        except:
+                            exec 'self.%s = None' % item[1]
+            
+                # Look for coefficients
+                # Format is [value, value, value, value]
+                coeff = get_content('ns:coefficients', entry)
+                if coeff is not None:
+                    # Remove brackets
+                    c_values = coeff.text.strip().replace('[','')
+                    c_values = c_values.replace(']','')
+                    toks = c_values.split()
+                    self.coefficients = []
+                    for c in toks:
+                        try:
+                            self.coefficients.append(float(c))
+                        except:
+                            # Bad data, skip. We will count the number of 
+                            # coefficients at the very end and deal with 
+                            # inconsistencies then.
+                            pass
+                    # Sanity check
+                    if not len(self.coefficients) == self.nfunc:
+                        # Inconsistent number of coefficients.
+                        # Don't keep the data.
+                        err_msg = "InversionState.fromXML: inconsistant "
+                        err_msg += "number of coefficients: "
+                        err_msg += "%d %d" % (len(self.coefficients),
+                                              self.nfunc)
+                        logging.error(err_msg)
+                        self.coefficients = None
+                
+                # Look for covariance matrix
+                # Format is [ [value, value], [value, value] ]
+                coeff = get_content('ns:covariance', entry)
+                if coeff is not None:
+                    # Parse rows
+                    rows = coeff.text.strip().split('[')
+                    self.covariance = []
+                    for row in rows:
+                        row = row.strip()
+                        if len(row) == 0: continue
+                        # Remove end bracket
+                        row = row.replace(']','')
+                        c_values = row.split()
+                        cov_row = []
+                        for c in c_values:
+                            try:
+                                cov_row.append(float(c))
+                            except:
+                                # Bad data, skip. We will count the number of 
+                                # coefficients at the very end and deal with 
+                                # inconsistencies then.
+                                pass
+                        # Sanity check: check the number of entries in the row
+                        if len(cov_row) == self.nfunc:
+                            self.covariance.append(cov_row)
+                    # Sanity check: check the number of rows in the covariance
+                    # matrix
+                    if not len(self.covariance) == self.nfunc:
+                        # Inconsistent dimensions of the covariance matrix.
+                        # Don't keep the data.
+                        err_msg = "InversionState.fromXML: "
+                        err_msg += "inconsistant dimensions of the "
+                        err_msg += " covariance matrix: "
+                        err_msg += "%d %d" % (len(self.covariance), self.nfunc)
+                        logging.error(err_msg)
+                        self.covariance = None
+    
+class Reader(CansasReader):
+    """
+    Class to load a .prv P(r) inversion file
+    """
+    ## File type
+    type_name = "P(r)"
+    
+    ## Wildcards
+    type = ["P(r) files (*.prv)|*.prv",
+            "SANSView files (*.svs)|*.svs"]
+    ## List of allowed extensions
+    ext = ['.prv', '.PRV', '.svs', '.SVS']  
+    
+    def __init__(self, call_back, cansas=True):
+        """
+        Initialize the call-back method to be called
+        after we load a file
+        
+        :param call_back: call-back method
+        :param cansas:  True = files will be written/read in CanSAS format
+                        False = write CanSAS format
+            
+        """
+        ## Call back method to be executed after a file is read
+        self.call_back = call_back
+        ## CanSAS format flag
+        self.cansas = cansas
+        self.state = None
+        
+    def read(self, path):
+        """ 
+        Load a new P(r) inversion state from file
+        
+        :param path: file path
+        
+        :return: None
+        
+        """
+        if self.cansas==True:
+            return self._read_cansas(path)
+        else:
+            return self._read_standalone(path)
+        
+    def _read_standalone(self, path):
+        """ 
+        Load a new P(r) inversion state from file.
+        The P(r) node is assumed to be the top element.
+        
+        :param path: file path
+        
+        :return: None
+        
+        """
+        # Read the new state from file
+        state = InversionState()
+        state.fromXML(file=path)
+        
+        # Call back to post the new state
+        self.state = state
+        #self.call_back(state)
+        return None
+    
+    def _parse_prstate(self, entry):
+        """
+        Read a p(r) inversion result from an XML node
+        
+        :param entry: XML node to read from 
+        
+        :return: InversionState object
+        
+        """
+        state = None
+        
+        # Locate the P(r) node
+        try:
+            nodes = entry.xpath('ns:%s' % PRNODE_NAME,
+                                namespaces={'ns': CANSAS_NS})
+            if nodes !=[]:
+                # Create an empty state
+                state =  InversionState()
+                state.fromXML(node=nodes[0])
+        except:
+            msg = "XML document does not contain P(r) "
+            msg += "information.\n %s" % sys.exc_value
+            logging.info(msg)
+            
+        return state
+    
+    def _read_cansas(self, path):
+        """ 
+        Load data and P(r) information from a CanSAS XML file.
+        
+        :param path: file path
+        
+        :return: Data1D object if a single SASentry was found, 
+                    or a list of Data1D objects if multiple entries were found,
+                    or None of nothing was found
+                    
+        :raise RuntimeError: when the file can't be opened
+        :raise ValueError: when the length of the data vectors are inconsistent
+        
+        """
+        output = []
+        
+        if os.path.isfile(path):
+            basename  = os.path.basename(path)
+            root, extension = os.path.splitext(basename)
+            #TODO: eventually remove the check for .xml once
+            # the P(r) writer/reader is truly complete.
+            if  extension.lower() in self.ext or extension.lower() == '.xml':
+                
+                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
+                # Check the format version number
+                # Specifying the namespace will take care of the file 
+                #format version 
+                root = tree.getroot()
+                
+                entry_list = root.xpath('/ns:SASroot/ns:SASentry',
+                                        namespaces={'ns': CANSAS_NS})
+
+                for entry in entry_list:
+                    sas_entry = self._parse_entry(entry)
+                    prstate = self._parse_prstate(entry)
+                    #prstate could be None when .svs file is loaded
+                    #in this case, skip appending to output
+                    if prstate != None:
+                        sas_entry.meta_data['prstate'] = prstate
+                        sas_entry.filename = prstate.file
+                        output.append(sas_entry)
+        else:
+            raise RuntimeError, "%s is not a file" % path
+        
+        # Return output consistent with the loader's api
+        if len(output) == 0:
+            return None
+        elif len(output) == 1:
+            # Call back to post the new state
+            self.call_back(output[0].meta_data['prstate'], datainfo = output[0])
+            #self.state = output[0].meta_data['prstate']
+            return output[0]
+        else:
+            return output                
+    
+    
+    def write(self, filename, datainfo=None, prstate=None):
+        """
+        Write the content of a Data1D as a CanSAS XML file
+        
+        :param filename: name of the file to write
+        :param datainfo: Data1D object
+        :param prstate: InversionState object
+        
+        """
+        # Sanity check
+        if self.cansas == True:
+            doc =self.write_toXML(datainfo, prstate)        
+            # Write the XML document
+            fd = open(filename, 'w')
+            fd.write(doc.toprettyxml())
+            fd.close()
+        else:
+            prstate.toXML(file=filename)
+        
+    def write_toXML(self, datainfo=None, state=None):
+        """
+        Write toXML, a helper for write()
+        
+        : return: xml doc
+        """
+        if datainfo is None:
+            datainfo = Data1D(x=[], y=[])    
+        elif not issubclass(datainfo.__class__, Data1D):
+            msg = "The cansas writer expects a Data1D "
+            msg += "instance: %s" % str(datainfo.__class__.__name__)
+            raise RuntimeError, msg
+    
+        # Create basic XML document
+        doc, sasentry = self._to_xml_doc(datainfo)
+    
+        # Add the invariant information to the XML document
+        if state is not None:
+            state.toXML(doc=doc, entry_node=sasentry)
+            
+        return doc 
+    
Index: prview/src/sans/perspectives/pr/pr.py
===================================================================
--- prview/src/sans/perspectives/pr/pr.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/pr.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,1401 @@
+
+################################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#
+#See the license text in license.txt
+#
+#copyright 2009, University of Tennessee
+################################################################################
+
+
+# Make sure the option of saving each curve is available 
+# Use the I(q) curve as input and compare the output to P(r)
+
+import os
+import sys
+import wx
+import logging
+import time
+import copy
+import math
+import numpy
+import pylab
+from sans.guiframe.dataFitting import Data1D
+from sans.guiframe.events import NewPlotEvent
+from sans.guiframe.events import StatusEvent 
+from sans.guiframe.gui_style import GUIFRAME_ID   
+from sans.pr.invertor import Invertor
+from DataLoader.loader import Loader
+import DataLoader
+
+from pr_widgets import load_error 
+from sans.guiframe.plugin_base import PluginBase
+
+
+PR_FIT_LABEL       = r"$P_{fit}(r)$"
+PR_LOADED_LABEL    = r"$P_{loaded}(r)$"
+IQ_DATA_LABEL      = r"$I_{obs}(q)$"
+IQ_FIT_LABEL       = r"$I_{fit}(q)$"
+IQ_SMEARED_LABEL   = r"$I_{smeared}(q)$"
+GROUP_ID_IQ_DATA = r"$I_{obs}(q)$"
+GROUP_ID_PR_FIT = r"$P_{fit}(r)$"
+
+
+
+class Plugin(PluginBase):
+    """
+    """
+    DEFAULT_ALPHA = 0.0001
+    DEFAULT_NFUNC = 10
+    DEFAULT_DMAX  = 140.0
+    
+    def __init__(self, standalone=True):
+        PluginBase.__init__(self, name="Pr Inversion", standalone=standalone)
+        ## Simulation window manager
+        self.simview = None
+       
+        ## State data
+        self.alpha      = self.DEFAULT_ALPHA
+        self.nfunc      = self.DEFAULT_NFUNC
+        self.max_length = self.DEFAULT_DMAX
+        self.q_min      = None
+        self.q_max      = None
+        self.has_bck    = False
+        self.slit_height = 0
+        self.slit_width  = 0
+        ## Remember last plottable processed
+        self.last_data  = "sphere_60_q0_2.txt"
+        self._current_file_data = None
+        ## Time elapsed for last computation [sec]
+        # Start with a good default
+        self.elapsed = 0.022
+        self.iq_data_shown = False
+        
+        ## Current invertor
+        self.invertor    = None
+        self.pr          = None
+        self.data_id = IQ_DATA_LABEL 
+        # Copy of the last result in case we need to display it.
+        self._last_pr    = None
+        self._last_out   = None
+        self._last_cov   = None
+        ## Calculation thread
+        self.calc_thread = None
+        ## Estimation thread
+        self.estimation_thread = None
+        ## Result panel
+        self.control_panel = None
+        ## Currently views plottable
+        self.current_plottable = None
+        ## Number of P(r) points to display on the output plot
+        self._pr_npts = 51
+        ## Flag to let the plug-in know that it is running standalone
+        self.standalone = standalone
+        self._normalize_output = False
+        self._scale_output_unity = False
+        
+        ## List of added P(r) plots
+        self._added_plots = {}
+        self._default_Iq  = {}
+        self.list_plot_id = []
+        
+        # Associate the inversion state reader with .prv files
+        from inversion_state import Reader
+         
+        # Create a CanSAS/Pr reader
+        self.state_reader = Reader(self.set_state)
+        self._extensions = '.prv'
+        l = Loader()
+        l.associate_file_reader('.prv', self.state_reader)
+        #l.associate_file_reader(".svs", self.state_reader)
+                
+        # Log startup
+        logging.info("Pr(r) plug-in started")
+        
+    def delete_data(self, data_id):
+        """
+        delete the data association with prview
+        """
+        if self.calc_thread is not None and self.calc_thread.isrunning():
+            msg = "Data in use in Prview\n"
+        if self.estimation_thread is not None and \
+            self.estimation_thread.isrunning():
+             msg = "Data in use in Prview\n"
+        self.control_panel.clear_panel()
+       
+    def get_data(self):
+        """
+        """
+        return self.current_plottable
+    
+    def set_state(self, state=None, datainfo=None):
+        """
+        Call-back method for the inversion state reader.
+        This method is called when a .prv file is loaded.
+        
+        :param state: InversionState object
+        :param datainfo: Data1D object [optional]
+        
+        """
+        try:
+            if datainfo.__class__.__name__ == 'list':
+                if len(datainfo) >= 1:
+                    data = datainfo[0]
+                else:
+                    data = None
+            else:
+                data = datainfo
+            if data is None:
+                raise RuntimeError, "Pr.set_state: datainfo parameter cannot be None in standalone mode"
+            
+            # Ensuring that plots are coordinated correctly
+            t = time.localtime(data.meta_data['prstate'].timestamp)
+            time_str = time.strftime("%b %d %H:%M", t)
+            
+            # Check that no time stamp is already appended
+            max_char = data.meta_data['prstate'].file.find("[")
+            if max_char < 0:
+                max_char = len(data.meta_data['prstate'].file)
+            
+            datainfo.meta_data['prstate'].file = data.meta_data['prstate'].file[0:max_char] +' [' + time_str + ']'
+            data.filename = data.meta_data['prstate'].file
+            # TODO:
+            #remove this call when state save all information about the gui data
+            # such as ID , Group_ID, etc...
+            #make self.current_plottable = datainfo directly
+            self.current_plottable = self.parent.create_gui_data(data,None)
+            self.current_plottable.group_id = data.meta_data['prstate'].file
+            
+            # Make sure the user sees the P(r) panel after loading
+            #self.parent.set_perspective(self.perspective)  
+            self.on_perspective(event=None)   
+            # Load the P(r) results
+            #state = self.state_reader.get_state()
+            data_dict = {self.current_plottable.id:self.current_plottable}
+            self.parent.add_data(data_list=data_dict)
+            wx.PostEvent(self.parent, NewPlotEvent(plot=self.current_plottable,
+                                        title=self.current_plottable.title))
+            self.control_panel.set_state(state)
+        except:
+            logging.error("prview.set_state: %s" % sys.exc_value)
+
+  
+    def help(self, evt):
+        """
+        Show a general help dialog. 
+        
+        :TODO: replace the text with a nice image
+        
+        """
+        from inversion_panel import HelpDialog
+        dialog = HelpDialog(None, -1)
+        if dialog.ShowModal() == wx.ID_OK:
+            dialog.Destroy()
+        else:
+            dialog.Destroy()
+    
+    def _fit_pr(self, evt):
+        """
+        """
+        from sans.pr.invertor import Invertor
+        # Generate P(r) for sphere
+        radius = 60.0
+        d_max  = 2*radius
+        
+        r = pylab.arange(0.01, d_max, d_max/51.0)
+        M = len(r)
+        y = numpy.zeros(M)
+        pr_err = numpy.zeros(M)
+        
+        sum = 0.0
+        for j in range(M):
+            value = self.pr_theory(r[j], radius)
+            sum += value
+            y[j] = value
+            pr_err[j] = math.sqrt(y[j])
+
+        y = y/sum*d_max/len(r)
+
+        # Perform fit
+        pr = Invertor()
+        pr.d_max = d_max
+        pr.alpha = 0
+        pr.x = r
+        pr.y = y
+        pr.err = pr_err
+        out, cov = pr.pr_fit()
+        for i in range(len(out)):
+            print "%g +- %g" % (out[i], math.sqrt(cov[i][i]))
+        
+        # Show input P(r)
+        title = "Pr"
+        new_plot = Data1D(pr.x, pr.y, dy=pr.err)
+        new_plot.name = "P_{obs}(r)"
+        new_plot.xaxis("\\rm{r}", 'A')
+        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+        new_plot.group_id = "P_{obs}(r)"
+        new_plot.id = "P_{obs}(r)"
+        new_plot.title = title
+        self.parent.update_theory(data_id=self.data_id,  theory=new_plot)
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
+
+        # Show P(r) fit
+        self.show_pr(out, pr)
+        
+        # Show I(q) fit
+        q = pylab.arange(0.001, 0.1, 0.01/51.0)
+        self.show_iq(out, pr, q)
+        
+    def show_shpere(self, x, radius=70.0, x_range=70.0):
+        """
+        """
+        # Show P(r)
+        y_true = numpy.zeros(len(x))
+
+        sum_true = 0.0
+        for i in range(len(x)):
+            y_true[i] = self.pr_theory(x[i], radius)            
+            sum_true += y_true[i]
+            
+        y_true = y_true/sum_true*x_range/len(x)
+        
+        # Show the theory P(r)
+        new_plot = Data1D(x, y_true)
+        new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
+        new_plot.name = "P_{true}(r)"
+        new_plot.xaxis("\\rm{r}", 'A')
+        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+        new_plot.id = "P_{true}(r)"
+        new_plot.group_id = "P_{true}(r)"
+        self.parent.update_theory(data_id=self.data_id, theory=new_plot)
+        #Put this call in plottables/guitools    
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
+                                                title="Sphere P(r)"))
+       
+        
+    def get_npts(self):
+        """
+        Returns the number of points in the I(q) data
+        """
+        try:
+            return len(self.pr.x)
+        except:
+            return 0
+        
+    def show_iq(self, out, pr, q=None):
+        """
+        """  
+        qtemp = pr.x
+        if not q==None:
+            qtemp = q
+
+        # Make a plot
+        maxq = -1
+        for q_i in qtemp:
+            if q_i>maxq:
+                maxq=q_i
+                
+        minq = 0.001
+        
+        # Check for user min/max
+        if not pr.q_min==None:
+            minq = pr.q_min
+        if not pr.q_max==None:
+            maxq = pr.q_max
+                
+        x = pylab.arange(minq, maxq, maxq/301.0)
+        y = numpy.zeros(len(x))
+        err = numpy.zeros(len(x))
+        for i in range(len(x)):
+            value = pr.iq(out, x[i])
+            y[i] = value
+            try:
+                err[i] = math.sqrt(math.fabs(value))
+            except:
+                err[i] = 1.0
+                print "Error getting error", value, x[i]
+                
+        new_plot = Data1D(x, y)
+        new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
+        new_plot.name = IQ_FIT_LABEL
+        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
+        title = "I(q)"
+        new_plot.title = title
+        
+        # If we have a group ID, use it
+        if pr.info.has_key("plot_group_id"):
+            new_plot.group_id = pr.info["plot_group_id"]
+        new_plot.id = IQ_FIT_LABEL
+        self.parent.update_theory(data_id=self.data_id, theory=new_plot)
+        
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
+        
+        # If we have used slit smearing, plot the smeared I(q) too
+        if pr.slit_width>0 or pr.slit_height>0:
+            x = pylab.arange(minq, maxq, maxq/301.0)
+            y = numpy.zeros(len(x))
+            err = numpy.zeros(len(x))
+            for i in range(len(x)):
+                value = pr.iq_smeared(out, x[i])
+                y[i] = value
+                try:
+                    err[i] = math.sqrt(math.fabs(value))
+                except:
+                    err[i] = 1.0
+                    print "Error getting error", value, x[i]
+                    
+            new_plot = Data1D(x, y)
+            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
+            new_plot.name = IQ_SMEARED_LABEL
+            new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+            new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
+            # If we have a group ID, use it
+            if pr.info.has_key("plot_group_id"):
+              new_plot.group_id = pr.info["plot_group_id"]
+            new_plot.id = IQ_SMEARED_LABEL
+            new_plot.title = title
+            self.parent.update_theory(data_id=self.data_id, theory=new_plot)
+            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
+        
+    def _on_pr_npts(self, evt):
+        """
+        Redisplay P(r) with a different number of points
+        """   
+        from inversion_panel import PrDistDialog
+        dialog = PrDistDialog(None, -1)
+        dialog.set_content(self._pr_npts)
+        if dialog.ShowModal() == wx.ID_OK:
+            self._pr_npts= dialog.get_content()
+            dialog.Destroy()
+            self.show_pr(self._last_out, self._last_pr, self._last_cov)
+        else:
+            dialog.Destroy()
+        
+        
+    def show_pr(self, out, pr, cov=None):
+        """
+        """     
+        # Show P(r)
+        x = pylab.arange(0.0, pr.d_max, pr.d_max/self._pr_npts)
+    
+        y = numpy.zeros(len(x))
+        dy = numpy.zeros(len(x))
+        y_true = numpy.zeros(len(x))
+
+        sum = 0.0
+        pmax = 0.0
+        cov2 = numpy.ascontiguousarray(cov)
+        
+        for i in range(len(x)):
+            if cov2==None:
+                value = pr.pr(out, x[i])
+            else:
+                (value, dy[i]) = pr.pr_err(out, cov2, x[i])
+            sum += value*pr.d_max/len(x)
+            
+            # keep track of the maximum P(r) value
+            if value>pmax:
+                pmax = value
+                
+            y[i] = value
+                
+        if self._normalize_output==True:
+            y = y/sum
+            dy = dy/sum
+        elif self._scale_output_unity==True:
+            y = y/pmax
+            dy = dy/pmax
+        
+        if cov2==None:
+            new_plot = Data1D(x, y)
+            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
+        else:
+            new_plot = Data1D(x, y, dy=dy)
+        new_plot.name = PR_FIT_LABEL
+        new_plot.xaxis("\\rm{r}", 'A')
+        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+        new_plot.title = "P(r) fit"
+        new_plot.id = PR_FIT_LABEL
+        # Make sure that the plot is linear
+        new_plot.xtransform = "x"
+        new_plot.ytransform = "y"  
+        new_plot.group_id = GROUP_ID_PR_FIT
+        self.parent.update_theory(data_id=self.data_id, theory=new_plot)    
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit"))
+        return x, pr.d_max
+          
+    def load(self, data):
+        """
+        Load data. This will eventually be replaced
+        by our standard DataLoader class.
+        """
+        class FileData:
+            x = None
+            y = None
+            err = None
+            path = None
+            
+            def __init__(self, path):
+                self.path = path
+                
+        self._current_file_data = FileData(data.path)
+        
+        # Use data loader to load file
+        #dataread = Loader().load(path)
+        dataread = data
+        # Notify the user if we could not read the file
+        if dataread is None:
+            raise RuntimeError, "Invalid data"
+            
+        x = None
+        y = None
+        err = None
+        if dataread.__class__.__name__ == 'Data1D':
+            x = dataread.x
+            y = dataread.y
+            err = dataread.dy
+        else:
+            if isinstance(dataread, list) and len(dataread)>0:
+                x = dataread[0].x
+                y = dataread[0].y
+                err = dataread[0].dy
+                msg = "PrView only allows a single data set at a time. "
+                msg += "Only the first data set was loaded." 
+                wx.PostEvent(self.parent, StatusEvent(status=msg))
+            else:
+                if dataread is None:
+                    return x, y, err
+                raise RuntimeError, "This tool can only read 1D data"
+        
+        self._current_file_data.x = x
+        self._current_file_data.y = y
+        self._current_file_data.err = err
+        return x, y, err
+                
+    def load_columns(self, path = "sphere_60_q0_2.txt"):
+        """
+        Load 2- or 3- column ascii
+        """
+        import numpy, math, sys
+        # Read the data from the data file
+        data_x   = numpy.zeros(0)
+        data_y   = numpy.zeros(0)
+        data_err = numpy.zeros(0)
+        scale    = None
+        min_err  = 0.0
+        if not path == None:
+            input_f = open(path,'r')
+            buff    = input_f.read()
+            lines   = buff.split('\n')
+            for line in lines:
+                try:
+                    toks = line.split()
+                    x = float(toks[0])
+                    y = float(toks[1])
+                    if len(toks)>2:
+                        err = float(toks[2])
+                    else:
+                        if scale==None:
+                            scale = 0.05*math.sqrt(y)
+                            #scale = 0.05/math.sqrt(y)
+                            min_err = 0.01*y
+                        err = scale*math.sqrt(y)+min_err
+                        #err = 0
+                        
+                    data_x = numpy.append(data_x, x)
+                    data_y = numpy.append(data_y, y)
+                    data_err = numpy.append(data_err, err)
+                except:
+                    pass
+                   
+        if not scale==None:
+            message = "The loaded file had no error bars, statistical errors are assumed."
+            wx.PostEvent(self.parent, StatusEvent(status=message))
+        else:
+            wx.PostEvent(self.parent, StatusEvent(status=''))
+                        
+        return data_x, data_y, data_err     
+        
+    def load_abs(self, path):
+        """
+        Load an IGOR .ABS reduced file
+        
+        :param path: file path
+        
+        :return: x, y, err vectors
+        
+        """
+        import numpy, math, sys
+        # Read the data from the data file
+        data_x   = numpy.zeros(0)
+        data_y   = numpy.zeros(0)
+        data_err = numpy.zeros(0)
+        scale    = None
+        min_err  = 0.0
+        
+        data_started = False
+        if not path == None:
+            input_f = open(path,'r')
+            buff    = input_f.read()
+            lines   = buff.split('\n')
+            for line in lines:
+                if data_started==True:
+                    try:
+                        toks = line.split()
+                        x = float(toks[0])
+                        y = float(toks[1])
+                        if len(toks)>2:
+                            err = float(toks[2])
+                        else:
+                            if scale==None:
+                                scale = 0.05*math.sqrt(y)
+                                #scale = 0.05/math.sqrt(y)
+                                min_err = 0.01*y
+                            err = scale*math.sqrt(y)+min_err
+                            #err = 0
+                            
+                        data_x = numpy.append(data_x, x)
+                        data_y = numpy.append(data_y, y)
+                        data_err = numpy.append(data_err, err)
+                    except:
+                        pass
+                elif line.find("The 6 columns")>=0:
+                    data_started = True      
+                   
+        if not scale==None:
+            message = "The loaded file had no error bars, statistical errors are assumed."
+            wx.PostEvent(self.parent, StatusEvent(status=message))
+        else:
+            wx.PostEvent(self.parent, StatusEvent(status=''))
+                        
+        return data_x, data_y, data_err     
+        
+    def pr_theory(self, r, R):
+        """  
+        """
+        if r<=2*R:
+            return 12.0* ((0.5*r/R)**2) * ((1.0-0.5*r/R)**2) * ( 2.0 + 0.5*r/R )
+        else:
+            return 0.0
+
+    def get_context_menu(self, plotpanel=None):
+        """
+        Get the context menu items available for P(r)
+        
+        :param graph: the Graph object to which we attach the context menu
+        
+        :return: a list of menu items with call-back function
+        
+        """
+        graph = plotpanel.graph
+        # Look whether this Graph contains P(r) data
+        if graph.selected_plottable not in plotpanel.plots:
+            return []
+        item = plotpanel.plots[graph.selected_plottable]
+        if item.id == PR_FIT_LABEL:
+            #add_data_hint = "Load a data file and display it on this plot"
+            #["Add P(r) data",add_data_hint , self._on_add_data],
+            change_n_hint = "Change the number of"
+            change_n_hint += " points on the P(r) output"
+            change_n_label = "Change number of P(r) points"
+            m_list = [[change_n_label, change_n_hint , self._on_pr_npts]]
+
+            if self._scale_output_unity or self._normalize_output:
+                hint = "Let the output P(r) keep the scale of the data"
+                m_list.append(["Disable P(r) scaling", hint, 
+                               self._on_disable_scaling])
+            if not self._scale_output_unity:
+                m_list.append(["Scale P_max(r) to unity", 
+                               "Scale P(r) so that its maximum is 1", 
+                               self._on_scale_unity])
+            if not self._normalize_output:
+                m_list.append(["Normalize P(r) to unity", 
+                               "Normalize the integral of P(r) to 1", 
+                               self._on_normalize])
+                
+            return m_list
+             
+        elif item.id in [PR_LOADED_LABEL, IQ_DATA_LABEL, IQ_FIT_LABEL,
+                          IQ_SMEARED_LABEL]:
+            return []
+        elif item.id == graph.selected_plottable:
+	       if not self.standalone and issubclass(item.__class__, Data1D):
+                return [["Compute P(r)", 
+                             "Compute P(r) from distribution", 
+                             self._on_context_inversion]]      
+                
+        return []
+
+    def _on_disable_scaling(self, evt):
+        """
+        Disable P(r) scaling
+            
+        :param evt: Menu event
+        
+        """
+        self._normalize_output = False
+        self._scale_output_unity = False
+        self.show_pr(self._last_out, self._last_pr, self._last_cov)
+        
+        # Now replot the original added data
+        for plot in self._added_plots:
+            self._added_plots[plot].y = numpy.copy(self._default_Iq[plot])
+            wx.PostEvent(self.parent, 
+                         NewPlotEvent(plot=self._added_plots[plot], 
+                                      title=self._added_plots[plot].name,
+                                                   update=True))        
+        
+        # Need the update flag in the NewPlotEvent to protect against
+        # the plot no longer being there...
+        
+    def _on_normalize(self, evt):
+        """
+        Normalize the area under the P(r) curve to 1.
+        This operation is done for all displayed plots.
+        
+        :param evt: Menu event
+        
+        """
+        self._normalize_output = True
+        self._scale_output_unity = False
+            
+        self.show_pr(self._last_out, self._last_pr, self._last_cov)
+        
+        # Now scale the added plots too
+        for plot in self._added_plots:
+            sum = numpy.sum(self._added_plots[plot].y)
+            npts = len(self._added_plots[plot].x)
+            sum *= self._added_plots[plot].x[npts-1]/npts
+            y = self._added_plots[plot].y/sum
+            
+            new_plot = Data1D(self._added_plots[plot].x, y)
+            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
+            new_plot.group_id = self._added_plots[plot].group_id
+            new_plot.id = self._added_plots[plot].id
+            new_plot.title = self._added_plots[plot].title
+            new_plot.name = self._added_plots[plot].name
+            new_plot.xaxis("\\rm{r}", 'A')
+            new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+            self.parent.update_theory(data_id=self.data_id, theory=new_plot)        
+            wx.PostEvent(self.parent, 
+                         NewPlotEvent(plot=new_plot, update=True,
+                                         title=self._added_plots[plot].name))
+        
+    def _on_scale_unity(self, evt):
+        """
+        Scale the maximum P(r) value on each displayed plot to 1.
+        
+        :param evt: Menu event
+        
+        """
+        self._scale_output_unity = True
+        self._normalize_output = False
+            
+        self.show_pr(self._last_out, self._last_pr, self._last_cov)
+        
+        # Now scale the added plots too
+        for plot in self._added_plots:
+            _max = 0
+            for y in self._added_plots[plot].y:
+                if y>_max: 
+                    _max = y
+            y = self._added_plots[plot].y/_max
+            
+            new_plot = Data1D(self._added_plots[plot].x, y)
+            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
+            new_plot.name = self._added_plots[plot].name
+            new_plot.xaxis("\\rm{r}", 'A')
+            new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+            self.parent.update_theory(data_id=self.data_id,theory=new_plot)        
+            wx.PostEvent(self.parent, 
+                         NewPlotEvent(plot=new_plot, update=True,
+                                title=self._added_plots[plot].name))        
+        
+        
+    def _on_add_data(self, evt):
+        """
+        Add a data curve to the plot
+        
+        :WARNING: this will be removed once guiframe.plotting has
+             its full functionality
+        """
+        path = self.choose_file()
+        if path==None:
+            return
+        
+        #x, y, err = self.parent.load_ascii_1D(path)
+        # Use data loader to load file
+        try:
+            dataread = Loader().load(path)
+            x = None
+            y = None
+            err = None
+            if dataread.__class__.__name__ == 'Data1D':
+                x = dataread.x
+                y = dataread.y
+                err = dataread.dy
+            else:
+                if isinstance(dataread, list) and len(dataread)>0:
+                    x = dataread[0].x
+                    y = dataread[0].y
+                    err = dataread[0].dy
+                    msg = "PrView only allows a single data set at a time. "
+                    msg += "Only the first data set was loaded." 
+                    wx.PostEvent(self.parent, StatusEvent(status=msg))
+                else:
+                    msg = "This tool can only read 1D data"
+                    wx.PostEvent(self.parent, StatusEvent(status=msg))
+                    return
+            
+        except:
+            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+            return
+        
+        filename = os.path.basename(path)
+        new_plot = Data1D(x, y)
+        new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
+        new_plot.name = filename
+        new_plot.xaxis("\\rm{r}", 'A')
+        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+        # Store a ref to the plottable for later use
+        self._added_plots[filename] = new_plot
+        self._default_Iq[filename]  = numpy.copy(y)
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=filename))
+        
+    def start_thread(self):
+        """
+        """
+        from pr_thread import CalcPr
+        from copy import deepcopy
+        
+        # If a thread is already started, stop it
+        if self.calc_thread != None and self.calc_thread.isrunning():
+            self.calc_thread.stop()
+                
+        pr = self.pr.clone()
+        self.calc_thread = CalcPr(pr, self.nfunc,
+                                   error_func=self._thread_error, 
+                                   completefn=self._completed, updatefn=None)
+        self.calc_thread.queue()
+        self.calc_thread.ready(2.5)
+    
+    def _thread_error(self, error):
+        """
+        """
+        wx.PostEvent(self.parent, StatusEvent(status=error))
+    
+    def _estimate_completed(self, alpha, message, elapsed):
+        """
+        Parameter estimation completed, 
+        display the results to the user
+        
+        :param alpha: estimated best alpha
+        :param elapsed: computation time
+        
+        """
+        # Save useful info
+        self.elapsed = elapsed
+        self.control_panel.alpha_estimate = alpha
+        if not message==None:
+            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
+        self.perform_estimateNT()
+    
+    def _estimateNT_completed(self, nterms, alpha, message, elapsed):
+        """
+        Parameter estimation completed, 
+        display the results to the user
+        
+        :param alpha: estimated best alpha
+        :param nterms: estimated number of terms
+        :param elapsed: computation time
+        
+        """
+        # Save useful info
+        self.elapsed = elapsed
+        self.control_panel.nterms_estimate = nterms
+        self.control_panel.alpha_estimate = alpha
+        if not message==None:
+            wx.PostEvent(self.parent, StatusEvent(status=str(message)))
+    
+    def _completed(self, out, cov, pr, elapsed):
+        """
+        wxCallAfter Method called with the results when the inversion
+        is done
+        
+        :param out: output coefficient for the base functions
+        :param cov: covariance matrix
+        :param pr: Invertor instance
+        :param elapsed: time spent computing
+        """
+        # Ensure hat you have all inputs are ready at the time call happens:
+        # Without CallAfter, it will freeze with wx >= 2.9.
+        wx.CallAfter(self._completed_call, out, cov, pr, elapsed)
+        
+    def _completed_call(self, out, cov, pr, elapsed):
+        """
+        Method called with the results when the inversion
+        is done
+        
+        :param out: output coefficient for the base functions
+        :param cov: covariance matrix
+        :param pr: Invertor instance
+        :param elapsed: time spent computing
+        
+        """
+        from copy import deepcopy
+        # Save useful info
+        self.elapsed = elapsed
+        # Keep a copy of the last result
+        self._last_pr  = pr.clone()
+        self._last_out = out
+        self._last_cov = cov
+        
+        # Save Pr invertor
+        self.pr = pr
+        
+        #message = "Computation completed in"
+        #message +=  %g seconds [chi2=%g]" % (elapsed, pr.chi2)
+        #wx.PostEvent(self.parent, StatusEvent(status=message))
+
+        cov = numpy.ascontiguousarray(cov)
+
+        # Show result on control panel
+        self.control_panel.chi2 = pr.chi2
+        self.control_panel.elapsed = elapsed
+        self.control_panel.oscillation = pr.oscillations(out)
+        #print "OSCILL", pr.oscillations(out)
+        #print "PEAKS:", pr.get_peaks(out) 
+        self.control_panel.positive = pr.get_positive(out)
+        self.control_panel.pos_err  = pr.get_pos_err(out, cov)
+        self.control_panel.rg = pr.rg(out)
+        self.control_panel.iq0 = pr.iq0(out)
+        self.control_panel.bck = pr.background
+        
+        if False:
+            for i in range(len(out)):
+                try:
+                    print "%d: %g +- %g" % (i, out[i],
+                                             math.sqrt(math.fabs(cov[i][i])))
+                except: 
+                    print sys.exc_value
+                    print "%d: %g +- ?" % (i, out[i])        
+        
+            # Make a plot of I(q) data
+            new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
+            new_plot.name = IQ_DATA_LABEL
+            new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+            new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
+            if pr.info.has_key("plot_group_id"):
+                new_plot.group_id = pr.info["plot_group_id"]
+            new_plot.id = self.data_id
+            self.parent.update_theory(data_id=self.data_id,
+                                       theory=new_plot)
+            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
+                
+        # Show I(q) fit
+        self.show_iq(out, self.pr)
+        
+        # Show P(r) fit
+        x_values, x_range = self.show_pr(out, self.pr, cov)  
+        
+        # Popup result panel
+        #result_panel = InversionResults(self.parent, 
+        #-1, style=wx.RAISED_BORDER)
+        
+    def show_data(self, path=None, data=None, reset=False):
+        """
+        Show data read from a file
+        
+        :param path: file path
+        :param reset: if True all other plottables will be cleared
+        
+        """
+        #if path is not None:
+        if data is not None:
+            try:
+                pr = self._create_file_pr(data)
+            except:
+                status = "Problem reading data: %s" % sys.exc_value
+                wx.PostEvent(self.parent, StatusEvent(status=status))
+                raise RuntimeError, status
+                
+            # If the file contains nothing, just return
+            if pr is None:
+                raise RuntimeError, "Loaded data is invalid"
+            
+            self.pr = pr
+       
+        # Make a plot of I(q) data
+        if self.pr.err == None:
+            new_plot = Data1D(self.pr.x, self.pr.y)
+            new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM
+        else:
+            new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
+        new_plot.name = IQ_DATA_LABEL
+        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
+        new_plot.interactive = True
+        new_plot.group_id = GROUP_ID_IQ_DATA
+        new_plot.id = self.data_id
+        new_plot.title = "I(q)"   
+        wx.PostEvent(self.parent, 
+                     NewPlotEvent(plot=new_plot, title="I(q)", reset=reset))
+        
+        self.current_plottable = new_plot
+        # Get Q range
+        self.control_panel.q_min = self.pr.x.min()
+        self.control_panel.q_max = self.pr.x.max()
+            
+    def save_data(self, filepath, prstate=None):
+        """
+        Save data in provided state object.
+        
+        :TODO: move the state code away from inversion_panel and move it here. 
+                Then remove the "prstate" input and make this method private.
+                
+        :param filepath: path of file to write to
+        :param prstate: P(r) inversion state 
+        
+        """
+        #TODO: do we need this or can we use DataLoader.loader.save directly?
+        
+        # Add output data and coefficients to state
+        prstate.coefficients = self._last_out
+        prstate.covariance = self._last_cov
+        
+        # Write the output to file
+        # First, check that the data is of the right type
+        if issubclass(self.current_plottable.__class__,
+                       DataLoader.data_info.Data1D):
+            self.state_reader.write(filepath, self.current_plottable, prstate)
+        else:
+            msg = "pr.save_data: the data being saved is not a"
+            msg += " DataLoader.data_info.Data1D object" 
+            raise RuntimeError, msg
+        
+        
+    def setup_plot_inversion(self, alpha, nfunc, d_max, q_min=None, q_max=None, 
+                             bck=False, height=0, width=0):
+        """
+        """
+        self.alpha = alpha
+        self.nfunc = nfunc
+        self.max_length = d_max
+        self.q_min = q_min
+        self.q_max = q_max
+        self.has_bck = bck
+        self.slit_height = height
+        self.slit_width  = width
+        
+        try:
+            pr = self._create_plot_pr()
+            if not pr==None:
+                self.pr = pr
+                self.perform_inversion()
+        except:
+            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+
+    def estimate_plot_inversion(self, alpha, nfunc, d_max, 
+                                q_min=None, q_max=None, 
+                                bck=False, height=0, width=0):
+        """
+        """
+        self.alpha = alpha
+        self.nfunc = nfunc
+        self.max_length = d_max
+        self.q_min = q_min
+        self.q_max = q_max
+        self.has_bck = bck
+        self.slit_height = height
+        self.slit_width  = width
+        
+        try:
+            pr = self._create_plot_pr()
+            if not pr==None:
+                self.pr = pr
+                self.perform_estimate()
+        except:
+            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))            
+
+    def _create_plot_pr(self, estimate=False):
+        """
+        Create and prepare invertor instance from
+        a plottable data set.
+        
+        :param path: path of the file to read in 
+        
+        """
+        # Sanity check
+        if self.current_plottable is None:
+            msg = "Please load a valid data set before proceeding."
+            wx.PostEvent(self.parent, StatusEvent(status=msg))  
+            return None   
+        
+        # Get the data from the chosen data set and perform inversion
+        pr = Invertor()
+        pr.d_max = self.max_length
+        pr.alpha = self.alpha
+        pr.q_min = self.q_min
+        pr.q_max = self.q_max
+        pr.x = self.current_plottable.x
+        pr.y = self.current_plottable.y
+        pr.has_bck = self.has_bck
+        pr.slit_height = self.slit_height
+        pr.slit_width = self.slit_width
+        
+        # Keep track of the plot window title to ensure that
+        # we can overlay the plots
+        pr.info["plot_group_id"] = self.current_plottable.group_id
+        
+        # Fill in errors if none were provided
+        err = self.current_plottable.dy
+        all_zeros = True
+        if err == None:
+            err = numpy.zeros(len(pr.y)) 
+        else:    
+            for i in range(len(err)):
+                if err[i]>0:
+                    all_zeros = False
+        
+        if all_zeros:        
+            scale = None
+            min_err = 0.0
+            for i in range(len(pr.y)):
+                # Scale the error so that we can fit over several decades of Q
+                if scale==None:
+                    scale = 0.05*math.sqrt(pr.y[i])
+                    min_err = 0.01*pr.y[i]
+                err[i] = scale*math.sqrt( math.fabs(pr.y[i]) ) + min_err
+            message = "The loaded file had no error bars, "
+            message += "statistical errors are assumed."
+            wx.PostEvent(self.parent, StatusEvent(status=message))
+
+        pr.err = err
+        
+        return pr
+
+          
+    def setup_file_inversion(self, alpha, nfunc, d_max, data,
+                             path=None, q_min=None, q_max=None, 
+                             bck=False, height=0, width=0):
+        """
+        """
+        self.alpha = alpha
+        self.nfunc = nfunc
+        self.max_length = d_max
+        self.q_min = q_min
+        self.q_max = q_max
+        self.has_bck = bck
+        self.slit_height = height
+        self.slit_width  = width
+        
+        try:
+            #pr = self._create_file_pr(path)
+            pr = self._create_file_pr(data)
+            if not pr==None:
+                self.pr = pr
+                self.perform_inversion()
+        except:
+            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+          
+    def estimate_file_inversion(self, alpha, nfunc, d_max, data,
+                                path=None, q_min=None, q_max=None, 
+                                bck=False, height=0, width=0):
+        """
+        """
+        self.alpha = alpha
+        self.nfunc = nfunc
+        self.max_length = d_max
+        self.q_min = q_min
+        self.q_max = q_max
+        self.has_bck = bck
+        self.slit_height = height
+        self.slit_width  = width
+        
+        try:
+            pr = self._create_file_pr(data)
+            #pr = self._create_file_pr(path)
+            if not pr is None:
+                self.pr = pr
+                self.perform_estimate()
+        except:
+            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+                
+          
+    def _create_file_pr(self, data):
+        """
+        Create and prepare invertor instance from
+        a file data set.
+        
+        :param path: path of the file to read in 
+        
+        """
+        # Load data
+        #if os.path.isfile(path):
+        """    
+        if self._current_file_data is not None \
+            and self._current_file_data.path==path:
+            # Protect against corrupted data from 
+            # previous failed load attempt
+            if self._current_file_data.x is None:
+                return None
+            x = self._current_file_data.x
+            y = self._current_file_data.y
+            err = self._current_file_data.err
+            
+            message = "The data from this file has already been loaded."
+            wx.PostEvent(self.parent, StatusEvent(status=message))
+        else:
+        """
+        # Reset the status bar so that we don't get mixed up
+        # with old messages. 
+        #TODO: refactor this into a proper status handling
+        wx.PostEvent(self.parent, StatusEvent(status=''))
+        try:
+            class FileData:
+                x = None
+                y = None
+                err = None
+                path = None
+                def __init__(self, path):
+                    self.path = path
+                
+            self._current_file_data = FileData(data.path)
+            self._current_file_data.x = data.x
+            self._current_file_data.y = data.y
+            self._current_file_data.err = data.dy
+            x, y, err = data.x, data.y, data.dy
+        except:
+            load_error(sys.exc_value)
+            return None
+        
+        # If the file contains no data, just return
+        if x is None or len(x) == 0:
+            load_error("The loaded file contains no data")
+            return None
+
+        # If we have not errors, add statistical errors
+        if y is not None:
+            if err == None or numpy.all(err) == 0:
+                err = numpy.zeros(len(y))
+                scale = None
+                min_err = 0.0
+                for i in range(len(y)):
+                    # Scale the error so that we can fit over several decades of Q
+                    if scale == None:
+                        scale = 0.05 * math.sqrt(y[i])
+                        min_err = 0.01 * y[i]
+                    err[i] = scale * math.sqrt(math.fabs(y[i])) + min_err
+                message = "The loaded file had no error bars, "
+                message += "statistical errors are assumed."
+                wx.PostEvent(self.parent, StatusEvent(status=message))
+        
+        try:
+            # Get the data from the chosen data set and perform inversion
+            pr = Invertor()
+            pr.d_max = self.max_length
+            pr.alpha = self.alpha
+            pr.q_min = self.q_min
+            pr.q_max = self.q_max
+            pr.x = x
+            pr.y = y
+            pr.err = err
+            pr.has_bck = self.has_bck
+            pr.slit_height = self.slit_height
+            pr.slit_width = self.slit_width
+            return pr
+        except:
+            load_error(sys.exc_value)
+        return None
+        
+    def perform_estimate(self):
+        """
+        """
+        from pr_thread import EstimatePr
+        from copy import deepcopy
+        
+        # If a thread is already started, stop it
+        if self.estimation_thread != None and \
+            self.estimation_thread.isrunning():
+            self.estimation_thread.stop()
+                
+        pr = self.pr.clone()
+        self.estimation_thread = EstimatePr(pr, self.nfunc,
+                                             error_func=self._thread_error, 
+                                         completefn = self._estimate_completed, 
+                                            updatefn   = None)
+        self.estimation_thread.queue()
+        self.estimation_thread.ready(2.5)
+    
+    def perform_estimateNT(self):
+        """
+        """
+        from pr_thread import EstimateNT
+        from copy import deepcopy
+        
+        # If a thread is already started, stop it
+        if self.estimation_thread != None and self.estimation_thread.isrunning():
+            self.estimation_thread.stop()
+                
+        pr = self.pr.clone()
+        # Skip the slit settings for the estimation
+        # It slows down the application and it doesn't change the estimates
+        pr.slit_height = 0.0
+        pr.slit_width  = 0.0
+        self.estimation_thread = EstimateNT(pr, self.nfunc, 
+                                            error_func=self._thread_error, 
+                                        completefn = self._estimateNT_completed, 
+                                            updatefn   = None)
+        self.estimation_thread.queue()
+        self.estimation_thread.ready(2.5)
+        
+    def perform_inversion(self):
+        """
+        """
+        # Time estimate
+        #estimated = self.elapsed*self.nfunc**2
+        #message = "Computation time may take up to %g seconds" % self.elapsed
+        #wx.PostEvent(self.parent, StatusEvent(status=message))
+        
+        # Start inversion thread
+        self.start_thread()
+        return
+        
+        out, cov = self.pr.lstsq(self.nfunc)
+        
+        # Save useful info
+        self.elapsed = self.pr.elapsed
+        
+        for i in range(len(out)):
+            try:
+                print "%d: %g +- %g" % (i, out[i],
+                                         math.sqrt(math.fabs(cov[i][i])))
+            except: 
+                print "%d: %g +- ?" % (i, out[i])        
+        
+        # Make a plot of I(q) data
+        new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err)
+        new_plot.name = "I_{obs}(q)"
+        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Iq"))
+        # Show I(q) fit
+        self.show_iq(out, self.pr)
+        # Show P(r) fit
+        x_values, x_range = self.show_pr(out, self.pr, cov=cov)
+        
+    def _on_context_inversion(self, event):
+        """
+        """
+        panel = event.GetEventObject()
+        Plugin.on_perspective(self, event=event)
+
+        # If we have more than one displayed plot, make the user choose
+        if len(panel.plots) >= 1 and \
+            panel.graph.selected_plottable in panel.plots:
+            dataset = panel.plots[panel.graph.selected_plottable].name
+        else:
+            logging.info("Prview Error: No data is available")
+            return
+        
+        # Store a reference to the current plottable
+        # If we have a suggested value, use it.
+        try:
+            estimate = float(self.control_panel.alpha_estimate)
+            self.control_panel.alpha = estimate
+        except:
+            self.control_panel.alpha = self.alpha
+            logging.info("Prview :Alpha Not estimate yet")
+            pass
+        try:
+            estimate = int(self.control_panel.nterms_estimate)
+            self.control_panel.nfunc = estimate
+        except:
+            self.control_panel.nfunc = self.nfunc
+            logging.info("Prview : ntemrs Not estimate yet")
+            pass
+        
+        self.current_plottable = panel.plots[panel.graph.selected_plottable]
+        self.set_data([self.current_plottable])
+        self.control_panel.plotname = dataset
+        #self.control_panel.nfunc = self.nfunc
+        self.control_panel.d_max = self.max_length
+        #self.parent.set_perspective(self.perspective)
+        self.control_panel._on_invert(None)
+            
+    def get_panels(self, parent):
+        """
+            Create and return a list of panel objects
+        """
+        from inversion_panel import InversionControl
+        
+        self.parent = parent
+        self.control_panel = InversionControl(self.parent, -1, 
+                                              style=wx.RAISED_BORDER,
+                                              standalone=self.standalone)
+        self.control_panel.set_manager(self)
+        self.control_panel.nfunc = self.nfunc
+        self.control_panel.d_max = self.max_length
+        self.control_panel.alpha = self.alpha
+        self.perspective = []
+        self.perspective.append(self.control_panel.window_name)
+     
+        return [self.control_panel]
+    
+    def set_data(self, data_list=None):
+        """
+        receive a list of data to compute pr
+        """
+        if data_list is None:
+            data_list = []
+        if len(data_list) >= 1:
+            if len(data_list) == 1:
+                data = data_list[0]
+            else:
+                msg = "Pr panel does not allow multiple Data.\n"
+                msg += "Please select one!\n"
+                from pr_widgets import DataDialog
+                dlg = DataDialog(data_list=data_list, text=msg)
+                if dlg.ShowModal() == wx.ID_OK:
+                    data = dlg.get_data()
+            if data is None:
+                return
+            if issubclass(data.__class__, Data1D):
+                try:
+                    wx.PostEvent(self.parent, NewPlotEvent(action='remove',
+                                                group_id=GROUP_ID_IQ_DATA, 
+                                                id=self.data_id))
+                                              
+                    self.data_id = data.id
+                    self.control_panel._change_file(evt=None, data=data)
+                except:
+                     msg = "Prview Set_data: " + str(sys.exc_value)
+                     wx.PostEvent(self.parent, StatusEvent(status=msg,
+                                                            info="error"))
+            else:    
+                msg = "Pr cannot be computed for data of "
+                msg += "type %s" % (data_list[0].__class__.__name__)
+                wx.PostEvent(self.parent, 
+                         StatusEvent(status=msg, info='error'))
+        else:
+            msg = "Pr contain no data"
+            wx.PostEvent(self.parent, StatusEvent(status=msg, info='warning'))
+            
+    def post_init(self):
+        """
+            Post initialization call back to close the loose ends
+            [Somehow openGL needs this call]
+        """
+        if self.standalone:
+            self.parent.set_perspective(self.perspective)
+  
+if __name__ == "__main__":
+    i = Plugin()
+    print i.perform_estimateNT()
+    
+    
+    
+    
Index: prview/src/sans/perspectives/pr/pr_thread.py
===================================================================
--- prview/src/sans/perspectives/pr/pr_thread.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/pr_thread.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,136 @@
+
+################################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#
+#See the license text in license.txt
+#
+#copyright 2009, University of Tennessee
+################################################################################
+
+import sys
+import time
+from data_util.calcthread import CalcThread
+
+class CalcPr(CalcThread):
+    """
+    Compute P(r)
+    """
+    
+    def __init__(self, pr, nfunc=5, error_func=None,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        """
+        """
+        CalcThread.__init__(self,completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.pr = pr
+        self.nfunc = nfunc
+        self.error_func = error_func
+        self.starttime = 0
+        
+    def compute(self):
+        """
+        Perform P(r) inversion
+        """
+        try:
+            self.starttime = time.time()
+            out, cov = self.pr.invert(self.nfunc)
+            #out, cov = self.pr.lstsq(self.nfunc)
+            #out, cov = self.pr.invert_optimize(self.nfunc)
+            elapsed = time.time()-self.starttime
+            self.complete(out=out, cov=cov, pr=self.pr, elapsed=elapsed)
+        except KeyboardInterrupt:
+            # Thread was interrupted, just proceed
+            pass
+        except:
+            if not self.error_func==None:
+                self.error_func("CalcPr.compute: %s" % sys.exc_value)
+
+class EstimatePr(CalcThread):
+    """
+    Estimate P(r)
+    """
+    
+    def __init__(self, pr, nfunc=5, error_func=None,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        CalcThread.__init__(self,completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.pr = pr
+        self.nfunc = nfunc
+        self.error_func = error_func
+        self.starttime = 0
+        
+    def compute(self):
+        """
+        Calculates the estimate
+        """
+        try:            
+            alpha, message, elapsed = self.pr.estimate_alpha(self.nfunc)
+            self.isquit()
+            self.complete(alpha=alpha, message=message, elapsed=elapsed)
+        except KeyboardInterrupt:
+            # Thread was interrupted, just proceed
+            pass
+        except:
+            if not self.error_func==None:
+                self.error_func("EstimatePr.compute: %s" % sys.exc_value)
+
+class EstimateNT(CalcThread):
+    """
+    """
+    def __init__(self, pr, nfunc=5, error_func=None,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        CalcThread.__init__(self,completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.pr = pr
+        self.nfunc = nfunc
+        self.error_func = error_func
+        self.starttime = 0
+        self._time_for_sleep = 0
+        self._sleep_delay = 1.0
+
+
+    def isquit(self):
+        """
+        """
+        CalcThread.isquit(self)
+        if time.time()>self._time_for_sleep+self._sleep_delay:
+            time.sleep(.2)
+            self._time_for_sleep = time.time()
+
+    def compute(self):
+        """
+        Calculates the estimate
+        """
+        try:            
+            t_0 = time.time()
+            self._time_for_sleep = t_0
+            nterms, alpha, message = self.pr.estimate_numterms(self.isquit)
+            t_1 = time.time()-t_0
+            self.isquit()
+            self.complete(nterms=nterms, alpha=alpha, message=message, elapsed=t_1)
+        except KeyboardInterrupt:
+            # Thread was interrupted, just proceed
+            pass
+        except:
+            if not self.error_func==None:
+                self.error_func("EstimatePr2.compute: %s" % sys.exc_value)
Index: prview/src/sans/perspectives/pr/pr_widgets.py
===================================================================
--- prview/src/sans/perspectives/pr/pr_widgets.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/pr_widgets.py	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,212 @@
+
+################################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#
+#See the license text in license.txt
+#
+#copyright 2009, University of Tennessee
+################################################################################
+
+"""
+Text controls for input/output of the main PrView panel
+"""
+
+import wx
+import os
+from wx.lib.scrolledpanel import ScrolledPanel
+class PrTextCtrl(wx.TextCtrl):
+    """
+    Text control for model and fit parameters.
+    Binds the appropriate events for user interactions.
+    """
+    def __init__(self, *args, **kwds):
+        
+        wx.TextCtrl.__init__(self, *args, **kwds)
+        
+        ## Set to True when the mouse is clicked while the whole string is selected
+        self.full_selection = False
+        ## Call back for EVT_SET_FOCUS events
+        _on_set_focus_callback = None
+        # Bind appropriate events
+        self.Bind(wx.EVT_LEFT_UP, self._highlight_text)
+        self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
+
+    def _on_set_focus(self, event):
+        """
+        Catch when the text control is set in focus to highlight the whole
+        text if necessary
+        
+        :param event: mouse event
+        
+        """
+        event.Skip()
+        self.full_selection = True
+        
+    def _highlight_text(self, event):
+        """
+        Highlight text of a TextCtrl only of no text has be selected
+        
+        :param event: mouse event
+        
+        """
+        # Make sure the mouse event is available to other listeners
+        event.Skip()
+        control  = event.GetEventObject()
+        if self.full_selection:
+            self.full_selection = False
+            # Check that we have a TextCtrl
+            if issubclass(control.__class__, wx.TextCtrl):
+                # Check whether text has been selected, 
+                # if not, select the whole string
+                (start, end) = control.GetSelection()
+                if start==end:
+                    control.SetSelection(-1,-1)
+
+class OutputTextCtrl(wx.TextCtrl):
+    """
+    Text control used to display outputs.
+    No editing allowed. The background is 
+    grayed out. User can't select text.
+    """
+    def __init__(self, *args, **kwds):
+        """
+        """
+        wx.TextCtrl.__init__(self, *args, **kwds)
+        self.SetEditable(False)
+        self.SetBackgroundColour(self.GetParent().GetBackgroundColour())
+        
+        # Bind to mouse event to avoid text highlighting
+        # The event will be skipped once the call-back
+        # is called.
+        self.Bind(wx.EVT_MOUSE_EVENTS, self._click)
+        
+    def _click(self, event):
+        """
+        Prevent further handling of the mouse event
+        by not calling Skip().
+        """ 
+        pass
+        
+
+class DataFileTextCtrl(OutputTextCtrl):
+    """
+    Text control used to display only the file name
+    given a full path.
+     
+    :TODO: now that we no longer choose the data file from the panel,
+        it's no longer necessary to pass around the file path. That code
+        should be refactored away and simplified. 
+    """
+    def __init__(self, *args, **kwds):
+        """
+        """
+        OutputTextCtrl.__init__(self, *args, **kwds)
+        self._complete_path = None
+    
+    def SetValue(self, value):
+        """
+        Sets the file name given a path
+        """
+        self._complete_path = str(value)
+        file = os.path.basename(self._complete_path)
+        OutputTextCtrl.SetValue(self, file)
+        
+    def GetValue(self):
+        """
+        Return the full path
+        """
+        return self._complete_path
+    
+class DataDialog(wx.Dialog):
+    """
+    Allow file selection at loading time
+    """
+    def __init__(self, data_list, parent=None, text='', *args, **kwds):
+        wx.Dialog.__init__(self, parent, *args, **kwds)
+        self.list_of_ctrl = []
+        self._do_layout(data_list, text=text)
+        
+    def _do_layout(self, data_list, text=''):
+        """
+        layout the dialog
+        """
+        if not data_list or len(data_list) <= 1:
+            return 
+        # Dialog box properties
+        self.SetTitle("Data Selection")
+        w = 400
+        h = 200
+        self.SetSize((w, h))
+        sizer_main = wx.BoxSizer(wx.VERTICAL)
+        sizer_txt = wx.BoxSizer(wx.VERTICAL)
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        choice_sizer = wx.GridBagSizer(5, 5)
+        #choice_sizer.SetMinSize((w-20, h-50))
+        panel = ScrolledPanel(self, style=wx.RAISED_BORDER, size=(w-20, h-50))
+        panel.SetupScrolling()
+        #add text
+        if text.strip() == "":
+            text = "This Perspective does not allow multiple data !\n"
+            text += "Please select only one Data.\n"
+        text_ctrl = wx.StaticText(self, -1, str(text))
+        sizer_txt.Add(text_ctrl)
+        iy = 0
+        ix = 0
+        rbox = wx.RadioButton(panel, -1, str(data_list[0].name), 
+                                  (10, 10), style= wx.RB_GROUP)
+        rbox.SetValue(True)
+        self.list_of_ctrl.append((rbox, data_list[0]))
+        choice_sizer.Add(rbox, (iy, ix), (1, 1),
+                         wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        for i in range(1, len(data_list)):
+            iy += 1
+            rbox = wx.RadioButton(panel, -1, str(data_list[i].name), (10, 10))
+            rbox.SetValue(False)
+            self.list_of_ctrl.append((rbox, data_list[i]))
+            choice_sizer.Add(rbox, (iy, ix),
+                           (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        panel.SetSizer(choice_sizer)
+        #add sizer
+        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        button_cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
+        sizer_button.Add(button_cancel, 0,
+                          wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
+        button_OK = wx.Button(self, wx.ID_OK, "Ok")
+        button_OK.SetFocus()
+        sizer_button.Add(button_OK, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
+        static_line = wx.StaticLine(self, -1)
+        
+        sizer_txt.Add(panel, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 5)
+        sizer_main.Add(sizer_txt, 1, wx.EXPAND|wx.ALL, 10)
+        sizer_main.Add(static_line, 0, wx.EXPAND, 0)
+        sizer_main.Add(sizer_button, 0, wx.EXPAND|wx.ALL, 10)
+        self.SetSizer(sizer_main)
+        self.Layout()
+        
+    def get_data(self):
+        """
+        return the selected data
+        """
+        for item in self.list_of_ctrl:
+            rbox, data = item
+            if rbox.GetValue():
+                return data 
+def load_error(error=None):
+    """
+    Pop up an error message.
+    
+    :param error: details error message to be displayed
+    """
+    message = "The data file you selected could not be loaded.\n"
+    message += "Make sure the content of your file is properly formatted.\n\n"
+    
+    if error is not None:
+        message += "When contacting the DANSE team, mention the"
+        message += " following:\n%s" % str(error)
+    
+    dial = wx.MessageDialog(None, message, 'Error Loading File',
+                            wx.OK | wx.ICON_EXCLAMATION)
+    dial.ShowModal()    
+
Index: prview/src/sans/perspectives/pr/requirements.txt
===================================================================
--- prview/src/sans/perspectives/pr/requirements.txt	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/requirements.txt	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,32 @@
+1. Thread [done]
+2. Find good defaults. Automate the inversion.
+   -How do we estimate d_max? Guinier fit?
+   -How do we estimate alpha? [done]
+   Compute the residuals once and choose a value that brings the reg term up to 
+   a fraction of the residuals.
+3. When fitting a plottable on a Graph with multiple plottables, remember the one the user chose [done]
+4. What q-range do we need to make it work? What are the constraints? [studied by Raiza]
+5. Create handshake so that a plug-in can set context menu items to its own panels... [done]
+6. Return figure of merit values and suggest the values for d_max and alpha. [alpha, nfunc done]
+7. Show time estimate [done]
+8. Plot the reg term alone to see what it looks like 
+9. Use data loader perspective/plug-in to load data/choose file [done]
+10. Common module for file reading and file choosing 
+11. Define limits of applicability 
+12. Return figures of merit [done]
+13. Errors on output constants [done]
+14. Clean up the InversionDialog [done]
+15. Add online help for inputs and outputs (especially figures of merit) [done]
+16. Add help text to the interface (description of what we are minimizing, to explain alpha and nfunc).
+17. Get rid of all output prints. 
+18. Clean up
+19. Replace help dialog text with a nice image. Mention how to choose alpha and what the suggested alpha means.
+20. If a figure of merit is clearly bad, put its background red
+21. Option to save data. [done]
+22. If you click compute before loading data, an error occurs. [fixed]
+23. Plot I(q) immediately when you choose a new file. [done]
+24. *** probably need to ship the perspectives separately...
+25. If no data is available, disable the Fit button. [Message sent instead]
+26. Remove an added curve...
+27. Add multiple additional data curves.
+28. Use Data Loader.
Index: prview/src/sans/perspectives/pr/test_pr_data.prv
===================================================================
--- prview/src/sans/perspectives/pr/test_pr_data.prv	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
+++ prview/src/sans/perspectives/pr/test_pr_data.prv	(revision 24e093a8dce51131b5845f3696386c1ddb08daf7)
@@ -0,0 +1,922 @@
+<?xml version="1.0" ?>
+<SASroot version="1.0" xmlns="cansas1d/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="cansas1d/1.0 http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd">
+	<SASentry>
+		<Title>
+			
+		</Title>
+		<SASdata>
+			<Idata>
+				<Q unit="1/A">
+					0.00925926
+				</Q>
+				<I unit="1/cm">
+					1246.59
+				</I>
+				<Idev unit="1/cm">
+					74.7954
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.0185185
+				</Q>
+				<I unit="1/cm">
+					612.143
+				</I>
+				<Idev unit="1/cm">
+					56.1434497931
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.0277778
+				</Q>
+				<I unit="1/cm">
+					361.142
+				</I>
+				<Idev unit="1/cm">
+					46.0142235714
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.037037
+				</Q>
+				<I unit="1/cm">
+					211.601
+				</I>
+				<Idev unit="1/cm">
+					38.1456435049
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.0462963
+				</Q>
+				<I unit="1/cm">
+					122.127
+				</I>
+				<Idev unit="1/cm">
+					31.9750194656
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.0555556
+				</Q>
+				<I unit="1/cm">
+					65.2385
+				</I>
+				<Idev unit="1/cm">
+					26.724727241
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.0648148
+				</Q>
+				<I unit="1/cm">
+					30.8914
+				</I>
+				<Idev unit="1/cm">
+					22.2777436502
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.0740741
+				</Q>
+				<I unit="1/cm">
+					12.4737
+				</I>
+				<Idev unit="1/cm">
+					18.7007996951
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.0833333
+				</Q>
+				<I unit="1/cm">
+					3.51371
+				</I>
+				<Idev unit="1/cm">
+					15.775037255
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.0925926
+				</Q>
+				<I unit="1/cm">
+					0.721835
+				</I>
+				<Idev unit="1/cm">
+					13.9657602374
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.101852
+				</Q>
+				<I unit="1/cm">
+					0.583607
+				</I>
+				<Idev unit="1/cm">
+					13.8145276822
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.111111
+				</Q>
+				<I unit="1/cm">
+					1.31084
+				</I>
+				<Idev unit="1/cm">
+					14.4870877916
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.12037
+				</Q>
+				<I unit="1/cm">
+					1.9432
+				</I>
+				<Idev unit="1/cm">
+					14.9267807813
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.12963
+				</Q>
+				<I unit="1/cm">
+					1.94286
+				</I>
+				<Idev unit="1/cm">
+					14.9265654829
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.138889
+				</Q>
+				<I unit="1/cm">
+					1.58912
+				</I>
+				<Idev unit="1/cm">
+					14.6913106929
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.148148
+				</Q>
+				<I unit="1/cm">
+					0.987076
+				</I>
+				<Idev unit="1/cm">
+					14.2198092557
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.157407
+				</Q>
+				<I unit="1/cm">
+					0.456678
+				</I>
+				<Idev unit="1/cm">
+					13.6588901802
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.166667
+				</Q>
+				<I unit="1/cm">
+					0.147595
+				</I>
+				<Idev unit="1/cm">
+					13.1441153991
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.175926
+				</Q>
+				<I unit="1/cm">
+					0.027441
+				</I>
+				<Idev unit="1/cm">
+					12.7583366435
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.185185
+				</Q>
+				<I unit="1/cm">
+					0.0999575
+				</I>
+				<Idev unit="1/cm">
+					13.0240353329
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.194444
+				</Q>
+				<I unit="1/cm">
+					0.198717
+				</I>
+				<Idev unit="1/cm">
+					13.2528539774
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.203704
+				</Q>
+				<I unit="1/cm">
+					0.277667
+				</I>
+				<Idev unit="1/cm">
+					13.3961377459
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.212963
+				</Q>
+				<I unit="1/cm">
+					0.288172
+				</I>
+				<Idev unit="1/cm">
+					13.4135712688
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.222222
+				</Q>
+				<I unit="1/cm">
+					0.220056
+				</I>
+				<Idev unit="1/cm">
+					13.2940298344
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.231481
+				</Q>
+				<I unit="1/cm">
+					0.139378
+				</I>
+				<Idev unit="1/cm">
+					13.1249660457
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.240741
+				</Q>
+				<I unit="1/cm">
+					0.0541106
+				</I>
+				<Idev unit="1/cm">
+					12.8765511076
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.25
+				</Q>
+				<I unit="1/cm">
+					0.0140158
+				</I>
+				<Idev unit="1/cm">
+					12.6748973452
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.259259
+				</Q>
+				<I unit="1/cm">
+					0.0132187
+				</I>
+				<Idev unit="1/cm">
+					12.6688673572
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.268519
+				</Q>
+				<I unit="1/cm">
+					0.0336301
+				</I>
+				<Idev unit="1/cm">
+					12.7896396576
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.277778
+				</Q>
+				<I unit="1/cm">
+					0.0672911
+				</I>
+				<Idev unit="1/cm">
+					12.9238421698
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.287037
+				</Q>
+				<I unit="1/cm">
+					0.0788983
+				</I>
+				<Idev unit="1/cm">
+					12.961767502
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.296296
+				</Q>
+				<I unit="1/cm">
+					0.0764438
+				</I>
+				<Idev unit="1/cm">
+					12.9539934251
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.305556
+				</Q>
+				<I unit="1/cm">
+					0.0555445
+				</I>
+				<Idev unit="1/cm">
+					12.8819565414
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.314815
+				</Q>
+				<I unit="1/cm">
+					0.0280548
+				</I>
+				<Idev unit="1/cm">
+					12.7615891659
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.324074
+				</Q>
+				<I unit="1/cm">
+					0.0111798
+				</I>
+				<Idev unit="1/cm">
+					12.6525589596
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.333333
+				</Q>
+				<I unit="1/cm">
+					0.00156156
+				</I>
+				<Idev unit="1/cm">
+					12.5356607533
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.342593
+				</Q>
+				<I unit="1/cm">
+					0.00830883
+				</I>
+				<Idev unit="1/cm">
+					12.6268169381
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.351852
+				</Q>
+				<I unit="1/cm">
+					0.0186266
+				</I>
+				<Idev unit="1/cm">
+					12.7068342924
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.361111
+				</Q>
+				<I unit="1/cm">
+					0.0275426
+				</I>
+				<Idev unit="1/cm">
+					12.7588775151
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.37037
+				</Q>
+				<I unit="1/cm">
+					0.03192
+				</I>
+				<Idev unit="1/cm">
+					12.7813011446
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.37963
+				</Q>
+				<I unit="1/cm">
+					0.0255329
+				</I>
+				<Idev unit="1/cm">
+					12.747986236
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.388889
+				</Q>
+				<I unit="1/cm">
+					0.0175216
+				</I>
+				<Idev unit="1/cm">
+					12.6995784722
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.398148
+				</Q>
+				<I unit="1/cm">
+					0.0073075
+				</I>
+				<Idev unit="1/cm">
+					12.6168093803
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.407407
+				</Q>
+				<I unit="1/cm">
+					0.0016631
+				</I>
+				<Idev unit="1/cm">
+					12.5378931217
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.416667
+				</Q>
+				<I unit="1/cm">
+					0.00224153
+				</I>
+				<Idev unit="1/cm">
+					12.5494803339
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.425926
+				</Q>
+				<I unit="1/cm">
+					0.0051335
+				</I>
+				<Idev unit="1/cm">
+					12.5923848782
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.435185
+				</Q>
+				<I unit="1/cm">
+					0.0112914
+				</I>
+				<Idev unit="1/cm">
+					12.6534882881
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.444444
+				</Q>
+				<I unit="1/cm">
+					0.0138209
+				</I>
+				<Idev unit="1/cm">
+					12.6734391272
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.453704
+				</Q>
+				<I unit="1/cm">
+					0.0137453
+				</I>
+				<Idev unit="1/cm">
+					12.6728707318
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.462963
+				</Q>
+				<I unit="1/cm">
+					0.0106682
+				</I>
+				<Idev unit="1/cm">
+					12.6482380887
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.472222
+				</Q>
+				<I unit="1/cm">
+					0.00532472
+				</I>
+				<Idev unit="1/cm">
+					12.5947190854
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.481481
+				</Q>
+				<I unit="1/cm">
+					0.00230646
+				</I>
+				<Idev unit="1/cm">
+					12.5506822206
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.490741
+				</Q>
+				<I unit="1/cm">
+					0.000335344
+				</I>
+				<Idev unit="1/cm">
+					12.4982278702
+				</Idev>
+			</Idata>
+			<Idata>
+				<Q unit="1/A">
+					0.5
+				</Q>
+				<I unit="1/cm">
+					0.00177224
+				</I>
+				<Idev unit="1/cm">
+					12.5402178421
+				</Idev>
+			</Idata>
+		</SASdata>
+		<SASsample name="">
+			<ID>
+				
+			</ID>
+		</SASsample>
+		<SASinstrument>
+			<name>
+				
+			</name>
+			<SASsource/>
+		</SASinstrument>
+		<pr_inversion version="1.0">
+			<filename>
+				cyl_400_40.txt [Jun 11 08:19]
+			</filename>
+			<timestamp epoch="1244826799.43">
+				Fri Jun 12 13:13:19 2009
+			</timestamp>
+			<inputs>
+				<nterms>
+					29
+				</nterms>
+				<d_max>
+					140.0
+				</d_max>
+				<alpha>
+					2.0
+				</alpha>
+				<slit_width>
+					0.0
+				</slit_width>
+				<slit_height>
+					0.0
+				</slit_height>
+				<qmin>
+					0.0093
+				</qmin>
+				<qmax>
+					0.5
+				</qmax>
+			</inputs>
+			<outputs>
+				<elapsed>
+					0.006
+				</elapsed>
+				<rg>
+					56.0
+				</rg>
+				<iq0>
+					850.0
+				</iq0>
+				<bck>
+					0.0
+				</bck>
+				<chi2>
+					0.61
+				</chi2>
+				<osc>
+					2.8
+				</osc>
+				<pos>
+					1.0
+				</pos>
+				<pos_err>
+					1.0
+				</pos_err>
+				<alpha_estimate>
+					2.0
+				</alpha_estimate>
+				<nterms_estimate>
+					29
+				</nterms_estimate>
+				<coefficients>
+					[  6.41780305e-03   3.16494199e-03   1.10764485e-03  -5.30787531e-05
+   1.96910118e-04   3.00844919e-05   1.40364798e-04  -8.08130307e-05
+   1.16110977e-04  -6.37007868e-05   1.00121813e-04  -9.47865913e-05
+   1.20431555e-04  -1.08604491e-04   1.19505526e-04  -1.28926818e-04
+   1.56968178e-04  -1.49471379e-04   1.39153500e-04  -1.31886026e-04
+   1.22263690e-04  -9.24565675e-05   1.10946228e-04  -4.84770085e-05
+   6.28747370e-05  -2.20003834e-05   4.55970520e-05  -7.56962703e-06
+   3.22299006e-05]
+				</coefficients>
+				<covariance>
+					[[  5.36167986e-09  -3.20170926e-10   1.69773808e-10  -1.32669708e-10
+    1.42673884e-10  -1.28518512e-10   1.07201287e-10  -9.36422447e-11
+    8.89217929e-11  -8.55020790e-11   8.72056415e-11  -8.73390997e-11
+    8.80083436e-11  -8.89723472e-11   8.95727768e-11  -9.75000205e-11
+    1.06939718e-10  -1.04501050e-10   9.37153935e-11  -9.18007277e-11
+    8.29613573e-11  -6.27350522e-11   7.79650897e-11  -3.29821486e-11
+    4.95750605e-11  -1.43164708e-11   3.71054949e-11  -3.68821336e-12
+    2.99522823e-11]
+ [ -3.20170926e-10   6.94999572e-09   3.79672368e-11   8.59792033e-11
+   -1.61235233e-10   1.02964969e-10  -5.74128071e-11   4.94598630e-11
+   -3.35407691e-11   8.38707139e-12   8.92496376e-12  -2.01409848e-11
+    3.91414592e-11  -5.78931440e-11   7.18932532e-11  -9.29841220e-11
+    1.17013359e-10  -1.20569429e-10   1.16740218e-10  -1.16330532e-10
+    1.02934971e-10  -7.76386231e-11   8.30795140e-11  -4.05291299e-11
+    3.85696751e-11  -1.72592746e-11   1.79224891e-11  -4.55215071e-12
+    7.54742458e-12]
+ [  1.69773808e-10   3.79672368e-11   6.70812821e-09  -2.63652838e-10
+    3.40971509e-10  -2.05067438e-10   1.16289136e-10  -9.72252708e-11
+    6.97320687e-11  -3.06214100e-11   4.96870318e-12   1.06786311e-11
+   -3.92789168e-11   6.48633285e-11  -8.62826574e-11   1.13117855e-10
+   -1.47570963e-10   1.51740633e-10  -1.49196143e-10   1.47253378e-10
+   -1.32597574e-10   9.62319304e-11  -1.07606328e-10   4.71853730e-11
+   -4.97041654e-11   1.82206203e-11  -2.11958201e-11   3.77868694e-12
+   -5.33904606e-12]
+ [ -1.32669708e-10   8.59792033e-11  -2.63652838e-10   5.88523459e-09
+   -3.67796835e-10   9.81127458e-11   6.48886577e-12  -4.81673015e-12
+    3.61439738e-11  -8.83028512e-11   1.26867846e-10  -1.49130389e-10
+    1.90057206e-10  -2.29614825e-10   2.59034953e-10  -3.11148467e-10
+    3.72133353e-10  -3.75354684e-10   3.54412851e-10  -3.50719359e-10
+    3.13331644e-10  -2.35139503e-10   2.67607215e-10  -1.22725241e-10
+    1.41884203e-10  -5.23629947e-11   8.37583954e-11  -1.33981193e-11
+    5.27646124e-11]
+ [  1.42673884e-10  -1.61235233e-10   3.40971509e-10  -3.67796835e-10
+    8.49428183e-09   3.89417746e-10  -3.78182922e-10   2.65195841e-10
+   -2.86401133e-10   3.58085852e-10  -4.20071326e-10   4.48181906e-10
+   -5.17516290e-10   5.80616884e-10  -6.31073001e-10   7.29358378e-10
+   -8.53191616e-10   8.49165755e-10  -7.91743869e-10   7.78106667e-10
+   -7.03543118e-10   5.18949991e-10  -6.24930936e-10   2.64280468e-10
+   -3.58035703e-10   1.08154093e-10  -2.33284897e-10   2.36293411e-11
+   -1.62873049e-10]
+ [ -1.28518512e-10   1.02964969e-10  -2.05067438e-10   9.81127458e-11
+    3.89417746e-10   1.23389221e-08   4.81163639e-10  -2.44798937e-10
+    2.85394041e-10  -3.94720261e-10   4.72379374e-10  -5.09667826e-10
+    5.98093260e-10  -6.84956677e-10   7.44182318e-10  -8.70948538e-10
+    1.01660871e-09  -1.01614564e-09   9.48166369e-10  -9.37901650e-10
+    8.38458572e-10  -6.30889216e-10   7.31136462e-10  -3.34568291e-10
+    4.06487996e-10  -1.45837470e-10   2.58187989e-10  -3.78859157e-11
+    1.78719076e-10]
+ [  1.07201287e-10  -5.74128071e-11   1.16289136e-10   6.48886577e-12
+   -3.78182922e-10   4.81163639e-10   1.45430733e-08   1.20381967e-10
+   -2.12417925e-10   3.71032186e-10  -4.57667520e-10   4.94962832e-10
+   -6.06102038e-10   7.00204895e-10  -7.74328875e-10   9.02430491e-10
+   -1.07006963e-09   1.06264059e-09  -1.00059868e-09   9.81590891e-10
+   -8.89219218e-10   6.51910974e-10  -7.80255128e-10   3.36239137e-10
+   -4.33886798e-10   1.42968279e-10  -2.66419152e-10   3.71674472e-11
+   -1.66295648e-10]
+ [ -9.36422447e-11   4.94598630e-11  -9.72252708e-11  -4.81673015e-12
+    2.65195841e-10  -2.44798937e-10   1.20381967e-10   1.69598579e-08
+    6.74977366e-10  -6.38829402e-10   6.55694074e-10  -6.71815645e-10
+    7.84621369e-10  -8.92233340e-10   9.58946003e-10  -1.11454894e-09
+    1.29471681e-09  -1.28378871e-09   1.20129838e-09  -1.18751620e-09
+    1.05785926e-09  -8.04812116e-10   9.12424748e-10  -4.50057896e-10
+    4.94724352e-10  -2.18846286e-10   3.00665223e-10  -8.01115759e-11
+    1.88512960e-10]
+ [  8.89217929e-11  -3.35407691e-11   6.97320687e-11   3.61439738e-11
+   -2.86401133e-10   2.85394041e-10  -2.12417925e-10   6.74977366e-10
+    2.20626533e-08   1.48747222e-09  -8.60563148e-10   8.12949199e-10
+   -9.62372845e-10   1.06530022e-09  -1.16015735e-09   1.31151485e-09
+   -1.55511555e-09   1.49838396e-09  -1.43745699e-09   1.39018591e-09
+   -1.25883525e-09   9.51905533e-10  -1.06213902e-09   5.75608711e-10
+   -5.26805968e-10   3.47666323e-10  -2.48347916e-10   2.22357210e-10
+   -4.27575376e-11]
+ [ -8.55020790e-11   8.38707139e-12  -3.06214100e-11  -8.83028512e-11
+    3.58085852e-10  -3.94720261e-10   3.71032186e-10  -6.38829402e-10
+    1.48747222e-09   2.66591566e-08   8.70876042e-10  -7.19762047e-10
+    1.05166478e-09  -1.06691957e-09   1.32764162e-09  -1.33205137e-09
+    1.82781717e-09  -1.34489346e-09   1.75755292e-09  -1.48808638e-09
+    1.22339416e-09  -1.50998730e-09   1.42284263e-10  -2.06890057e-09
+   -1.19655455e-09  -2.45145695e-09  -2.05456788e-09  -2.80178697e-09
+   -2.70723848e-09]
+ [  8.72056415e-11   8.92496376e-12   4.96870318e-12   1.26867846e-10
+   -4.20071326e-10   4.72379374e-10  -4.57667520e-10   6.55694074e-10
+   -8.60563148e-10   8.70876042e-10   3.13783655e-08   6.90069639e-10
+   -1.13498134e-09   1.28680194e-09  -1.46867795e-09   1.61474467e-09
+   -2.02903047e-09   1.82614026e-09  -1.89279847e-09   1.75923856e-09
+   -1.58426913e-09   1.33506804e-09  -1.11871317e-09   1.12092035e-09
+   -2.18225256e-10   1.03610564e-09   2.84934645e-10   1.07033966e-09
+    4.79630064e-09]
+ [ -8.73390997e-11  -2.01409848e-11   1.06786311e-11  -1.49130389e-10
+    4.48181906e-10  -5.09667826e-10   4.94962832e-10  -6.71815645e-10
+    8.12949199e-10  -7.19762047e-10   6.90069639e-10   3.73264653e-08
+    1.54361783e-09  -1.78582391e-09   1.73381970e-09  -2.10116064e-09
+    2.31595654e-09  -2.46792065e-09   2.08925570e-09  -2.18755298e-09
+    1.97126664e-09  -1.31564882e-09   2.08464433e-09  -3.27091902e-10
+    1.65358063e-09   2.95896170e-10   1.67294166e-09   7.14407782e-09
+   -1.62799916e-10]
+ [  8.80083436e-11   3.91414592e-11  -3.92789168e-11   1.90057206e-10
+   -5.17516290e-10   5.98093260e-10  -6.06102038e-10   7.84621369e-10
+   -9.62372845e-10   1.05166478e-09  -1.13498134e-09   1.54361783e-09
+    4.44219900e-08   1.91124891e-09  -2.00866271e-09   2.17559453e-09
+   -2.76816863e-09   2.44032693e-09  -2.54051633e-09   2.34494261e-09
+   -2.12304683e-09   1.82575271e-09  -1.51168711e-09   1.62862593e-09
+   -2.86194736e-10   1.72133883e-09   1.00601693e-08  -7.63911957e-10
+   -5.90327602e-10]
+ [ -8.89723472e-11  -5.78931440e-11   6.48633285e-11  -2.29614825e-10
+    5.80616884e-10  -6.84956677e-10   7.00204895e-10  -8.92233340e-10
+    1.06530022e-09  -1.06691957e-09   1.28680194e-09  -1.78582391e-09
+    1.91124891e-09   5.00744455e-08   1.82680567e-09  -2.45645957e-09
+    2.79609086e-09  -3.05043239e-09   2.49580849e-09  -2.66907791e-09
+    2.42028329e-09  -1.58050149e-09   2.74705308e-09  -2.79987999e-10
+    2.36502992e-09   1.42647323e-08  -9.98760828e-10  -9.59925528e-10
+   -5.32703845e-11]
+ [  8.95727768e-11   7.18932532e-11  -8.62826574e-11   2.59034953e-10
+   -6.31073001e-10   7.44182318e-10  -7.74328875e-10   9.58946003e-10
+   -1.16015735e-09   1.32764162e-09  -1.46867795e-09   1.73381970e-09
+   -2.00866271e-09   1.82680567e-09   5.58464309e-08   2.72626743e-09
+   -3.61001275e-09   3.11641110e-09  -3.16094467e-09   2.92038207e-09
+   -2.67799926e-09   2.37022576e-09  -2.14920810e-09   2.34081096e-09
+    1.86476987e-08  -2.44466719e-09  -2.26459750e-09  -9.59815975e-10
+   -8.39868740e-10]
+ [ -9.75000205e-11  -9.29841220e-11   1.13117855e-10  -3.11148467e-10
+    7.29358378e-10  -8.70948538e-10   9.02430491e-10  -1.11454894e-09
+    1.31151485e-09  -1.33205137e-09   1.61474467e-09  -2.10116064e-09
+    2.17559453e-09  -2.45645957e-09   2.72626743e-09   6.41311238e-08
+    4.21810399e-09  -4.28734330e-09   3.23938407e-09  -3.51782001e-09
+    3.26907679e-09  -2.24768502e-09   4.57398103e-09   2.69357001e-08
+   -2.85601796e-09  -3.04875765e-09  -1.21742706e-09  -1.29446569e-09
+   -5.75107533e-11]
+ [  1.06939718e-10   1.17013359e-10  -1.47570963e-10   3.72133353e-10
+   -8.53191616e-10   1.01660871e-09  -1.07006963e-09   1.29471681e-09
+   -1.55511555e-09   1.82781717e-09  -2.02903047e-09   2.31595654e-09
+   -2.76816863e-09   2.79609086e-09  -3.61001275e-09   4.21810399e-09
+    7.09166325e-08   3.99779665e-09  -3.24603038e-09   3.25711624e-09
+   -3.20359753e-09   2.73477502e-09   3.36929846e-08  -5.80202301e-09
+   -6.60146398e-09  -3.36851715e-09  -3.48918244e-09  -1.45515379e-09
+   -1.26027513e-09]
+ [ -1.04501050e-10  -1.20569429e-10   1.51740633e-10  -3.75354684e-10
+    8.49165755e-10  -1.01614564e-09   1.06264059e-09  -1.28378871e-09
+    1.49838396e-09  -1.34489346e-09   1.82614026e-09  -2.46792065e-09
+    2.44032693e-09  -3.05043239e-09   3.11641110e-09  -4.28734330e-09
+    3.99779665e-09   6.88825524e-08   7.20570634e-09  -1.67670708e-09
+   -1.31155060e-09   2.75255363e-08  -1.77158930e-08  -1.63348749e-08
+   -9.99847541e-09  -8.76701248e-09  -4.80097872e-09  -3.59957707e-09
+   -9.39129739e-10]
+ [  9.37153935e-11   1.16740218e-10  -1.49196143e-10   3.54412851e-10
+   -7.91743869e-10   9.48166369e-10  -1.00059868e-09   1.20129838e-09
+   -1.43745699e-09   1.75755292e-09  -1.89279847e-09   2.08925570e-09
+   -2.54051633e-09   2.49580849e-09  -3.16094467e-09   3.23938407e-09
+   -3.24603038e-09   7.20570634e-09   6.55656021e-08   5.54466709e-09
+    3.64103328e-08  -1.28661574e-08  -1.37476567e-08  -8.42579934e-09
+   -8.39107419e-09  -4.75080744e-09  -4.34890329e-09  -2.02203376e-09
+   -1.46039337e-09]
+ [ -9.18007277e-11  -1.16330532e-10   1.47253378e-10  -3.50719359e-10
+    7.78106667e-10  -9.37901650e-10   9.81590891e-10  -1.18751620e-09
+    1.39018591e-09  -1.48808638e-09   1.75923856e-09  -2.18755298e-09
+    2.34494261e-09  -2.66907791e-09   2.92038207e-09  -3.51782001e-09
+    3.25711624e-09  -1.67670708e-09   5.54466709e-09   1.01230220e-07
+   -2.62583425e-09  -3.65427248e-09  -1.25412108e-09  -3.50782877e-09
+   -5.94002070e-10  -1.84913167e-09  -1.87160661e-10  -7.54719002e-10
+    2.41796063e-10]
+ [  8.29613573e-11   1.02934971e-10  -1.32597574e-10   3.13331644e-10
+   -7.03543118e-10   8.38458572e-10  -8.89219218e-10   1.05785926e-09
+   -1.25883525e-09   1.22339416e-09  -1.58426913e-09   1.97126664e-09
+   -2.12304683e-09   2.42028329e-09  -2.67799926e-09   3.26907679e-09
+   -3.20359753e-09  -1.31155060e-09   3.64103328e-08  -2.62583425e-09
+    5.06765326e-08   1.11458057e-08   7.98755599e-09   8.53557702e-09
+    4.36576395e-09   4.40375611e-09   1.95888234e-09   1.74265659e-09
+    2.22871420e-10]
+ [ -6.27350522e-11  -7.76386231e-11   9.62319304e-11  -2.35139503e-10
+    5.18949991e-10  -6.30889216e-10   6.51910974e-10  -8.04812116e-10
+    9.51905533e-10  -1.50998730e-09   1.33506804e-09  -1.31564882e-09
+    1.82575271e-09  -1.58050149e-09   2.37022576e-09  -2.24768502e-09
+    2.73477502e-09   2.75255363e-08  -1.28661574e-08  -3.65427248e-09
+    1.11458057e-08   5.64908344e-08   3.11685074e-08   2.17472346e-08
+    1.78585345e-08   1.18761747e-08   8.82924330e-09   4.89006736e-09
+    2.51309179e-09]
+ [  7.79650897e-11   8.30795140e-11  -1.07606328e-10   2.67607215e-10
+   -6.24930936e-10   7.31136462e-10  -7.80255128e-10   9.12424748e-10
+   -1.06213902e-09   1.42284263e-10  -1.11871317e-09   2.08464433e-09
+   -1.51168711e-09   2.74705308e-09  -2.14920810e-09   4.57398103e-09
+    3.36929846e-08  -1.77158930e-08  -1.37476567e-08  -1.25412108e-09
+    7.98755599e-09   3.11685074e-08   8.14000416e-08   5.22440021e-08
+    3.65474879e-08   2.78035390e-08   1.78852880e-08   1.13664890e-08
+    4.42157514e-09]
+ [ -3.29821486e-11  -4.05291299e-11   4.71853730e-11  -1.22725241e-10
+    2.64280468e-10  -3.34568291e-10   3.36239137e-10  -4.50057896e-10
+    5.75608711e-10  -2.06890057e-09   1.12092035e-09  -3.27091902e-10
+    1.62862593e-09  -2.79987999e-10   2.34081096e-09   2.69357001e-08
+   -5.80202301e-09  -1.63348749e-08  -8.42579934e-09  -3.50782877e-09
+    8.53557702e-09   2.17472346e-08   5.22440021e-08   8.04752904e-08
+    5.42511785e-08   3.84030821e-08   2.66104436e-08   1.58379096e-08
+    7.08595235e-09]
+ [  4.95750605e-11   3.85696751e-11  -4.97041654e-11   1.41884203e-10
+   -3.58035703e-10   4.06487996e-10  -4.33886798e-10   4.94724352e-10
+   -5.26805968e-10  -1.19655455e-09  -2.18225256e-10   1.65358063e-09
+   -2.86194736e-10   2.36502992e-09   1.86476987e-08  -2.85601796e-09
+   -6.60146398e-09  -9.99847541e-09  -8.39107419e-09  -5.94002070e-10
+    4.36576395e-09   1.78585345e-08   3.65474879e-08   5.42511785e-08
+    7.00065497e-08   4.85487842e-08   3.21995798e-08   1.98978762e-08
+    8.30491194e-09]
+ [ -1.43164708e-11  -1.72592746e-11   1.82206203e-11  -5.23629947e-11
+    1.08154093e-10  -1.45837470e-10   1.42968279e-10  -2.18846286e-10
+    3.47666323e-10  -2.45145695e-09   1.03610564e-09   2.95896170e-10
+    1.72133883e-09   1.42647323e-08  -2.44466719e-09  -3.04875765e-09
+   -3.36851715e-09  -8.76701248e-09  -4.75080744e-09  -1.84913167e-09
+    4.40375611e-09   1.18761747e-08   2.78035390e-08   3.84030821e-08
+    4.85487842e-08   5.79311209e-08   3.88041625e-08   2.31612576e-08
+    1.02219793e-08]
+ [  3.71054949e-11   1.79224891e-11  -2.11958201e-11   8.37583954e-11
+   -2.33284897e-10   2.58187989e-10  -2.66419152e-10   3.00665223e-10
+   -2.48347916e-10  -2.05456788e-09   2.84934645e-10   1.67294166e-09
+    1.00601693e-08  -9.98760828e-10  -2.26459750e-09  -1.21742706e-09
+   -3.48918244e-09  -4.80097872e-09  -4.34890329e-09  -1.87160661e-10
+    1.95888234e-09   8.82924330e-09   1.78852880e-08   2.66104436e-08
+    3.21995798e-08   3.88041625e-08   4.32691059e-08   2.66064931e-08
+    1.11577286e-08]
+ [ -3.68821336e-12  -4.55215071e-12   3.77868694e-12  -1.33981193e-11
+    2.36293411e-11  -3.78859157e-11   3.71674472e-11  -8.01115759e-11
+    2.22357210e-10  -2.80178697e-09   1.07033966e-09   7.14407782e-09
+   -7.63911957e-10  -9.59925528e-10  -9.59815975e-10  -1.29446569e-09
+   -1.45515379e-09  -3.59957707e-09  -2.02203376e-09  -7.54719002e-10
+    1.74265659e-09   4.89006736e-09   1.13664890e-08   1.58379096e-08
+    1.98978762e-08   2.31612576e-08   2.66064931e-08   2.84850397e-08
+    1.29670416e-08]
+ [  2.99522823e-11   7.54742458e-12  -5.33904606e-12   5.27646124e-11
+   -1.62873049e-10   1.78719076e-10  -1.66295648e-10   1.88512960e-10
+   -4.27575376e-11  -2.70723848e-09   4.79630064e-09  -1.62799916e-10
+   -5.90327602e-10  -5.32703845e-11  -8.39868740e-10  -5.75107533e-11
+   -1.26027513e-09  -9.39129739e-10  -1.46039337e-09   2.41796063e-10
+    2.22871420e-10   2.51309179e-09   4.42157514e-09   7.08595235e-09
+    8.30491194e-09   1.02219793e-08   1.11577286e-08   1.29670416e-08
+    1.23645969e-08]]
+				</covariance>
+			</outputs>
+		</pr_inversion>
+	</SASentry>
+</SASroot>
