Index: sansview/perspectives/pr/__init__.py
===================================================================
--- sansview/perspectives/pr/__init__.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
+++ sansview/perspectives/pr/__init__.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
@@ -0,0 +1,2 @@
+PLUGIN_ID = "P(r) plug-in 1.0"
+from pr import *
Index: sansview/perspectives/pr/calcthread.py
===================================================================
--- sansview/perspectives/pr/calcthread.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
+++ sansview/perspectives/pr/calcthread.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
@@ -0,0 +1,294 @@
+# This program is public domain
+
+## \file
+#  \brief Abstract class for defining calculation threads.
+#
+
+import thread, traceback
+
+import sys
+if sys.platform.count("darwin")>0:
+    import mactime as time
+else:
+    import time
+
+class CalcThread:
+    """Threaded calculation class.  Inherit from here and specialize
+    the compute() method to perform the appropriate operations for the
+    class.
+
+    If you specialize the __init__ method be sure to call
+    CalcThread.__init__, passing it the keyword arguments for
+    yieldtime, worktime, update and complete.
+    
+    When defining the compute() method you need to include code which
+    allows the GUI to run.  They are as follows:
+        self.isquit()          call frequently to check for interrupts
+        self.update(kw=...)    call when the GUI could be updated
+        self.complete(kw=...)  call before exiting compute()
+    The update() and complete() calls accept field=value keyword
+    arguments which are passed to the called function.  complete()
+    should be called before exiting the GUI function.  A KeyboardInterrupt
+    event is triggered if the GUI signals that the computation should
+    be halted.
+
+    The following documentation should be included in the description
+    of the derived class.
+
+    The user of this class will call the following:
+
+        thread = Work(...,kw=...)  prepare the work thread.
+        thread.queue(...,kw=...)   queue a work unit
+        thread.requeue(...,kw=...) replace work unit on the end of queue
+        thread.reset(...,kw=...)   reset the queue to the given work unit
+        thread.stop()              clear the queue and halt
+        thread.interrupt()         halt the current work unit but continue
+        thread.ready(delay=0.)     request an update signal after delay
+        thread.isrunning()         returns true if compute() is running
+
+    Use queue() when all work must be done.  Use requeue() when intermediate
+    work items don't need to be done (e.g., in response to a mouse move
+    event).  Use reset() when the current item doesn't need to be completed
+    before the new event (e.g., in response to a mouse release event).  Use
+    stop() to halt the current and pending computations (e.g., in response to
+    a stop button).
+    
+    The methods queue(), requeue() and reset() are proxies for the compute()
+    method in the subclass.  Look there for a description of the arguments.
+    The compute() method can be called directly to run the computation in
+    the main thread, but it should not be called if isrunning() returns true.
+
+    The constructor accepts additional keywords yieldtime=0.01 and
+    worktime=0.01 which determine the cooperative multitasking
+    behaviour.  Yield time is the duration of the sleep period
+    required to give other processes a chance to run.  Work time
+    is the duration between sleep periods.
+
+    Notifying the GUI thread of work in progress and work complete
+    is done with updatefn=updatefn and completefn=completefn arguments
+    to the constructor.  Details of the parameters to the functions
+    depend on the particular calculation class, but they will all
+    be passed as keyword arguments.  Details of how the functions
+    should be implemented vary from framework to framework.
+
+    For wx, something like the following is needed:
+
+        import wx, wx.lib.newevent
+        (CalcCompleteEvent, EVT_CALC_COMPLETE) = wx.lib.newevent.NewEvent()
+
+        # methods in the main window class of your application
+        def __init__():
+            ...
+            # Prepare the calculation in the GUI thread.
+            self.work = Work(completefn=self.CalcComplete)
+            self.Bind(EVT_CALC_COMPLETE, self.OnCalcComplete)
+            ...
+            # Bind work queue to a menu event.
+            self.Bind(wx.EVT_MENU, self.OnCalcStart, id=idCALCSTART)
+            ...
+
+        def OnCalcStart(self,event):
+            # Start the work thread from the GUI thread.
+            self.work.queue(...work unit parameters...)
+
+        def CalcComplete(self,**kwargs):
+            # Generate CalcComplete event in the calculation thread.
+            # kwargs contains field1, field2, etc. as defined by
+            # the Work thread class.
+            event = CalcCompleteEvent(**kwargs)
+            wx.PostEvent(self, event)
+
+        def OnCalcComplete(self,event):
+            # Process CalcComplete event in GUI thread.
+            # Use values from event.field1, event.field2 etc. as
+            # defined by the Work thread class to show the results.
+            ...
+    """
+
+    def __init__(self,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        """Prepare the calculator"""
+        self.yieldtime     = yieldtime
+        self.worktime      = worktime
+        self.completefn    = completefn
+        self.updatefn      = updatefn
+        self._interrupting = False
+        self._running      = False
+        self._queue        = []
+        self._lock         = thread.allocate_lock()
+        self._delay        = 1e6
+
+    def queue(self,*args,**kwargs):
+        """Add a work unit to the end of the queue.  See the compute()
+        method for details of the arguments to the work unit."""
+        self._lock.acquire()
+        self._queue.append((args,kwargs))
+        # Cannot do start_new_thread call within the lock
+        self._lock.release()
+        if not self._running:
+            # print "Starting thread"
+            self._time_for_update = time.clock()+1e6
+            thread.start_new_thread(self._run,())
+
+    def requeue(self,*args,**kwargs):
+        """Replace the work unit on the end of the queue.  See the compute()
+        method for details of the arguments to the work unit."""
+        self._lock.acquire()
+        self._queue = self._queue[:-1]
+        self._lock.release()
+        self.queue(*args,**kwargs)
+
+    def reset(self,*args,**kwargs):
+        """Clear the queue and start a new work unit.  See the compute()
+        method for details of the arguments to the work unit."""
+        self.stop()
+        self.queue(*args,**kwargs)
+
+    def stop(self):
+        """Clear the queue and stop the thread.  New items may be
+        queued after stop.  To stop just the current work item, and
+        continue the rest of the queue call the interrupt method"""
+        self._lock.acquire()
+        self._interrupting = True
+        self._queue = []
+        self._lock.release()
+
+    def interrupt(self):
+        """Stop the current work item.  To clear the work queue as
+        well call the stop() method."""
+        self._lock.acquire()
+        self._interrupting = True
+        self._lock.release()
+
+    def isrunning(self): return self._running
+
+    def ready(self, delay=0.):
+        """Ready for another update after delay=t seconds.  Call
+        this for threads which can show intermediate results from
+        long calculations."""
+        self._delay = delay
+        self._lock.acquire()
+        self._time_for_update = time.clock() + delay
+        # print "setting _time_for_update to ",self._time_for_update
+        self._lock.release()
+
+    def isquit(self):
+        """Check for interrupts.  Should be called frequently to
+        provide user responsiveness.  Also yields to other running
+        threads, which is required for good performance on OS X."""
+
+        # Only called from within the running thread so no need to lock
+        if self._running and self.yieldtime>0 and time.clock()>self._time_for_nap:
+            # print "sharing"
+            time.sleep(self.yieldtime)
+            self._time_for_nap = time.clock() + self.worktime
+        if self._interrupting: raise KeyboardInterrupt
+
+    def update(self,**kwargs):
+        """Update GUI with the lastest results from the current work unit."""
+        if self.updatefn != None and time.clock() > self._time_for_update:
+            self._lock.acquire()
+            self._time_for_update = time.clock() + self._delay
+            self._lock.release()
+            
+            #self._time_for_update += 1e6  # No more updates
+            self.updatefn(**kwargs)
+            time.sleep(self.yieldtime)
+            if self._interrupting: raise KeyboardInterrupt
+        else:
+            self.isquit()
+        return
+
+    def complete(self,**kwargs):
+        """Update the GUI with the completed results from a work unit."""
+        if self.completefn != None:
+            self.completefn(**kwargs)
+            time.sleep(self.yieldtime)
+        return
+
+    def compute(self,*args,**kwargs):
+        """Perform a work unit.  The subclass will provide details of
+        the arguments."""
+        raise NotImplemented, "Calculation thread needs compute method"
+
+    def _run(self):
+        """Internal function to manage the thread."""
+        # The code for condition wait in the threading package is
+        # implemented using polling.  I'll accept for now that the
+        # authors of this code are clever enough that polling is
+        # difficult to avoid.  Rather than polling, I will exit the
+        # thread when the queue is empty and start a new thread when
+        # there is more work to be done.
+        while 1:
+            self._lock.acquire()
+            # print "lock aquired"
+            self._time_for_nap = time.clock()+self.worktime
+            self._running = True
+            if self._queue == []: break
+            self._interrupting = False
+            args,kwargs = self._queue[0]
+            self._queue = self._queue[1:]
+            self._lock.release()
+            # print "lock released"
+            try:
+                self.compute(*args,**kwargs)
+            except KeyboardInterrupt:
+                pass
+            except:
+                traceback.print_exc()
+                #print 'CalcThread exception',
+        self._running = False
+
+# ======================================================================
+# Demonstration of calcthread in action
+class CalcDemo(CalcThread):
+    """Example of a calculation thread."""
+    def compute(self,n):
+        total = 0.
+        for i in range(n):
+            #self.update(i=i)
+            for j in range(n):
+                self.isquit()
+                total += j
+        self.complete(total=total)
+
+class CalcCommandline:
+    def __init__(self, n=20000):
+        print thread.get_ident()
+        self.starttime = time.clock()
+        self.done = False
+        self.work = CalcDemo(completefn=self.complete,
+                             updatefn=self.update, yieldtime=0.001)
+        #self.work2 = CalcDemo(completefn=self.complete,
+        #                     updatefn=self.update)
+        #self.work3 = CalcDemo(completefn=self.complete,
+        #                     updatefn=self.update)
+        self.work.queue(n)
+        #self.work2.queue(n)
+        #self.work3.queue(n)
+        print "Expect updates from Main every second and from thread every 2.5 seconds"
+        print ""
+        self.work.ready(.5)
+        while not self.done:
+            time.sleep(1)
+            print "Main thread %d at %.2f"%(thread.get_ident(),time.clock()-self.starttime)
+
+    def update(self,i=0):
+        print "Update i=%d from thread %d at %.2f"%(i,thread.get_ident(),time.clock()-self.starttime)
+        self.work.ready(2.5)
+
+    def complete(self,total=0.0):
+        print "Complete total=%g from thread %d at %.2f"%(total,thread.get_ident(),time.clock()-self.starttime)
+        self.done = True
+
+if __name__ == "__main__":
+    CalcCommandline()
+
+# version
+__id__ = "$Id: calcthread.py 249 2007-06-15 17:03:01Z ziwen $"
+
+# End of file
Index: sansview/perspectives/pr/inversion_panel.py
===================================================================
--- sansview/perspectives/pr/inversion_panel.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
+++ sansview/perspectives/pr/inversion_panel.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
@@ -0,0 +1,1104 @@
+#!/usr/bin/env python
+
+# version
+__id__ = "$Id: aboutdialog.py 1193 2007-05-03 17:29:59Z dmitriy $"
+__revision__ = "$Revision: 1193 $"
+
+import wx
+from sans.guicomm.events import StatusEvent    
+
+class InversionDlg(wx.Dialog):
+    def __init__(self, parent, id, title, plots, file=False, pars=True):
+        
+        # Estimate size
+        nplots = len(plots)
+        # y size for data set only
+        ysize  = 110 + nplots*20
+        # y size including parameters
+        if pars:
+            ysize  += 90
+        
+        wx.Dialog.__init__(self, parent, id, title, size=(250, ysize))
+        self.SetTitle(title)
+
+        # Data set
+        self.datasets = InversionPanel(self, -1, plots)
+        vbox = wx.BoxSizer(wx.VERTICAL)
+
+        vbox.Add(self.datasets)
+
+        # Parameters
+        self.pars_flag = False
+        if pars==True:
+            self.pars_flag = True
+            self.pars = ParsDialog(self, -1, file=file)
+            vbox.Add(self.pars)
+
+        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.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 get_content(self):
+        dataset = self.datasets.get_selected()
+        if self.pars_flag:
+            nfunc, alpha, dmax, file = self.pars.getContent()
+            return dataset, nfunc, alpha, dmax
+        else:
+            return dataset
+    
+    def set_content(self, dataset, nfunc, alpha, dmax):
+        if not dataset==None and dataset in self.datasets.radio_buttons.keys():
+            self.datasets.radio_buttons[dataset].SetValue(True)
+        if self.pars_flag:
+            self.pars.setContent(nfunc, alpha, dmax, None)
+
+class InversionPanel(wx.Panel):
+    
+    def __init__(self, parent, id = -1, plots = None, **kwargs):
+        wx.Panel.__init__(self, parent, id = id, **kwargs)
+        
+        self.plots = plots
+        self.radio_buttons = {}
+        
+        self._do_layout()
+        
+    def _do_layout(self):
+        panel = wx.Panel(self, -1)
+        vbox = wx.BoxSizer(wx.VERTICAL)
+
+        ysize = 30+20*len(self.plots)
+        wx.StaticBox(panel, -1, 'Choose a data set', (5, 5), (230, ysize))
+        ypos = 30
+        self.radio_buttons = {}
+        for item in self.plots.keys():
+            self.radio_buttons[self.plots[item].name] = wx.RadioButton(panel, -1, self.plots[item].name, (15, ypos))
+            ypos += 20
+        
+        vbox.Add(panel)
+
+        self.SetSizer(vbox)
+        
+    def get_selected(self):
+        for item in self.radio_buttons:
+            if self.radio_buttons[item].GetValue():
+                return item
+        return None
+
+class InversionControl(wx.Panel):
+    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
+    
+    ## Fraction of P(r) that is positive 
+    positive = 1.0
+    
+    ## Fraction of P(r) that is greater than zero by more than 1 sigma
+    pos_err  = 1.0
+    
+    def __init__(self, parent, id = -1, plots = None, standalone=False, **kwargs):
+        wx.Panel.__init__(self, parent, id = id, **kwargs)
+        
+        self.plots = plots
+        self.radio_buttons = {}
+        
+        ## 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.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
+        
+        ## Data manager
+        self.manager   = None
+        
+        ## Standalone flage
+        self.standalone = standalone
+        
+        self._do_layout()
+        
+    def __setattr__(self, name, value):
+        """
+            Allow direct hooks to text boxes
+        """
+        if name=='nfunc':
+            self.nfunc_ctl.SetValue(str(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=='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':
+            if self.standalone==False:
+                self.plot_data.SetValue(str(value))
+                self.plot_radio.SetValue(True)
+                self._on_pars_changed(None)
+        elif name=='datafile':
+            self.data_file.SetValue(str(value))
+            self.file_radio.SetValue(True)
+            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 -1.0
+        elif name=='bck':
+            try:
+                return float(self.bck_ctl.GetValue())
+            except:
+                return -1.0
+        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 -1.0
+        elif name=='rg':
+            try:
+                return float(self.rg_ctl.GetValue())
+            except:
+                return -1.0
+        elif name=='iq0':
+            try:
+                return float(self.iq0_ctl.GetValue())
+            except:
+                return -1.0
+        elif name=='oscillation':
+            try:
+                return float(self.osc_ctl.GetValue())
+            except:
+                return -1.0
+        elif name=='pos':
+            try:
+                return float(self.pos_ctl.GetValue())
+            except:
+                return -1.0
+        elif name=='pos_err':
+            return self.pos_err_ctl.GetValue()
+        elif name=='alpha_estimate':
+            return self.alpha_estimate_ctl.GetLabel()
+        elif name=='nterms_estimate':
+            return self.nterms_estimate_ctl.GetLabel()
+        elif name=='plotname':
+            if self.standalone==False:
+                return self.plot_data.GetValue()
+        elif name=='datafile':
+            return self.data_file.GetValue()
+        else:
+            wx.Panel.__getattr__(self, name)
+        
+    def set_manager(self, manager):
+        self.manager = manager
+        # Get data
+        
+        # Push data to form
+        
+        
+    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((320,50))
+        pars_sizer = wx.GridBagSizer(5,5)
+
+        iy = 0
+        if self.standalone==False:
+            self.file_radio = wx.RadioButton(self, -1, "File data:")
+        else:
+            self.file_radio = wx.StaticText(self, -1, "Data file:")
+        self.data_file = wx.TextCtrl(self, -1, size=(100,20))
+        self.data_file.SetEditable(False)
+        self.data_file.SetValue("")
+        id = wx.NewId()
+        choose_button = wx.Button(self, id, "Choose file")
+        self.Bind(wx.EVT_BUTTON, self._change_file, id = id)   
+        pars_sizer.Add(self.file_radio, (iy,0), (1,1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        #pars_sizer.Add(self.data_file, (iy,1), (1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        pars_sizer.Add(self.data_file, (iy,1), (1,1), wx.ADJUST_MINSIZE, 15)
+        pars_sizer.Add(choose_button, (iy,3), (1,1), wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        
+        if self.standalone==False:
+            iy += 1
+            self.plot_radio = wx.RadioButton(self, -1, "Plot data:")
+            self.plot_data = wx.TextCtrl(self, -1, size=(100,20))
+            self.plot_data.SetEditable(False)
+            pars_sizer.Add(self.plot_radio, (iy,0), (1,1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+            pars_sizer.Add(self.plot_data, (iy,1), (1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        
+        self.bck_chk = wx.CheckBox(self, -1, "Estimate background level")
+        self.bck_chk.SetToolTipString("Check box to let the fit estimate the constant background level.")
+        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.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        
+        # ----- Add slit parameters -----
+        if True:
+            sbox = wx.StaticBox(self, -1, "Slit parameters")
+            sboxsizer = wx.StaticBoxSizer(sbox, wx.VERTICAL)
+            sboxsizer.SetMinSize((320,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 = wx.TextCtrl(self, -1, size=(60,20))
+            self.swidth_ctl = wx.TextCtrl(self, -1, size=(60,20))
+            self.sheight_ctl.SetToolTipString("Enter slit height in units of Q or leave blank.")
+            self.swidth_ctl.SetToolTipString("Enter slit width in units of Q or leave blank.")
+            #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_sunits1,    (iy,2), (1,1), wx.LEFT|wx.EXPAND, 10)
+            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.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        
+        
+        # ----- Q range -----
+        qbox = wx.StaticBox(self, -1, "Q range")
+        qboxsizer = wx.StaticBoxSizer(qbox, wx.VERTICAL)
+        qboxsizer.SetMinSize((320,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 = wx.TextCtrl(self, -1, size=(60,20))
+        self.qmax_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.qmin_ctl.SetToolTipString("Select a lower bound for Q or leave blank.")
+        self.qmax_ctl.SetToolTipString("Select an upper bound for Q or leave blank.")
+        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.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        
+        
+        
+
+        # ----- Parameters -----
+        parsbox = wx.StaticBox(self, -1, "Parameters")
+        boxsizer2 = wx.StaticBoxSizer(parsbox, wx.VERTICAL)
+        boxsizer2.SetMinSize((320,50))
+        
+        explanation  = "P(r) is found by fitting a set of base functions to I(Q). "
+        explanation += "The minimization involves a regularization term to ensure "
+        explanation += "a smooth P(r). 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,80))
+        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 = wx.TextCtrl(self, -1, size=(60,20))
+        self.nfunc_ctl.SetToolTipString("Number of terms in the expansion.")
+        self.alpha_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.alpha_ctl.SetToolTipString("Control parameter for the size of the regularization term.")
+        self.dmax_ctl  = wx.TextCtrl(self, -1, size=(60,20))
+        self.dmax_ctl.SetToolTipString("Maximum distance between any two points in the system.")
+        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.SetBackgroundColour('#ffdf85')
+        #self.nterms_estimate_ctl.SetBackgroundColour(self.GetBackgroundColour())
+        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)
+        
+        
+        
+        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)
+
+        boxsizer2.Add(sizer_params, 0)
+        
+        iy_vb += 1
+        vbox.Add(boxsizer2, (iy_vb,0), (1,1), wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+
+
+        # ----- Results -----
+        resbox = wx.StaticBox(self, -1, "Outputs")
+        ressizer = wx.StaticBoxSizer(resbox, wx.VERTICAL)
+        ressizer.SetMinSize((320,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    = wx.TextCtrl(self, -1, size=(60,20))
+        self.rg_ctl.SetEditable(False)
+        self.rg_ctl.SetToolTipString("Radius of gyration for the computed P(r).")
+        self.iq0_ctl   = wx.TextCtrl(self, -1, size=(60,20))
+        self.iq0_ctl.SetEditable(False)
+        self.iq0_ctl.SetToolTipString("Scattering intensity at Q=0 for the computed P(r).")
+        self.bck_ctl   = wx.TextCtrl(self, -1, size=(60,20))
+        self.bck_ctl.SetEditable(False)
+        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 = wx.TextCtrl(self, -1, size=(60,20))
+        self.time_ctl.SetEditable(False)
+        self.time_ctl.SetToolTipString("Computation time for the last inversion, in seconds.")
+        
+        self.chi2_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.chi2_ctl.SetEditable(False)
+        self.chi2_ctl.SetToolTipString("Chi^2 over degrees of freedom.")
+        
+        # Oscillation parameter
+        self.osc_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.osc_ctl.SetEditable(False)
+        self.osc_ctl.SetToolTipString("Oscillation parameter. P(r) for a sphere has an oscillation parameter of 1.1.")
+        
+        # Positive fraction figure of merit
+        self.pos_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.pos_ctl.SetEditable(False)
+        self.pos_ctl.SetToolTipString("Fraction of P(r) that is positive. Theoretically, P(r) is defined positive.")
+        
+        # 1-simga positive fraction figure of merit
+        self.pos_err_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.pos_err_ctl.SetEditable(False)
+        message  = "Fraction of P(r) that is at least 1 standard deviation greater than zero.\n"
+        message += "This figure of merit tells you about the size of the P(r) errors.\n"
+        message += "If it is close to 1 and the other figures of merit are bad, consider changing "
+        message += "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.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+
+        # ----- 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)   
+        
+        id = wx.NewId()
+        button_Reset = wx.Button(self, id, "Reset")
+        button_Reset.SetToolTipString("Reset inversion parameters to default.")
+        self.Bind(wx.EVT_BUTTON, self._on_reset, id = id)   
+        #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_Reset, 0, wx.LEFT|wx.ADJUST_MINSIZE, 10)
+        sizer_button.Add(button_OK, 0, wx.LEFT|wx.ADJUST_MINSIZE, 10)
+        #sizer_button.Add(button_Cancel, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)        
+        iy_vb += 1
+        vbox.Add(sizer_button, (iy_vb,0), (1,1), wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
+
+
+        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 _on_reset(self, evt):
+        """
+            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)
+            
+            if self.standalone==False and self.plot_radio.GetValue():
+                dataset = self.plot_data.GetValue()
+                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)
+            else:
+                path = self.data_file.GetValue()
+                self.manager.estimate_file_inversion(alpha=alpha, nfunc=nfunc, 
+                                                     d_max=dmax, path=path,
+                                                     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 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_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:
+            if self.standalone==False and self.plot_radio.GetValue():
+                dataset = self.plot_data.GetValue()
+                if len(dataset.strip())==0:
+                    message = "No data to invert. Select a data set before 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:
+                path = self.data_file.GetValue()
+                if len(path.strip())==0:
+                    message = "No data to invert. Select a data set before proceeding with P(r) inversion."
+                    wx.PostEvent(self.manager.parent, StatusEvent(status=message))
+                else:
+                    self.manager.setup_file_inversion(alpha=alpha, nfunc=nfunc, 
+                                                      d_max=dmax, path=path,
+                                                      q_min=qmin, q_max=qmax,
+                                                      bck=has_bck,
+                                                      height=height,
+                                                      width=width)
+                
+        else:
+            message = "The P(r) form contains invalid values: please submit it again."
+            wx.PostEvent(self.parent, StatusEvent(status=message))
+        
+    def _change_file(self, evt):
+        """
+            Choose a new input file for I(q)
+        """
+        import os
+        if not self.manager==None:
+            path = self.manager.choose_file()
+            
+            if path and os.path.isfile(path):
+                self.data_file.SetValue(str(path))
+                if self.standalone==False:
+                    self.file_radio.SetValue(True)
+                self.manager.show_data(path, reset=True)
+                self._on_pars_changed(None)
+
+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)
+        #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()
+
+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 = wx.TextCtrl(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)
+
+
+class ParsDialog(wx.Panel):
+    """
+        Dialog box to let the user edit detector settings
+    """
+    
+    def __init__(self, parent, id = id, file=True, **kwargs):
+
+        wx.Panel.__init__(self, parent, id = id, **kwargs)
+        self.file = file
+        
+        self.label_nfunc = wx.StaticText(self, -1, "Number of terms")
+        self.label_alpha = wx.StaticText(self, -1, "Regularization constant")
+        self.label_dmax  = wx.StaticText(self, -1, "Max distance [A]")
+        
+        # Npts, q max
+        self.nfunc_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.alpha_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.dmax_ctl  = wx.TextCtrl(self, -1, size=(60,20))
+
+        self.label_file = None
+        self.file_ctl   = None
+
+        self.static_line_3 = wx.StaticLine(self, -1)
+        
+        
+
+        self.__do_layout()
+
+        self.Fit()
+        
+    def _load_file(self, evt):
+        import os
+        path = None
+        dlg = wx.FileDialog(self, "Choose a file", os.getcwd(), "", "*.txt", wx.OPEN)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            mypath = os.path.basename(path)
+        dlg.Destroy()
+        
+        if path and os.path.isfile(path):
+            self.file_ctl.SetValue(str(path))
+
+        
+    def checkValues(self, event):
+        flag = True
+        try:
+            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()
+            
+        try:
+            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()
+            
+        try:
+            int(self.nfunc_ctl.GetValue())
+            self.nfunc_ctl.SetBackgroundColour(wx.WHITE)
+            self.nfunc_ctl.Refresh()
+        except:
+            flag = False
+            self.nfunc_ctl.SetBackgroundColour("pink")
+            self.nfunc_ctl.Refresh()
+        
+        if flag:
+            event.Skip(True)
+    
+    def setContent(self, nfunc, alpha, dmax, file):
+        self.nfunc_ctl.SetValue(str(nfunc))
+        self.alpha_ctl.SetValue(str(alpha))
+        self.dmax_ctl.SetValue(str(dmax))
+        if self.file:
+            self.file_ctl.SetValue(str(file))
+
+    def getContent(self):
+        nfunc = int(self.nfunc_ctl.GetValue())
+        alpha = float(self.alpha_ctl.GetValue())
+        dmax = float(self.dmax_ctl.GetValue())
+        file = None
+        if self.file:
+            file = self.file_ctl.GetValue()
+        return nfunc, alpha, dmax, file
+
+
+    def __do_layout(self):
+        sizer_main = wx.BoxSizer(wx.VERTICAL)
+        sizer_params = wx.GridBagSizer(5,5)
+
+        iy = 0
+        sizer_params.Add(self.label_nfunc, (iy,0), (1,1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        sizer_params.Add(self.nfunc_ctl,   (iy,1), (1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        sizer_params.Add(self.label_alpha, (iy,0), (1,1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        sizer_params.Add(self.alpha_ctl,   (iy,1), (1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        sizer_params.Add(self.label_dmax, (iy,0), (1,1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        sizer_params.Add(self.dmax_ctl,   (iy,1), (1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        iy += 1
+        if self.file:
+            self.label_file  = wx.StaticText(self, -1, "Input file")
+            self.file_ctl  = wx.TextCtrl(self, -1, size=(120,20))
+            sizer_params.Add(self.label_file, (iy,0), (1,1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+            sizer_params.Add(self.file_ctl,   (iy,1), (1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+
+        sizer_main.Add(sizer_params, 0, wx.EXPAND|wx.ALL, 10)
+        
+        
+        if self.file:
+            sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+            self.button_load = wx.Button(self, 1, "Choose file")
+            self.Bind(wx.EVT_BUTTON, self._load_file, id = 1)        
+            sizer_button.Add(self.button_load, 0, wx.LEFT|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()
+        # end wxGlade
+
+
+# end of class DialogAbout
+
+##### testing code ############################################################
+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: sansview/perspectives/pr/mactime.py
===================================================================
--- sansview/perspectives/pr/mactime.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
+++ sansview/perspectives/pr/mactime.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
@@ -0,0 +1,7 @@
+import time
+
+def clock():
+    return time.time()
+
+def sleep(t):
+    return time.sleep(t)
Index: sansview/perspectives/pr/pr.py
===================================================================
--- sansview/perspectives/pr/pr.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
+++ sansview/perspectives/pr/pr.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
@@ -0,0 +1,1142 @@
+#TODO: Use simview to generate P(r) and I(q) pairs in sansview.
+# 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
+from danse.common.plottools import Data1D, Theory1D
+from sans.guicomm.events import NewPlotEvent, StatusEvent    
+import math, numpy
+from sans.pr.invertor import Invertor
+from DataLoader.loader import Loader
+
+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)$"
+
+import wx.lib
+(NewPrFileEvent, EVT_PR_FILE) = wx.lib.newevent.NewEvent()
+
+
+class Plugin:
+    
+    DEFAULT_ALPHA = 0.0001
+    DEFAULT_NFUNC = 10
+    DEFAULT_DMAX  = 140.0
+    
+    def __init__(self):
+        ## Plug-in name
+        self.sub_menu = "Pr inversion"
+        
+        ## Reference to the parent window
+        self.parent = None
+        
+        ## Simulation window manager
+        self.simview = None
+        
+        ## List of panels for the simulation perspective (names)
+        self.perspective = []
+        
+        ## 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
+        # 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 = True
+        self._normalize_output = False
+        self._scale_output_unity = False
+        
+        ## List of added P(r) plots
+        self._added_plots = {}
+        self._default_Iq  = {}
+        
+        # Log startup
+        logging.info("Pr(r) plug-in started")
+        
+        
+
+    def populate_menu(self, id, owner):
+        """
+            Create a menu for the plug-in
+        """
+        return []
+    
+    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
+        import numpy
+        import pylab
+        import math
+        from sans.guicomm.events import NewPlotEvent            
+        from danse.common.plottools import Data1D, Theory1D
+        
+        # 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)
+        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}")
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Pr"))
+
+        # 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):
+        import numpy
+        import pylab
+        import math
+        from sans.guicomm.events import NewPlotEvent            
+        from danse.common.plottools import Data1D, Theory1D
+        # 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 = Theory1D(x, y_true)
+        new_plot.name = "P_{true}(r)"
+        new_plot.xaxis("\\rm{r}", 'A')
+        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+        
+        
+        #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):
+        import numpy
+        import pylab
+        import math
+        from sans.guicomm.events import NewPlotEvent            
+        from danse.common.plottools import Data1D, Theory1D
+
+        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 = Theory1D(x, y)
+        new_plot.name = IQ_FIT_LABEL
+        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
+        #new_plot.group_id = "test group"
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="I(q)"))
+        
+        # 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 = Theory1D(x, y)
+            new_plot.name = IQ_SMEARED_LABEL
+            new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+            new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
+            #new_plot.group_id = "test group"
+            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="I(q)"))
+        
+        
+    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):
+        import numpy
+        import pylab
+        import math
+        from sans.guicomm.events import NewPlotEvent            
+        from danse.common.plottools import Data1D, Theory1D
+        
+        # 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 = Theory1D(x, y)
+        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}")
+            
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit"))
+        
+        return x, pr.d_max
+        
+        
+    def choose_file(self):
+        """
+        
+        """
+        #TODO: this should be in a common module
+        return self.parent.choose_file()
+                
+                
+    def load(self, path):
+        """
+            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(path)
+        
+        # Use data loader to load file
+        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:
+                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, graph=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
+        """
+        # Look whether this Graph contains P(r) data
+        #if graph.selected_plottable==IQ_DATA_LABEL:
+        for item in graph.plottables:
+            if item.name==PR_FIT_LABEL:
+                m_list = [["Add P(r) data", "Load a data file and display it on this plot", self._on_add_data],
+                       ["Change number of P(r) points", "Change the number of points on the P(r) output", self._on_pr_npts]]
+
+                if self._scale_output_unity==True or self._normalize_output==True:
+                    m_list.append(["Disable P(r) scaling", 
+                                   "Let the output P(r) keep the scale of the data", 
+                                   self._on_disable_scaling])
+                
+                if self._scale_output_unity==False:
+                    m_list.append(["Scale P_max(r) to unity", 
+                                   "Scale P(r) so that its maximum is 1", 
+                                   self._on_scale_unity])
+                    
+                if self._normalize_output==False:
+                    m_list.append(["Normalize P(r) to unity", 
+                                   "Normalize the integral of P(r) to 1", 
+                                   self._on_normalize])
+                    
+                return m_list
+                #return [["Add P(r) data", "Load a data file and display it on this plot", self._on_add_data],
+                #       ["Change number of P(r) points", "Change the number of points on the P(r) output", self._on_pr_npts]]
+
+            elif item.name==graph.selected_plottable:
+                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 = Theory1D(self._added_plots[plot].x, y)
+            new_plot.name = self._added_plots[plot].name
+            new_plot.xaxis("\\rm{r}", 'A')
+            new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+            
+            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 = Theory1D(self._added_plots[plot].x, y)
+            new_plot.name = self._added_plots[plot].name
+            new_plot.xaxis("\\rm{r}", 'A')
+            new_plot.yaxis("\\rm{P(r)} ","cm^{-3}")
+            
+            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:
+                    wx.PostEvent(self.parent, StatusEvent(status="This tool can only read 1D data"))
+                    return
+            
+        except:
+            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+            return
+        
+        filename = os.path.basename(path)
+        
+        #new_plot = Data1D(x, y, dy=err)
+        new_plot = Theory1D(x, y)
+        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):
+        """
+            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 %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}")
+            #new_plot.group_id = "test group"
+            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, 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:
+            try:
+                pr = self._create_file_pr(path)
+                self.pr = pr
+            except:  
+                wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+                return
+              
+        # Make a plot of I(q) data
+        if self.pr.err==None:
+            new_plot = Theory1D(self.pr.x, self.pr.y)
+        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 = "test group"
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="I(q)", reset=reset))
+        
+        # Get Q range
+        self.control_panel.q_min = self.pr.x.min()
+        self.control_panel.q_max = self.pr.x.max()
+            
+
+        
+    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 
+        """
+        # 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
+        
+        # Fill in errors if none were provided
+        if self.current_plottable.dy == None:
+            print "no error", self.current_plottable.name
+            y = numpy.zeros(len(pr.y))
+            for i in range(len(pr.y)):
+                y[i] = math.sqrt(pr.y[i])
+            pr.err = y
+        else:
+            pr.err = self.current_plottable.dy
+        
+        #self.pr = pr
+        return pr
+
+          
+    def setup_file_inversion(self, alpha, nfunc, d_max, path, 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)
+            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, path, 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)
+            if not pr==None:
+                self.pr = pr
+                self.perform_estimate()
+        except:
+            wx.PostEvent(self.parent, StatusEvent(status=sys.exc_value))
+                
+          
+    def _create_file_pr(self, path):
+        """
+            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
+            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=''))
+                x, y, err = self.load(path)
+            
+            # If we have not errors, add statistical errors
+            if err==None and y is not None:
+                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, 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:
+                wx.PostEvent(self.parent, StatusEvent(status="Problem reading data: %s" % 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()
+
+        from inversion_panel import InversionDlg
+        
+        # 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.graph.selected_plottable
+            if False:
+                dialog = InversionDlg(None, -1, "P(r) Inversion", panel.plots, pars=False)
+                dialog.set_content(self.last_data, self.nfunc, self.alpha, self.max_length)
+                if dialog.ShowModal() == wx.ID_OK:
+                    dataset = dialog.get_content()
+                    dialog.Destroy()
+                else:
+                    dialog.Destroy()
+                    return
+        elif len(panel.plots)==1:
+            dataset = panel.plots.keys()[0]
+        else:
+            print "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
+            print "No estimate yet"
+            pass
+        try:
+            estimate = int(self.control_panel.nterms_estimate)
+            self.control_panel.nfunc = estimate
+        except:
+            self.control_panel.nfunc = self.nfunc
+            print "No estimate yet"
+            pass
+        
+        self.current_plottable = panel.plots[dataset]
+        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)
+        
+        self.parent.Bind(EVT_PR_FILE, self._on_new_file)
+        
+        return [self.control_panel]
+    
+    def _on_new_file(self, evt):
+        """
+            Called when the application manager posted an
+            EVT_PR_FILE event. Just prompt the control
+            panel to load a new data file.
+        """
+        self.control_panel._change_file(None)
+    
+    def get_perspective(self):
+        """
+            Get the list of panel names for this perspective
+        """
+        return self.perspective
+    
+    def on_perspective(self, event):
+        """
+            Call back function for the perspective menu item.
+            We notify the parent window that the perspective
+            has changed.
+        """
+        self.parent.set_perspective(self.perspective)
+    
+    def post_init(self):
+        """
+            Post initialization call back to close the loose ends
+            [Somehow openGL needs this call]
+        """
+        self.parent.set_perspective(self.perspective)
+  
+if __name__ == "__main__":
+    i = Plugin()
+    print i.perform_estimateNT()
+    
+    
+    
+    
Index: sansview/perspectives/pr/pr_thread.py
===================================================================
--- sansview/perspectives/pr/pr_thread.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
+++ sansview/perspectives/pr/pr_thread.py	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
@@ -0,0 +1,127 @@
+import sys, time
+from calcthread import CalcThread
+
+class CalcPr(CalcThread):
+    """
+        Compute 2D model
+        This calculation assumes a 2-fold symmetry of the model
+        where points are computed for one half of the detector
+        and I(qx, qy) = I(-qx, -qy) is assumed.
+    """
+    
+    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):
+    """
+        Compute 2D model
+        This calculation assumes a 2-fold symmetry of the model
+        where points are computed for one half of the detector
+        and I(qx, qy) = I(-qx, -qy) is assumed.
+    """
+    
+    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: sansview/perspectives/pr/requirements.txt
===================================================================
--- sansview/perspectives/pr/requirements.txt	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
+++ sansview/perspectives/pr/requirements.txt	(revision 1f723e0382308b0394e7907217159516f6cc5d4c)
@@ -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.
