Index: prview/perspectives/pr/__init__.py
===================================================================
--- prview/perspectives/pr/__init__.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
+++ prview/perspectives/pr/__init__.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
@@ -0,0 +1,2 @@
+PLUGIN_ID = "P(r) plug-in 1.0"
+from pr import *
Index: prview/perspectives/pr/calcthread.py
===================================================================
--- prview/perspectives/pr/calcthread.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
+++ prview/perspectives/pr/calcthread.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
@@ -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: prview/perspectives/pr/inversion_panel.py
===================================================================
--- prview/perspectives/pr/inversion_panel.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
+++ prview/perspectives/pr/inversion_panel.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
@@ -0,0 +1,573 @@
+#!/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_max = 1.5
+    
+    def __init__(self, parent, id = -1, plots = None, **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
+        
+        ## Estimates
+        self.alpha_estimate_ctl = None
+        
+        ## Data manager
+        self.manager   = None
+        
+        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.3g" % value)
+        elif name=='elapsed':
+            self.time_ctl.SetValue("%-5.2g" % value)
+        elif name=='oscillation':
+            self.osc_ctl.SetValue("%-5.2g" % value)
+        elif name=='alpha_estimate':
+            self.alpha_estimate_ctl.SetValue("%-5.2g" % value)
+        elif name=='plotname':
+            self.plot_data.SetValue(str(value))
+            self.plot_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':
+            int(self.nfunc_ctl.GetValue())
+        elif name=='d_max':
+            self.dmax_ctl.GetValue()
+        elif name=='alpha':
+            self.alpha_ctl.GetValue()
+        elif name=='chi2':
+            self.chi2_ctl.GetValue()
+        elif name=='elapsed':
+            self.time_ctl.GetValue()
+        elif name=='oscillation':
+            self.osc_ctl.GetValue()
+        elif name=='alpha_estimate':
+            self.alpha_estimate_ctl.GetValue()
+        elif name=='plotname':
+            self.plot_data.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):
+        #panel = wx.Panel(self, -1)
+        vbox = wx.BoxSizer(wx.VERTICAL)
+
+        # ----- I(q) data -----
+        databox = wx.StaticBox(self, -1, "I(q) data")
+        
+        boxsizer1 = wx.StaticBoxSizer(databox, wx.VERTICAL)
+        boxsizer1.SetMinSize((320,50))
+        pars_sizer = wx.GridBagSizer(5,5)
+
+        iy = 0
+        self.file_radio = wx.RadioButton(self, -1, "File data:")
+        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(choose_button, (iy,3), (1,1), wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        
+        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)
+        
+        boxsizer1.Add(pars_sizer, 0, wx.EXPAND)
+        vbox.Add(boxsizer1)
+
+        # ----- Parameters -----
+        parsbox = wx.StaticBox(self, -1, "Parameters")
+        boxsizer2 = wx.StaticBoxSizer(parsbox, wx.VERTICAL)
+        boxsizer2.SetMinSize((320,50))
+        
+        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]")
+        label_sugg  = wx.StaticText(self, -1, "Suggested value")
+        
+        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.alpha_estimate_ctl  = wx.TextCtrl(self, -1, size=(60,20))
+        self.alpha_estimate_ctl.Enable(False)
+        
+        # EVT_TEXT would trigger an event for each character entered
+        self.nfunc_ctl.Bind(wx.EVT_KILL_FOCUS, self._on_pars_changed)
+        #self.alpha_ctl.Bind(wx.EVT_KILL_FOCUS, self._on_pars_changed)
+        self.dmax_ctl.Bind(wx.EVT_KILL_FOCUS, self._on_pars_changed)
+        self.Bind(wx.EVT_TEXT_ENTER, self._on_pars_changed)
+        
+        sizer_params = wx.GridBagSizer(5,5)
+
+        iy = 0
+        sizer_params.Add(label_sugg, (iy,2), (1,1), wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        iy += 1
+        sizer_params.Add(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.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        iy += 1
+        sizer_params.Add(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.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        sizer_params.Add(self.alpha_estimate_ctl,   (iy,2), (1,1), wx.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        iy += 1
+        sizer_params.Add(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.RIGHT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+
+        boxsizer2.Add(sizer_params, 0)
+        vbox.Add(boxsizer2)
+
+        # ----- Results -----
+        resbox = wx.StaticBox(self, -1, "Outputs")
+        ressizer = wx.StaticBoxSizer(resbox, wx.VERTICAL)
+        ressizer.SetMinSize((320,50))
+        
+        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")
+        
+        self.time_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.time_ctl.SetEditable(False)
+        self.chi2_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.chi2_ctl.SetEditable(False)
+        self.osc_ctl = wx.TextCtrl(self, -1, size=(60,20))
+        self.osc_ctl.SetEditable(False)
+        
+        sizer_res = wx.GridBagSizer(5,5)
+
+        iy = 0
+        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)
+
+        ressizer.Add(sizer_res, 0)
+        vbox.Add(ressizer)
+
+        # ----- Buttons -----
+        static_line = wx.StaticLine(self, -1)
+        vbox.Add(static_line, 0, wx.EXPAND|wx.TOP, 10)
+        
+        id = wx.NewId()
+        button_OK = wx.Button(self, id, "Compute")
+        self.Bind(wx.EVT_BUTTON, self._on_invert, 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_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)
+        
+    def _on_pars_changed(self, evt):
+        """
+            Called when an input parameter has changed
+            We will estimate the alpha parameter behind the
+            scenes. 
+        """
+        flag, alpha, dmax, nfunc = self._read_pars()
+        
+        # If the pars are valid, estimate alpha
+        if flag:
+            if self.plot_radio.GetValue():
+                dataset = self.plot_data.GetValue()
+                self.manager.estimate_plot_inversion(alpha=alpha, nfunc=nfunc, 
+                                                     d_max=dmax)
+            else:
+                path = self.data_file.GetValue()
+                self.manager.estimate_file_inversion(alpha=alpha, nfunc=nfunc, 
+                                                     d_max=dmax, path=path)
+        
+        
+    def _read_pars(self):    
+        alpha = 0
+        nfunc = 5
+        dmax  = 120
+        
+        flag = True
+        
+        # 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())
+            self.nfunc_ctl.SetBackgroundColour(wx.WHITE)
+            self.nfunc_ctl.Refresh()
+        except:
+            flag = False
+            self.nfunc_ctl.SetBackgroundColour("pink")
+            self.nfunc_ctl.Refresh()
+        
+        return flag, alpha, dmax, nfunc
+    
+    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 = self._read_pars()
+        
+        if flag:
+            if self.plot_radio.GetValue():
+                dataset = self.plot_data.GetValue()
+                self.manager.setup_plot_inversion(alpha=alpha, nfunc=nfunc, 
+                                                  d_max=dmax)
+            else:
+                path = self.data_file.GetValue()
+                self.manager.setup_file_inversion(alpha=alpha, nfunc=nfunc, 
+                                                  d_max=dmax, path=path)
+                
+        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))
+                self.file_radio.SetValue(True)
+                self._on_pars_changed(None)
+        
+
+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()
+        plots = {}
+        plots['a'] = TestPlot("data 1")
+        plots['b'] = TestPlot("data 2")
+        #plots['c'] = TestPlot("data 3")
+        #plots['d'] = TestPlot("data 1")
+        #plots['e'] = TestPlot("data 2")
+        #plots['f'] = TestPlot("data 3")
+        dialog = InversionDlg(None, -1, "P(r) parameters", plots)
+        if dialog.ShowModal() == wx.ID_OK:
+            print dialog.get_content()
+        dialog.Destroy()
+        
+        return 1
+
+# end of class MyApp
+
+if __name__ == "__main__":
+    app = MyApp(0)
+    app.MainLoop()
+    
+##### end of testing code #####################################################    
Index: prview/perspectives/pr/pars_dialog.py
===================================================================
--- prview/perspectives/pr/pars_dialog.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
+++ prview/perspectives/pr/pars_dialog.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+
+# version
+__id__ = "$Id: aboutdialog.py 1193 2007-05-03 17:29:59Z dmitriy $"
+__revision__ = "$Revision: 1193 $"
+
+import wx
+
+
+class ParsDialog(wx.Dialog):
+    """
+        Dialog box to let the user edit detector settings
+    """
+    
+    def __init__(self, *args, **kwds):
+
+        kwds["style"] = wx.DEFAULT_DIALOG_STYLE
+        wx.Dialog.__init__(self, *args, **kwds)
+        
+        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]")
+        self.label_file  = wx.StaticText(self, -1, "Input file")
+        
+        # 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.file_ctl  = wx.TextCtrl(self, -1, size=(120,20))
+
+        self.static_line_3 = wx.StaticLine(self, -1)
+        
+        
+        self.button_OK = wx.Button(self, wx.ID_OK, "OK")
+        self.Bind(wx.EVT_BUTTON, self.checkValues, self.button_OK)
+        
+        self.button_Cancel = wx.Button(self, wx.ID_CANCEL, "Cancel")
+        
+        self.button_load = wx.Button(self, 1, "Choose file")
+        self.Bind(wx.EVT_BUTTON, self._load_file, id = 1)        
+
+        self.__set_properties()
+        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))
+        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 = self.file_ctl.GetValue()
+        return nfunc, alpha, dmax, file
+
+    def __set_properties(self):
+        self.SetTitle("P(r) inversion parameters")
+        self.SetSize((600, 595))
+
+    def __do_layout(self):
+        sizer_main = wx.BoxSizer(wx.VERTICAL)
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        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
+        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)
+        sizer_main.Add(self.static_line_3, 0, wx.EXPAND, 0)
+        
+        
+        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        sizer_button.Add(self.button_load, 0, wx.LEFT|wx.ADJUST_MINSIZE, 10)
+        sizer_button.Add(self.button_OK, 0, wx.LEFT|wx.ADJUST_MINSIZE, 10)
+        sizer_button.Add(self.button_Cancel, 0, wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)
+        
+        
+        sizer_main.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
+        self.SetAutoLayout(True)
+        self.SetSizer(sizer_main)
+        self.Layout()
+        self.Centre()
+        # end wxGlade
+
+
+# end of class DialogAbout
+
+##### testing code ############################################################
+class MyApp(wx.App):
+    def OnInit(self):
+        wx.InitAllImageHandlers()
+        dialog = ParsDialog(None, -1, "")
+        self.SetTopWindow(dialog)
+        dialog.setContent(10, 0.05)
+        nfunc, alpha = dialog.getContent()
+        print nfunc, alpha
+        dialog.Destroy()
+        return 1
+
+# end of class MyApp
+
+if __name__ == "__main__":
+    app = MyApp(0)
+    app.MainLoop()
+    
+##### end of testing code #####################################################    
Index: prview/perspectives/pr/pr.py
===================================================================
--- prview/perspectives/pr/pr.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
+++ prview/perspectives/pr/pr.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
@@ -0,0 +1,551 @@
+#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 wx
+from sans.guitools.plottables import Data1D, Theory1D
+from sans.guicomm.events import NewPlotEvent, StatusEvent    
+import math, numpy
+from sans.pr.invertor import Invertor
+
+class Plugin:
+    
+    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      = 0.0001
+        self.nfunc      = 10
+        self.max_length = 140.0
+        ## Remember last plottable processed
+        self.last_data  = "sphere_60_q0_2.txt"
+        ## Time elapsed for last computation [sec]
+        # Start with a good default
+        self.elapsed = 0.022
+        
+        ## Current invertor
+        self.invertor    = 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
+
+    def populate_menu(self, id, owner):
+        """
+            Create a menu for the plug-in
+        """
+        import wx
+        shapes = wx.Menu()
+
+        id = wx.NewId()
+        shapes.Append(id, '&Sphere test')
+        wx.EVT_MENU(owner, id, self._fit_pr)
+        return [(id, shapes, "P(r)")]
+    
+    def _fit_pr(self, evt):
+        from sans.pr.invertor import Invertor
+        import numpy
+        import pylab
+        import math
+        from sans.guicomm.events import NewPlotEvent            
+        from sans.guitools.plottables 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, 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 sans.guitools.plottables 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 show_iq(self, out, pr, q=None):
+        import numpy
+        import pylab
+        import math
+        from sans.guicomm.events import NewPlotEvent            
+        from sans.guitools.plottables 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
+                
+        x = pylab.arange(0.001, 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 = "I_{fit}(q)"
+        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"))
+        
+        
+        
+        
+    def show_pr(self, out, pr, cov=None):
+        import numpy
+        import pylab
+        import math
+        from sans.guicomm.events import NewPlotEvent            
+        from sans.guitools.plottables import Data1D, Theory1D
+        
+        # Show P(r)
+        x = pylab.arange(0.0, pr.d_max, pr.d_max/51.0)
+    
+        y = numpy.zeros(len(x))
+        dy = numpy.zeros(len(x))
+        y_true = numpy.zeros(len(x))
+
+        sum = 0.0
+        for i in range(len(x)):
+            if cov==None:
+                value = pr.pr(out, x[i])
+            else:
+                (value, dy[i]) = pr.pr_err(out, cov, x[i])
+            sum += value
+            y[i] = value
+            
+        y = y/sum*pr.d_max/len(x)
+        dy = dy/sum*pr.d_max/len(x)
+        
+        if cov==None:
+            new_plot = Theory1D(x, y)
+        else:
+            new_plot = Data1D(x, y, dy=dy)
+        new_plot.name = "P_{fit}(r)"
+        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 = "sphere_test_data.txt"):
+        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)
+        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])
+                    data_x = numpy.append(data_x, x)
+                    data_y = numpy.append(data_y, y)
+                    try:
+                        scale = 0.05/math.sqrt(data_x[0])
+                    except:
+                        scale = 1.0
+                    #data_err = numpy.append(data_err, 10.0*math.sqrt(y)+1000.0)
+                    data_err = numpy.append(data_err, scale*math.sqrt(y))
+                except:
+                    print "Error reading line: ", line
+                    print sys.exc_value
+                   
+        print "Lines read:", len(data_x)
+        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, plot_id=None):
+        """
+            Get the context menu items available for P(r)
+            @param plot_id: Unique ID of a plot, so that we can recognize those
+                            that we created
+            @return: a list of menu items with call-back function
+        """
+        return [["Compute P(r)", "Compute P(r) from distribution", self._on_context_inversion]]
+    
+
+    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, 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
+    
+    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
+        """
+        # Save useful info
+        self.elapsed = elapsed
+        message = "Computation completed in %g seconds [chi2=%g]" % (elapsed, pr.chi2)
+        wx.PostEvent(self.parent, StatusEvent(status=message))
+
+        # 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)
+        
+        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, self.pr.err)
+        new_plot.name = "I_{obs}(q)"
+        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)  
+        
+        # Popup result panel
+        #result_panel = InversionResults(self.parent, -1, style=wx.RAISED_BORDER)
+        
+    def setup_plot_inversion(self, alpha, nfunc, d_max):
+        self.alpha = alpha
+        self.nfunc = nfunc
+        self.max_length = d_max
+        
+        self._create_plot_pr()
+        self.perform_inversion()
+
+    def estimate_plot_inversion(self, alpha, nfunc, d_max):
+        self.alpha = alpha
+        self.nfunc = nfunc
+        self.max_length = d_max
+        
+        self._create_plot_pr()
+        self.perform_estimate()
+
+    def _create_plot_pr(self):
+        """
+            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.x = self.current_plottable.x
+        pr.y = self.current_plottable.y
+        
+        # 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
+
+          
+    def setup_file_inversion(self, alpha, nfunc, d_max, path):
+        self.alpha = alpha
+        self.nfunc = nfunc
+        self.max_length = d_max
+        
+        self._create_file_pr(path)
+        
+        self.perform_inversion()
+          
+    def estimate_file_inversion(self, alpha, nfunc, d_max, path):
+        self.alpha = alpha
+        self.nfunc = nfunc
+        self.max_length = d_max
+        
+        if self._create_file_pr(path):
+            self.perform_estimate()
+          
+    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):
+            x, y, err = self.load(path)
+            
+            # Get the data from the chosen data set and perform inversion
+            pr = Invertor()
+            pr.d_max = self.max_length
+            pr.alpha = self.alpha
+            pr.x = x
+            pr.y = y
+            pr.err = err
+            
+            self.pr = pr
+            return True
+        return False
+        
+    def perform_estimate(self):
+        print "ESTIMATE"
+        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_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:
+            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
+        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.control_panel.alpha = self.alpha
+        self.parent.set_perspective(self.perspective)
+            
+    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)
+        self.control_panel.set_manager(self)
+        self.control_panel.nfunc = self.nfunc
+        self.control_panel.d_max = self.max_length
+        self.control_panel.alpha = self.alpha
+        
+        self.perspective = []
+        self.perspective.append(self.control_panel.window_name)
+        return [self.control_panel]
+    
+    def 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]
+        """
+        pass
+    
Index: prview/perspectives/pr/pr_thread.py
===================================================================
--- prview/perspectives/pr/pr_thread.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
+++ prview/perspectives/pr/pr_thread.py	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
@@ -0,0 +1,135 @@
+import sys, time
+from calcthread import CalcThread
+from sans.pr.invertor import Invertor
+import numpy
+from config import printEVT
+
+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 isquit(self):
+        try:
+            CalcThread.isquit(self)
+        except KeyboardInterrupt:
+            printEVT("P(r) calc interrupted")
+            raise KeyboardInterrupt
+        
+    def compute(self):
+        import time
+        try:
+            self.starttime = time.time()
+            #out, cov = self.pr.invert(self.nfunc)
+            out, cov = self.pr.lstsq(self.nfunc)
+            elapsed = time.time()-self.starttime
+            self.complete(out=out, cov=cov, pr=self.pr, elapsed=elapsed)
+        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 isquit(self):
+        try:
+            CalcThread.isquit(self)
+        except KeyboardInterrupt:
+            printEVT("P(r) calc interrupted")
+            raise KeyboardInterrupt
+        
+    def compute(self):
+        import time
+        try:
+            
+            print "Alpha  Oscill"
+            
+            self.starttime = time.time()
+            # If the current alpha is zero, try
+            # another value
+            if self.pr.alpha<=0:
+                self.pr.alpha = 0.0001
+                 
+            out, cov = self.pr.lstsq(self.nfunc)
+            elapsed = time.time()-self.starttime
+            
+
+            # Take the default and try to find 
+            # a better value
+            best_alpha = self.pr.alpha
+            best_osc   = self.pr.oscillations(out) 
+            
+            print best_alpha, best_osc
+            
+            alpha = self.pr.suggested_alpha
+            print "initial:", alpha
+
+            # Look at smaller values
+            for i in range(5):
+                self.pr.alpha = (0.1)**(i)*alpha
+                out, cov = self.pr.lstsq(self.nfunc)
+                osc = self.pr.oscillations(out) 
+                print self.pr.alpha, osc
+                if  osc < best_osc:
+                    best_osc = osc
+                    best_alpha = alpha
+            
+            ## Look at larger values
+            #for i in range(4):
+            #    self.pr.alpha = (10.0)**(i+1)*alpha
+            #    out, cov = self.pr.lstsq(self.nfunc)
+            #    osc = self.pr.oscillations(out) 
+            #    print self.pr.alpha, osc
+            #    if  osc < best_osc:
+            #        best_osc = osc
+            #        best_alpha = alpha
+            
+            
+            self.complete(alpha=best_alpha, elapsed=elapsed)
+            
+            
+            
+            
+        except:
+            if not self.error_func==None:
+                printEVT("EstimatePr.compute: %s" % sys.exc_value)
+
+    
Index: prview/perspectives/pr/requirements.txt
===================================================================
--- prview/perspectives/pr/requirements.txt	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
+++ prview/perspectives/pr/requirements.txt	(revision f3d51f6ec6277f40c94acc80d658917deab47a7a)
@@ -0,0 +1,19 @@
+1. Thread
+2. Find good defaults. Automate the inversion.
+   -How do we estimate d_max?
+   -How do we estimate alpha?
+   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?
+5. Create handshake so that a plug-in can set context menu items to its own panels...
+6. Return figure of merit values and suggest the values for d_max and alpha.
+7. Show time estimate
+8. Plot the reg term alone to see what it looks like
+9. Use data loader perspective/plug-in to load data/choose file
+10. Common module for file reading and file choosing
+11. Define limits of applicability
+12. Return figures of merit
+13. Errors on output constants
+14. Clean up the InversionDialog
+15. Add online help for inputs and outputs (especially figures of merit)
