Index: src/sas/qtgui/Perspectives/Inversion/DMaxExplorerWidget.py
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/DMaxExplorerWidget.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
+++ src/sas/qtgui/Perspectives/Inversion/DMaxExplorerWidget.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+"""
+Dialog panel to explore the P(r) inversion results for a range
+of D_max value. User picks a number of points and a range of
+distances, then can toggle between inversion outputs and see
+their distribution as a function of D_max.
+"""
+
+# global
+import sys
+import os
+import logging
+import numpy as np
+from PyQt5 import QtCore
+from PyQt5 import QtGui
+from PyQt5 import QtWidgets
+
+from twisted.internet import threads
+
+# sas-global
+from sas.qtgui.Plotting.PlotterData import Data1D
+from sas.qtgui.Plotting.Plotter import PlotterWidget
+import sas.qtgui.Utilities.GuiUtils as GuiUtils
+
+# local
+from .UI.dmax import Ui_DmaxExplorer
+# from InvariantDetails import DetailsDialog
+# from InvariantUtils import WIDGETS
+
+logger = logging.getLogger(__name__)
+
+def enum(*sequential, **named):
+    enums = dict(list(zip(sequential, list(range(len(sequential))))), **named)
+    return type('Enum', (), enums)
+
+W = enum( 'NPTS',           #0
+          'DMIN',               #1
+          'DMAX',               #2
+          'VARIABLE',         #3
+)
+
+class DmaxWindow(QtWidgets.QDialog, Ui_DmaxExplorer):
+    # The controller which is responsible for managing signal slots connections
+    # for the gui and providing an interface to the data model.
+    name = "Dmax Explorer"  # For displaying in the combo box
+
+    def __init__(self, pr_state, nfunc, parent=None):
+        super(DmaxWindow, self).__init__()
+        self.setupUi(self)
+
+        self.setWindowTitle("Dâââ Explorer")
+
+        self.pr_state = pr_state
+        self.nfunc = nfunc
+        self.communicator = GuiUtils.Communicate()
+
+        self.plot = PlotterWidget(self, self)
+        self.hasPlot = None
+        self.verticalLayout.insertWidget(0, self.plot)
+
+        # Let's choose the Standard Item Model.
+        self.model = QtGui.QStandardItemModel(self)
+        self.mapper = None
+
+        # # Connect buttons to slots.
+        # # Needs to be done early so default values propagate properly.
+        self.setupSlots()
+
+        # Set up the model.
+        self.setupModel()
+
+        # # Set up the mapper
+        self.setupMapper()
+
+    def setupSlots(self):
+        self.closeButton.clicked.connect(self.close)
+
+        self.model.itemChanged.connect(self.modelChanged)
+
+    def setupModel(self):
+        self.model.setItem(W.NPTS, QtGui.QStandardItem(str(self.nfunc)))
+        self.model.setItem(W.DMIN,
+                           QtGui.QStandardItem(
+                               str(0.9*self.pr_state.d_max)))
+        self.model.setItem(W.DMAX,
+                           QtGui.QStandardItem(
+                               str(1.1*self.pr_state.d_max)))
+        self.model.setItem(W.VARIABLE,
+                           QtGui.QStandardItem( "ÏÂ²/dof"))
+
+    def setupMapper(self):
+        self.mapper = QtWidgets.QDataWidgetMapper(self)
+        self.mapper.setOrientation(QtCore.Qt.Vertical)
+        self.mapper.setModel(self.model)
+
+        self.mapper.addMapping(self.Npts, W.NPTS)
+        self.mapper.addMapping(self.minDist, W.DMIN)
+        self.mapper.addMapping(self.maxDist, W.DMAX)
+        self.mapper.addMapping(self.dependentVariable, W.VARIABLE)
+
+        self.mapper.toFirst()
+
+    def modelChanged(self, item):
+        if not self.mapper:
+            return
+
+        iq0 = []
+        rg = []
+        pos = []
+        pos_err = []
+        osc = []
+        bck = []
+        chi2 = []
+
+        xs = np.linspace(float(self.model.item(W.DMIN).text()),
+                         float(self.model.item(W.DMAX).text()),
+                         float(self.model.item(W.NPTS).text()))
+
+        original = self.pr_state.d_max
+        for x in xs:
+            self.pr_state.d_max = x
+            try:
+                out, cov = self.pr_state.invert(self.pr_state.nfunc)
+
+                iq0.append(self.pr_state.iq0(out))
+                rg.append(self.pr_state.rg(out))
+                pos.append(self.pr_state.get_positive(out))
+                pos_err.append(self.pr_state.get_pos_err(out, cov))
+                osc.append(self.pr_state.oscillations(out))
+                bck.append(self.pr_state.background)
+                chi2.append(self.pr_state.chi2)
+            except:
+                # This inversion failed, skip this D_max value
+                msg = "ExploreDialog: inversion failed "
+                msg += "for D_max=%s\n%s" % (str(x), sys.exc_info()[1])
+                print(msg)
+                # logger.error(msg)
+
+        #Return the invertor to its original state
+        self.pr_state.d_max = original
+        self.pr_state.invert(self.nfunc)
+
+        plotter = str(self.model.item(W.VARIABLE).text())
+        if plotter == "ÏÂ²/dof":
+            ys = chi2
+        elif plotter == "I(Q=0)":
+            ys = iq0
+        elif plotter == "Rg":
+            ys = rg
+        elif plotter == "Oscillation parameter":
+            ys = osc
+        elif plotter == "Background":
+            ys = bck
+        elif plotter == "Positive Fraction":
+            ys = pos
+        else:
+            ys = pos_err
+
+        data = Data1D(xs, ys)
+        if self.hasPlot:
+            self.plot.removePlot(None)
+        self.hasPlot = True
+        self.plot.plot(data=data, marker="-")
Index: src/sas/qtgui/Perspectives/Inversion/InversionLogic.py
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/InversionLogic.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
+++ src/sas/qtgui/Perspectives/Inversion/InversionLogic.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
@@ -0,0 +1,286 @@
+import math
+import pylab
+import numpy as np
+
+from sas.qtgui.Plotting.PlotterData import Data1D
+
+PR_FIT_LABEL = r"$P_{fit}(r)$"
+PR_LOADED_LABEL = r"$P_{loaded}(r)$"
+IQ_DATA_LABEL = r"$I_{obs}(q)$"
+IQ_FIT_LABEL = r"$I_{fit}(q)$"
+IQ_SMEARED_LABEL = r"$I_{smeared}(q)$"
+GROUP_ID_IQ_DATA = r"$I_{obs}(q)$"
+GROUP_ID_PR_FIT = r"$P_{fit}(r)$"
+
+
+class InversionLogic(object):
+    """
+    All the data-related logic. This class deals exclusively with Data1D/2D
+    No QStandardModelIndex here.
+    """
+
+    # TODO: Add way to change this value
+    _pr_n_pts = 51
+
+    def __init__(self, data=None):
+        self._data = data
+        self.data_is_loaded = False
+        if data is not None:
+            self.data_is_loaded = True
+
+    @property
+    def data(self):
+        return self._data
+
+    @data.setter
+    def data(self, value):
+        """ data setter """
+        self._data = value
+        self.data_is_loaded = True
+
+    def isLoadedData(self):
+        """ accessor """
+        return self.data_is_loaded
+
+    def new1DPlot(self, out, pr, q=None):
+        """
+        Create a new 1D data instance based on fitting results
+        """
+
+        qtemp = pr.x
+        if q is not None:
+            qtemp = q
+
+        # Make a plot
+        maxq = max(qtemp)
+
+        minq = min(qtemp)
+
+        # Check for user min/max
+        if pr.q_min is not None and maxq >= pr.q_min >= minq:
+            minq = pr.q_min
+        if pr.q_max is not None and maxq >= pr.q_max >= minq:
+            maxq = pr.q_max
+
+        x = pylab.arange(minq, maxq, maxq / 301.0)
+        y = np.zeros(len(x))
+        err = np.zeros(len(x))
+        for i in range(len(x)):
+            value = pr.iq(out, x[i])
+            y[i] = value
+            try:
+                err[i] = math.sqrt(math.fabs(value))
+            except:
+                err[i] = 1.0
+                print(("Error getting error", value, x[i]))
+
+        new_plot = Data1D(x, y)
+        new_plot.name = IQ_FIT_LABEL
+        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+        new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
+        title = "I(q)"
+        new_plot.title = title
+
+        # If we have a group ID, use it
+        if 'plot_group_id' in pr.info:
+            new_plot.group_id = pr.info["plot_group_id"]
+        new_plot.id = IQ_FIT_LABEL
+
+        # 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 = np.zeros(len(x))
+            err = np.zeros(len(x))
+            for i in range(len(x)):
+                value = pr.iq_smeared(pr.out, x[i])
+                y[i] = value
+                try:
+                    err[i] = math.sqrt(math.fabs(value))
+                except:
+                    err[i] = 1.0
+                    print(("Error getting error", value, x[i]))
+
+            new_plot = Data1D(x, y)
+            new_plot.name = IQ_SMEARED_LABEL
+            new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+            new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}")
+            # If we have a group ID, use it
+            if 'plot_group_id' in pr.info:
+                new_plot.group_id = pr.info["plot_group_id"]
+            new_plot.id = IQ_SMEARED_LABEL
+            new_plot.title = title
+
+        return new_plot
+
+    def update1DPlot(self, plot, out, pr, q=None):
+        """
+        Create a new 1D data instance based on fitting results
+        """
+
+        qtemp = pr.x
+        if q is not None:
+            qtemp = q
+
+        # Make a plot
+        maxq = max(qtemp)
+
+        minq = min(qtemp)
+
+        # Check for user min/max
+        if pr.q_min is not None and maxq >= pr.q_min >= minq:
+            minq = pr.q_min
+        if pr.q_max is not None and maxq >= pr.q_max >= minq:
+            maxq = pr.q_max
+
+        x = pylab.arange(minq, maxq, maxq / 301.0)
+        y = np.zeros(len(x))
+        err = np.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]))
+
+        plot.x = x
+        plot.y = y
+
+        # 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 = np.zeros(len(x))
+            err = np.zeros(len(x))
+            for i in range(len(x)):
+                value = pr.iq_smeared(pr.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]))
+
+            plot.x = x
+            plot.y = y
+
+        return plot
+
+    def newPRPlot(self, out, pr, cov=None):
+        """
+        """
+        # Show P(r)
+        x = pylab.arange(0.0, pr.d_max, pr.d_max / self._pr_n_pts)
+
+        y = np.zeros(len(x))
+        dy = np.zeros(len(x))
+
+        total = 0.0
+        pmax = 0.0
+        cov2 = np.ascontiguousarray(cov)
+
+        for i in range(len(x)):
+            if cov2 is None:
+                value = pr.pr(out, x[i])
+            else:
+                (value, dy[i]) = pr.pr_err(out, cov2, x[i])
+            total += 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 / total
+        #     dy = dy / total
+        # elif self._scale_output_unity == True:
+        #     y = y / pmax
+        #     dy = dy / pmax
+
+        if cov2 is None:
+            new_plot = Data1D(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}")
+        new_plot.title = "P(r) fit"
+        new_plot.id = PR_FIT_LABEL
+        # Make sure that the plot is linear
+        new_plot.xtransform = "x"
+        new_plot.ytransform = "y"
+        new_plot.group_id = GROUP_ID_PR_FIT
+
+        return new_plot
+
+    def updatePRPlot(self, plot, out, pr, cov=None):
+        x = pylab.arange(0.0, pr.d_max, pr.d_max / self._pr_n_pts)
+
+        y = np.zeros(len(x))
+        dy = np.zeros(len(x))
+
+        total = 0.0
+        pmax = 0.0
+        cov2 = np.ascontiguousarray(cov)
+
+        for i in range(len(x)):
+            if cov2 is None:
+                value = pr.pr(out, x[i])
+            else:
+                (value, dy[i]) = pr.pr_err(out, cov2, x[i])
+            total += 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 / total
+        #     dy = dy / total
+        # elif self._scale_output_unity == True:
+        #     y = y / pmax
+        #     dy = dy / pmax
+        plot.x = x
+        plot.y = y
+
+        if cov2 is not None:
+            plot.dy = dy
+
+        return plot
+
+    def computeDataRange(self):
+        """
+        Wrapper for calculating the data range based on local dataset
+        """
+        return self.computeRangeFromData(self.data)
+
+    def computeRangeFromData(self, data):
+        """
+        Compute the minimum and the maximum range of the data
+        return the npts contains in data
+        """
+        qmin, qmax = None, None
+        if isinstance(data, Data1D):
+            try:
+                qmin = min(data.x)
+                qmax = max(data.x)
+            except (ValueError, TypeError):
+                msg = "Unable to find min/max/length of \n data named %s" % \
+                            self.data.filename
+                raise ValueError(msg)
+
+        else:
+            qmin = 0
+            try:
+                x = max(np.fabs(data.xmin), np.fabs(data.xmax))
+                y = max(np.fabs(data.ymin), np.fabs(data.ymax))
+            except (ValueError, TypeError):
+                msg = "Unable to find min/max of \n data named %s" % \
+                            self.data.filename
+                raise ValueError(msg)
+            qmax = np.sqrt(x * x + y * y)
+        return qmin, qmax
Index: src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
+++ src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
@@ -0,0 +1,603 @@
+import sys
+import logging
+import pylab
+import numpy as np
+
+from PyQt5 import QtGui, QtCore, QtWidgets
+
+# sas-global
+import sas.qtgui.Utilities.GuiUtils as GuiUtils
+
+# pr inversion GUI elements
+from .InversionUtils import WIDGETS
+from .UI.TabbedInversionUI import Ui_PrInversion
+from .InversionLogic import InversionLogic
+
+# pr inversion calculation elements
+from sas.sascalc.dataloader.data_info import Data1D
+from sas.sascalc.pr.invertor import Invertor
+
+def is_float(value):
+    """Converts text input values to floats. Empty strings throw ValueError"""
+    try:
+        return float(value)
+    except ValueError:
+        return 0.0
+
+
+# TODO: Remove data
+# TODO: Modify plot references, don't just send new
+# TODO: Update help with batch capabilities
+# TODO: Easy way to scroll through results - no tabs in window(?) - 'spreadsheet'
+# TODO: Method to export results in some meaningful way
+class InversionWindow(QtWidgets.QTabWidget, Ui_PrInversion):
+    """
+    The main window for the P(r) Inversion perspective.
+    """
+
+    name = "Inversion"
+
+    def __init__(self, parent=None, data=None):
+        super(InversionWindow, self).__init__()
+        self.setupUi(self)
+
+        self.setWindowTitle("P(r) Inversion Perspective")
+
+        self._manager = parent
+        self._model_item = QtGui.QStandardItem()
+        #self._helpView = QtWebKit.QWebView()
+
+        self.communicate = GuiUtils.Communicate()
+
+        self.logic = InversionLogic()
+
+        # The window should not close
+        self._allow_close = False
+
+        # current QStandardItem showing on the panel
+        self._data = None
+        # current Data1D as referenced by self._data
+        self._data_set = None
+
+        # p(r) calculator
+        self._calculator = Invertor()
+        self._last_calculator = None
+        self.calc_thread = None
+        self.estimation_thread = None
+
+        # Current data object in view
+        self._data_index = 0
+        # list mapping data to p(r) calculation
+        self._data_list = {}
+        if not isinstance(data, list):
+            data_list = [data]
+        if data is not None:
+            for datum in data_list:
+                self._data_list[datum] = self._calculator.clone()
+
+        # plots for current data
+        self.pr_plot = None
+        self.data_plot = None
+        # plot references for all data in perspective
+        self.pr_plot_list = {}
+        self.data_plot_list = {}
+
+        self.model = QtGui.QStandardItemModel(self)
+        self.mapper = QtWidgets.QDataWidgetMapper(self)
+        # Link user interactions with methods
+        self.setupLinks()
+        # Set values
+        self.setupModel()
+        # Set up the Widget Map
+        self.setupMapper()
+        # Set base window state
+        self.setupWindow()
+
+    ######################################################################
+    # Base Perspective Class Definitions
+
+    def communicator(self):
+        return self.communicate
+
+    def allowBatch(self):
+        return True
+
+    def setClosable(self, value=True):
+        """
+        Allow outsiders close this widget
+        """
+        assert isinstance(value, bool)
+        self._allow_close = value
+
+    def closeEvent(self, event):
+        """
+        Overwrite QDialog close method to allow for custom widget close
+        """
+        if self._allow_close:
+            # reset the closability flag
+            self.setClosable(value=False)
+            event.accept()
+        else:
+            event.ignore()
+            # Maybe we should just minimize
+            self.setWindowState(QtCore.Qt.WindowMinimized)
+
+    ######################################################################
+    # Initialization routines
+
+    def setupLinks(self):
+        """Connect the use controls to their appropriate methods"""
+        self.dataList.currentIndexChanged.connect(self.displayChange)
+        self.calculateAllButton.clicked.connect(self.startThreadAll)
+        self.calculateThisButton.clicked.connect(self.startThread)
+        self.removeButton.clicked.connect(self.removeData)
+        self.helpButton.clicked.connect(self.help)
+        self.estimateBgd.toggled.connect(self.toggleBgd)
+        self.manualBgd.toggled.connect(self.toggleBgd)
+        self.regConstantSuggestionButton.clicked.connect(self.acceptAlpha)
+        self.noOfTermsSuggestionButton.clicked.connect(self.acceptNoTerms)
+        self.explorerButton.clicked.connect(self.openExplorerWindow)
+        self.backgroundInput.textChanged.connect(
+            lambda: self._calculator.set_est_bck(int(is_float(
+                str(self.backgroundInput.text())))))
+        self.minQInput.textChanged.connect(
+            lambda: self._calculator.set_qmin(is_float(
+                str(self.minQInput.text()))))
+        self.regularizationConstantInput.textChanged.connect(
+            lambda: self._calculator.set_alpha(is_float(
+                str(self.regularizationConstantInput.text()))))
+        self.maxDistanceInput.textChanged.connect(
+            lambda: self._calculator.set_dmax(is_float(
+                str(self.maxDistanceInput.text()))))
+        self.maxQInput.textChanged.connect(
+            lambda: self._calculator.set_qmax(is_float(
+                str(self.maxQInput.text()))))
+        self.slitHeightInput.textChanged.connect(
+            lambda: self._calculator.set_slit_height(is_float(
+                str(self.slitHeightInput.text()))))
+        self.slitWidthInput.textChanged.connect(
+            lambda: self._calculator.set_slit_width(is_float(
+                str(self.slitHeightInput.text()))))
+        self.model.itemChanged.connect(self.model_changed)
+
+    def setupMapper(self):
+        # Set up the mapper.
+        self.mapper.setOrientation(QtCore.Qt.Vertical)
+        self.mapper.setModel(self.model)
+
+        # Filename
+        self.mapper.addMapping(self.dataList, WIDGETS.W_FILENAME)
+        # Background
+        self.mapper.addMapping(self.backgroundInput, WIDGETS.W_BACKGROUND_INPUT)
+        self.mapper.addMapping(self.estimateBgd, WIDGETS.W_ESTIMATE)
+        self.mapper.addMapping(self.manualBgd, WIDGETS.W_MANUAL_INPUT)
+
+        # Qmin/Qmax
+        self.mapper.addMapping(self.minQInput, WIDGETS.W_QMIN)
+        self.mapper.addMapping(self.maxQInput, WIDGETS.W_QMAX)
+
+        # Slit Parameter items
+        self.mapper.addMapping(self.slitWidthInput, WIDGETS.W_SLIT_WIDTH)
+        self.mapper.addMapping(self.slitHeightInput, WIDGETS.W_SLIT_HEIGHT)
+
+        # Parameter Items
+        self.mapper.addMapping(self.regularizationConstantInput,
+                               WIDGETS.W_REGULARIZATION)
+        self.mapper.addMapping(self.regConstantSuggestionButton,
+                               WIDGETS.W_REGULARIZATION_SUGGEST)
+        self.mapper.addMapping(self.explorerButton, WIDGETS.W_EXPLORE)
+        self.mapper.addMapping(self.maxDistanceInput, WIDGETS.W_MAX_DIST)
+        self.mapper.addMapping(self.noOfTermsInput, WIDGETS.W_NO_TERMS)
+        self.mapper.addMapping(self.noOfTermsSuggestionButton,
+                               WIDGETS.W_NO_TERMS_SUGGEST)
+
+        # Output
+        self.mapper.addMapping(self.rgValue, WIDGETS.W_RG)
+        self.mapper.addMapping(self.iQ0Value, WIDGETS.W_I_ZERO)
+        self.mapper.addMapping(self.backgroundValue, WIDGETS.W_BACKGROUND_OUTPUT)
+        self.mapper.addMapping(self.computationTimeValue, WIDGETS.W_COMP_TIME)
+        self.mapper.addMapping(self.chiDofValue, WIDGETS.W_CHI_SQUARED)
+        self.mapper.addMapping(self.oscillationValue, WIDGETS.W_OSCILLATION)
+        self.mapper.addMapping(self.posFractionValue, WIDGETS.W_POS_FRACTION)
+        self.mapper.addMapping(self.sigmaPosFractionValue,
+                               WIDGETS.W_SIGMA_POS_FRACTION)
+
+        # Main Buttons
+        self.mapper.addMapping(self.removeButton, WIDGETS.W_REMOVE)
+        self.mapper.addMapping(self.calculateAllButton, WIDGETS.W_CALCULATE_ALL)
+        self.mapper.addMapping(self.calculateThisButton,
+                               WIDGETS.W_CALCULATE_VISIBLE)
+        self.mapper.addMapping(self.helpButton, WIDGETS.W_HELP)
+
+        self.mapper.toFirst()
+
+    def setupModel(self):
+        """
+        Update boxes with initial values
+        """
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_FILENAME, item)
+        item = QtGui.QStandardItem('0.0')
+        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_QMIN, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_QMAX, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_SLIT_WIDTH, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_SLIT_HEIGHT, item)
+        item = QtGui.QStandardItem("10")
+        self.model.setItem(WIDGETS.W_NO_TERMS, item)
+        item = QtGui.QStandardItem("0.0001")
+        self.model.setItem(WIDGETS.W_REGULARIZATION, item)
+        item = QtGui.QStandardItem("140.0")
+        self.model.setItem(WIDGETS.W_MAX_DIST, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_RG, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_I_ZERO, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_COMP_TIME, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_CHI_SQUARED, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_OSCILLATION, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_POS_FRACTION, item)
+        item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, item)
+
+    def setupWindow(self):
+        """Initialize base window state on init"""
+        self.setTabPosition(0)
+        self.enableButtons()
+        self.estimateBgd.setChecked(True)
+
+    ######################################################################
+    # Methods for updating GUI
+
+    def enableButtons(self):
+        """
+        Enable buttons when data is present, else disable them
+        """
+        self.removeButton.setEnabled(self.logic.data_is_loaded)
+        self.explorerButton.setEnabled(self.logic.data_is_loaded)
+        self.calculateAllButton.setEnabled(self.logic.data_is_loaded)
+        self.calculateThisButton.setEnabled(self.logic.data_is_loaded)
+
+    def populateDataComboBox(self, filename, data_ref):
+        """
+        Append a new file name to the data combobox
+        :param filename: data filename
+        :param data_ref: QStandardItem reference for data set to be added
+        """
+        qt_item = QtCore.QString.fromUtf8(filename)
+        ref = QtCore.QVariant(data_ref)
+        self.dataList.addItem(qt_item, ref)
+
+    def acceptNoTerms(self):
+        """Send estimated no of terms to input"""
+        self.model.setItem(WIDGETS.W_NO_TERMS, QtGui.QStandardItem(
+            self.noOfTermsSuggestionButton.text()))
+
+    def acceptAlpha(self):
+        """Send estimated alpha to input"""
+        self.model.setItem(WIDGETS.W_REGULARIZATION, QtGui.QStandardItem(
+            self.regConstantSuggestionButton.text()))
+
+    def displayChange(self):
+        variant_ref = self.dataList.itemData(self.dataList.currentIndex())
+        self.setCurrentData(variant_ref.toPyObject())
+
+    def removeData(self):
+        """Remove the existing data reference from the P(r) Persepective"""
+        self._data_list.pop(self._data)
+        self.pr_plot_list.pop(self._data)
+        self.data_plot_list.pop(self._data)
+        self.dataList.removeItem(self.dataList.currentIndex())
+        self.dataList.setCurrentIndex(0)
+
+    ######################################################################
+    # GUI Interaction Events
+
+    def update_calculator(self):
+        """Update all p(r) params"""
+        self._calculator.set_x(self._data_set.x)
+        self._calculator.set_y(self._data_set.y)
+        self._calculator.set_err(self._data_set.dy)
+
+    def model_changed(self):
+        """Update the values when user makes changes"""
+        if not self.mapper:
+            msg = "Unable to update P{r}. The connection between the main GUI "
+            msg += "and P(r) was severed. Attempting to restart P(r)."
+            logging.warning(msg)
+            self.setClosable(True)
+            self.close()
+            InversionWindow.__init__(self.parent(), list(self._data_list.keys()))
+            exit(0)
+        # TODO: Only send plot first time - otherwise, update in complete
+        if self.pr_plot is not None:
+            title = self.pr_plot.name
+            GuiUtils.updateModelItemWithPlot(
+                self._data, QtCore.QVariant(self.pr_plot), title)
+        if self.data_plot is not None:
+            title = self.data_plot.name
+            GuiUtils.updateModelItemWithPlot(
+                self._data, QtCore.QVariant(self.data_plot), title)
+        self.mapper.toFirst()
+
+    def help(self):
+        """
+        Open the P(r) Inversion help browser
+        """
+        tree_location = (GuiUtils.HELP_DIRECTORY_LOCATION +
+                         "user/sasgui/perspectives/pr/pr_help.html")
+
+        # Actual file anchor will depend on the combo box index
+        # Note that we can be clusmy here, since bad current_fitter_id
+        # will just make the page displayed from the top
+        #self._helpView.load(QtCore.QUrl(tree_location))
+        #self._helpView.show()
+
+    def toggleBgd(self):
+        """
+        Toggle the background between manual and estimated
+        """
+        sender = self.sender()
+        if sender is self.estimateBgd:
+            self.backgroundInput.setEnabled(False)
+        else:
+            self.backgroundInput.setEnabled(True)
+
+    def openExplorerWindow(self):
+        """
+        Open the Explorer window to see correlations between params and results
+        """
+        # TODO: Link Invertor() and DmaxWindow so window updates when recalculated
+        from .DMaxExplorerWidget import DmaxWindow
+        self.dmaxWindow = DmaxWindow(self._calculator, self.getNFunc(), self)
+        self.dmaxWindow.show()
+
+    ######################################################################
+    # Response Actions
+
+    def setData(self, data_item=None, is_batch=False):
+        """
+        Assign new data set(s) to the P(r) perspective
+        Obtain a QStandardItem object and parse it to get Data1D/2D
+        Pass it over to the calculator
+        """
+        assert data_item is not None
+
+        if not isinstance(data_item, list):
+            msg = "Incorrect type passed to the P(r) Perspective"
+            raise AttributeError
+
+        for data in data_item:
+            # Create initial internal mappings
+            self._data_list[data] = self._calculator.clone()
+            self._data_set = GuiUtils.dataFromItem(data)
+            self.data_plot_list[data] = self.data_plot
+            self.pr_plot_list[data] = self.pr_plot
+            ref_var = QtCore.QVariant(data)
+            self.populateDataComboBox(self._data_set.filename, ref_var)
+            self.setCurrentData(data)
+
+            # Estimate initial values from data
+            self.performEstimate()
+            self.logic = InversionLogic(self._data_set)
+
+            # Estimate q range
+            qmin, qmax = self.logic.computeDataRange()
+            self.model.setItem(WIDGETS.W_QMIN, QtGui.QStandardItem(
+                "{:.4g}".format(qmin)))
+            self.model.setItem(WIDGETS.W_QMAX, QtGui.QStandardItem(
+                "{:.4g}".format(qmax)))
+
+        self.enableButtons()
+
+    def getNFunc(self):
+        """Get the n_func value from the GUI object"""
+        return int(UI.TabbedInversionUI._fromUtf8(self.noOfTermsInput.text()))
+
+    def setCurrentData(self, data_ref):
+        """Get the current data and display as necessary"""
+
+        if not isinstance(data_ref, QtGui.QStandardItem):
+            msg = "Incorrect type passed to the P(r) Perspective"
+            raise AttributeError
+
+        # Data references
+        self._data = data_ref
+        self._data_set = GuiUtils.dataFromItem(data_ref)
+        self._calculator = self._data_list[data_ref]
+        self.pr_plot = self.pr_plot_list[data_ref]
+        self.data_plot = self.data_plot_list[data_ref]
+
+    ######################################################################
+    # Thread Creators
+
+    # TODO: Move to individual class(?)
+
+    def startThreadAll(self):
+        for data_ref, pr in list(self._data_list.items()):
+            self._data_set = GuiUtils.dataFromItem(data_ref)
+            self._calculator = pr
+            self.startThread()
+
+    def startThread(self):
+        """
+            Start a calculation thread
+        """
+        from .Thread import CalcPr
+
+        # Set data before running the calculations
+        self.update_calculator()
+
+        # If a thread is already started, stop it
+        if self.calc_thread is not None and self.calc_thread.isrunning():
+            self.calc_thread.stop()
+        pr = self._calculator.clone()
+        nfunc = self.getNFunc()
+        self.calc_thread = CalcPr(pr, nfunc,
+                                  error_func=self._threadError,
+                                  completefn=self._completed, updatefn=None)
+        self.calc_thread.queue()
+        self.calc_thread.ready(2.5)
+
+    def performEstimateNT(self):
+        """
+            Perform parameter estimation
+        """
+        from .Thread import EstimateNT
+
+        # If a thread is already started, stop it
+        if (self.estimation_thread is not None and
+                self.estimation_thread.isrunning()):
+            self.estimation_thread.stop()
+        pr = self._calculator.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
+        nfunc = self.getNFunc()
+        self.estimation_thread = EstimateNT(pr, nfunc,
+                                            error_func=self._threadError,
+                                            completefn=self._estimateNTCompleted,
+                                            updatefn=None)
+        self.estimation_thread.queue()
+        self.estimation_thread.ready(2.5)
+
+    def performEstimate(self):
+        """
+            Perform parameter estimation
+        """
+        from .Thread import EstimatePr
+
+        self.startThread()
+
+        # If a thread is already started, stop it
+        if (self.estimation_thread is not None and
+                self.estimation_thread.isrunning()):
+            self.estimation_thread.stop()
+        pr = self._calculator.clone()
+        nfunc = self.getNFunc()
+        self.estimation_thread = EstimatePr(pr, nfunc,
+                                            error_func=self._threadError,
+                                            completefn=self._estimateCompleted,
+                                            updatefn=None)
+        self.estimation_thread.queue()
+        self.estimation_thread.ready(2.5)
+
+    ######################################################################
+    # Thread Complete
+
+    def _estimateCompleted(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.model.setItem(WIDGETS.W_COMP_TIME,
+                           QtGui.QStandardItem(str(elapsed)))
+        self.regConstantSuggestionButton.setText(QtCore.QString(str(alpha)))
+        self.regConstantSuggestionButton.setEnabled(True)
+        if message:
+            logging.info(message)
+        self.performEstimateNT()
+
+    def _estimateNTCompleted(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.noOfTermsSuggestionButton.setText(QtCore.QString(
+            "{:n}".format(nterms)))
+        self.noOfTermsSuggestionButton.setEnabled(True)
+        self.regConstantSuggestionButton.setText(QtCore.QString(
+            "{:.3g}".format(alpha)))
+        self.regConstantSuggestionButton.setEnabled(True)
+        self.model.setItem(WIDGETS.W_COMP_TIME,
+                           QtGui.QStandardItem(str(elapsed)))
+        self.PrTabWidget.setCurrentIndex(0)
+        if message:
+            logging.info(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
+
+        """
+        # Save useful info
+        cov = np.ascontiguousarray(cov)
+        pr.cov = cov
+        pr.out = out
+        pr.elapsed = elapsed
+
+        # Show result on control panel
+
+        # TODO: Connect self._calculator to GUI - two-to-one connection possible?
+        self.model.setItem(WIDGETS.W_RG, QtGui.QStandardItem(str(pr.rg(out))))
+        self.model.setItem(WIDGETS.W_I_ZERO,
+                           QtGui.QStandardItem(str(pr.iq0(out))))
+        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT,
+                           QtGui.QStandardItem("{:.3f}".format(pr.est_bck)))
+        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT,
+                           QtGui.QStandardItem(str(pr.background)))
+        self.model.setItem(WIDGETS.W_CHI_SQUARED,
+                           QtGui.QStandardItem(str(pr.chi2[0])))
+        self.model.setItem(WIDGETS.W_COMP_TIME,
+                           QtGui.QStandardItem(str(elapsed)))
+        self.model.setItem(WIDGETS.W_OSCILLATION,
+                           QtGui.QStandardItem(str(pr.oscillations(out))))
+        self.model.setItem(WIDGETS.W_POS_FRACTION,
+                           QtGui.QStandardItem(str(pr.get_positive(out))))
+        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION,
+                           QtGui.QStandardItem(str(pr.get_pos_err(out, cov))))
+
+        # Display results tab
+        self.PrTabWidget.setCurrentIndex(1)
+        # Save Pr invertor
+        self._calculator = pr
+        # Append data to data list
+        self._data_list[self._data] = self._calculator.clone()
+
+        # Create new P(r) and fit plots
+        if self.pr_plot is None:
+            self.pr_plot = self.logic.newPRPlot(out, self._calculator, cov)
+            self.pr_plot_list[self._data] = self.pr_plot
+        else:
+            # FIXME: this should update the existing plot, not create a new one
+            self.pr_plot = self.logic.newPRPlot(out, self._calculator, cov)
+            self.pr_plot_list[self._data] = self.pr_plot
+        if self.data_plot is None:
+            self.data_plot = self.logic.new1DPlot(out, self._calculator)
+            self.data_plot_list[self._data] = self.data_plot
+        else:
+            # FIXME: this should update the existing plot, not create a new one
+            self.data_plot = self.logic.new1DPlot(out, self._calculator)
+            self.data_plot_list[self._data] = self.data_plot
+
+    def _threadError(self, error):
+        """
+            Call-back method for calculation errors
+        """
+        logging.warning(error)
Index: src/sas/qtgui/Perspectives/Inversion/InversionUtils.py
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/InversionUtils.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
+++ src/sas/qtgui/Perspectives/Inversion/InversionUtils.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
@@ -0,0 +1,33 @@
+def enum(*sequential, **named):
+    enums = dict(list(zip(sequential, list(range(len(sequential))))), **named)
+    return type('Enum', (), enums)
+
+WIDGETS = enum( 'W_FILENAME',               #0
+                'W_BACKGROUND_INPUT',             #1
+                'W_ESTIMATE',               #2
+                'W_MANUAL_INPUT',           #3
+                'W_QMIN',                   #4
+                'W_QMAX',                   #5
+                'W_SLIT_HEIGHT',            #6
+                'W_SLIT_WIDTH',             #7
+                'W_NO_TERMS',               #8
+                'W_NO_TERMS_SUGGEST',       #9
+                'W_REGULARIZATION',         #10
+                'W_REGULARIZATION_SUGGEST', #11
+                'W_MAX_DIST',               #12
+                'W_EXPLORE',                #13
+                # results
+                'W_RG',
+                'W_I_ZERO',
+                'W_BACKGROUND_OUTPUT',
+                'W_COMP_TIME',
+                'W_CHI_SQUARED',
+                'W_OSCILLATION',
+                'W_POS_FRACTION',
+                'W_SIGMA_POS_FRACTION',
+                # bottom buttons
+                'W_REMOVE',
+                'W_CALCULATE_ALL',
+                'W_CALCULATE_VISIBLE',
+                'W_HELP'
+)
Index: src/sas/qtgui/Perspectives/Inversion/Thread.py
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/Thread.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
+++ src/sas/qtgui/Perspectives/Inversion/Thread.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
@@ -0,0 +1,104 @@
+import sys
+import time
+from sas.sascalc.data_util.calcthread import CalcThread
+
+
+class CalcPr(CalcThread):
+    """
+    Compute P(r)
+    """
+
+    def __init__(self, pr, nfunc=5, error_func=None, completefn=None,
+                 updatefn=None, yieldtime=0.01, worktime=0.01):
+        """
+        """
+        CalcThread.__init__(self, completefn, updatefn, yieldtime, worktime)
+        self.pr = pr
+        self.nfunc = nfunc
+        self.error_func = error_func
+        self.starttime = 0
+
+    def compute(self):
+        """
+        Perform P(r) inversion
+        """
+        try:
+            self.starttime = time.time()
+            out, cov = self.pr.invert(self.nfunc)
+            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 self.error_func is not None:
+                self.error_func("CalcPr.compute: %s" % sys.exc_info()[1])
+
+
+class EstimatePr(CalcThread):
+    """
+    Estimate P(r)
+    """
+
+    def __init__(self, pr, nfunc=5, error_func=None, completefn=None,
+                 updatefn=None, yieldtime=0.01, worktime=0.01):
+        CalcThread.__init__(self, completefn, updatefn, yieldtime, worktime)
+        self.pr = pr
+        self.nfunc = nfunc
+        self.error_func = error_func
+        self.starttime = 0
+
+    def compute(self):
+        """
+        Calculates the estimate
+        """
+        try:
+            alpha, message, elapsed = self.pr.estimate_alpha(self.nfunc)
+            self.isquit()
+            self.complete(alpha=alpha, message=message, elapsed=elapsed)
+        except KeyboardInterrupt:
+            # Thread was interrupted, just proceed
+            pass
+        except:
+            if self.error_func is not None:
+                self.error_func("EstimatePr.compute: %s" % sys.exc_info()[1])
+
+
+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 self.error_func is not None:
+                self.error_func("EstimatePr2.compute: %s" % sys.exc_info()[1])
Index: src/sas/qtgui/Perspectives/Inversion/UI/DMaxExplorer.ui
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/UI/DMaxExplorer.ui	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
+++ src/sas/qtgui/Perspectives/Inversion/UI/DMaxExplorer.ui	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DmaxExplorer</class>
+ <widget class="QDialog" name="DmaxExplorer">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>609</width>
+    <height>524</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_2">
+   <item row="0" column="0">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <spacer name="verticalSpacer">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <layout class="QGridLayout" name="gridLayout">
+       <item row="1" column="2">
+        <widget class="QLabel" name="label_4">
+         <property name="text">
+          <string>Max Distance [â«]</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="3">
+        <widget class="QLineEdit" name="maxDist"/>
+       </item>
+       <item row="0" column="2">
+        <widget class="QLabel" name="label_2">
+         <property name="text">
+          <string>Npts</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="3">
+        <widget class="QLineEdit" name="Npts"/>
+       </item>
+       <item row="0" column="0">
+        <widget class="QLabel" name="label">
+         <property name="text">
+          <string>Dependent Variable:</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <widget class="QLineEdit" name="minDist"/>
+       </item>
+       <item row="0" column="1">
+        <widget class="QComboBox" name="dependentVariable">
+         <item>
+          <property name="text">
+           <string>1-Ï positive fraction</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>ÏÂ²/dof</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>I(Q=0)</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>Rg</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>Oscillation parameter</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>Background</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>Positive Fraction</string>
+          </property>
+         </item>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <widget class="QLabel" name="label_3">
+         <property name="text">
+          <string>Min Distance [â«]</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0" colspan="4">
+        <widget class="QPushButton" name="closeButton">
+         <property name="text">
+          <string>Close</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
Index: src/sas/qtgui/Perspectives/Inversion/UI/TabbedInversionUI.ui
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/UI/TabbedInversionUI.ui	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
+++ src/sas/qtgui/Perspectives/Inversion/UI/TabbedInversionUI.ui	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
@@ -0,0 +1,589 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PrInversion</class>
+ <widget class="QDialog" name="PrInversion">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>458</width>
+    <height>679</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>458</width>
+    <height>679</height>
+   </size>
+  </property>
+  <property name="maximumSize">
+   <size>
+    <width>16777215</width>
+    <height>16777215</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>P(r) Inversion</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_7">
+   <item row="4" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout_8">
+     <property name="sizeConstraint">
+      <enum>QLayout::SetDefaultConstraint</enum>
+     </property>
+     <item>
+      <widget class="QPushButton" name="removeButton">
+       <property name="text">
+        <string>Remove</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="calculateThisButton">
+       <property name="text">
+        <string>Calculate</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="calculateAllButton">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Calculate All</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeType">
+        <enum>QSizePolicy::Expanding</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="helpButton">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Help</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="3" column="0">
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>2</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item row="1" column="0" rowspan="2">
+    <widget class="QTabWidget" name="PrTabWidget">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="tabMain">
+      <attribute name="title">
+       <string>P(r) Parameters</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_6" stretch="0,1,1,0,0">
+       <item>
+        <widget class="QGroupBox" name="dataSourceGroupBox">
+         <property name="title">
+          <string>I(q) data source</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_2">
+          <item row="1" column="0">
+           <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="2,1,0,2,2">
+            <item>
+             <widget class="QLabel" name="label_5">
+              <property name="text">
+               <string>Background Level:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="backgroundInput">
+              <property name="enabled">
+               <bool>false</bool>
+              </property>
+              <property name="text">
+               <string>0.0</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_10">
+              <property name="text">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ã
+&lt;span style=&quot; vertical-align:super;&quot;&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QRadioButton" name="estimateBgd">
+              <property name="text">
+               <string>Estimate</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QRadioButton" name="manualBgd">
+              <property name="text">
+               <string>Manual Input</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+          <item row="0" column="0">
+           <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,3">
+            <item>
+             <widget class="QLabel" name="label">
+              <property name="text">
+               <string>Data File Name:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QComboBox" name="dataList"/>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="qRangeGroupBox">
+         <property name="title">
+          <string>Total Q range</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_3">
+          <item row="0" column="0">
+           <layout class="QHBoxLayout" name="horizontalLayout_4">
+            <item>
+             <widget class="QLabel" name="label_7">
+              <property name="text">
+               <string>Min:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="minQInput">
+              <property name="enabled">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_11">
+              <property name="text">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ã
+&lt;span style=&quot; vertical-align:super;&quot;&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_8">
+              <property name="text">
+               <string>Max:</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="maxQInput">
+              <property name="enabled">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_9">
+              <property name="text">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ã
+&lt;span style=&quot; vertical-align:super;&quot;&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="slitParamsGroupBox">
+         <property name="title">
+          <string>Slit Parameters</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout">
+          <item row="0" column="0">
+           <layout class="QHBoxLayout" name="horizontalLayout">
+            <item>
+             <widget class="QLabel" name="label_3">
+              <property name="text">
+               <string>Height</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="slitHeightInput">
+              <property name="enabled">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_6">
+              <property name="text">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ã
+&lt;span style=&quot; vertical-align:super;&quot;&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_4">
+              <property name="text">
+               <string>Width</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLineEdit" name="slitWidthInput">
+              <property name="enabled">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QLabel" name="label_2">
+              <property name="text">
+               <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ã
+&lt;span style=&quot; vertical-align:super;&quot;&gt;-1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="paramGroupBox">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="toolTip">
+          <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;pre style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&gt;&lt;span style=&quot; font-family:'Courier New'; font-size:9pt; color:#000000;&quot;&gt;P(r) is found by fitting a set of base functions to I(Q). The minimization involves a regularization term to ensure a smooth P(r). The regularization constant gives the size of that term. The suggested value is the value above which the output P(r) will have only one peak.&lt;/span&gt;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="title">
+          <string>Parameters</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_8">
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_13">
+            <property name="text">
+             <string>Regularization constant</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="2">
+           <widget class="QPushButton" name="noOfTermsSuggestionButton">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="2">
+           <widget class="QPushButton" name="explorerButton">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="toolTip">
+             <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Open the D&lt;span style=&quot; vertical-align:sub;&quot;&gt;max&lt;/span&gt; explorer window.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+            </property>
+            <property name="layoutDirection">
+             <enum>Qt::LeftToRight</enum>
+            </property>
+            <property name="text">
+             <string>Explore</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QLineEdit" name="noOfTermsInput"/>
+          </item>
+          <item row="2" column="1">
+           <widget class="QLineEdit" name="maxDistanceInput"/>
+          </item>
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_12">
+            <property name="text">
+             <string>Number of Terms</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="2">
+           <widget class="QPushButton" name="regConstantSuggestionButton">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="text">
+             <string/>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="label_14">
+            <property name="text">
+             <string>Max distance [Ã
+]</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QLineEdit" name="regularizationConstantInput"/>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="outputsGroupBox">
+         <property name="title">
+          <string>Outputs</string>
+         </property>
+         <layout class="QGridLayout" name="gridLayout_4">
+          <item row="0" column="1">
+           <widget class="QLabel" name="label_23">
+            <property name="text">
+             <string>Calculated Value</string>
+            </property>
+            <property name="alignment">
+             <set>Qt::AlignCenter</set>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="2">
+           <widget class="QLabel" name="label_28">
+            <property name="text">
+             <string>Units</string>
+            </property>
+           </widget>
+          </item>
+          <item row="6" column="1">
+           <widget class="QLineEdit" name="oscillationValue">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="0">
+           <widget class="QLabel" name="label_17">
+            <property name="text">
+             <string>Background</string>
+            </property>
+           </widget>
+          </item>
+          <item row="7" column="1">
+           <widget class="QLineEdit" name="posFractionValue">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="2">
+           <widget class="QLabel" name="label_25">
+            <property name="text">
+             <string>Ã
+-1</string>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="1">
+           <widget class="QLineEdit" name="backgroundValue">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_15">
+            <property name="text">
+             <string>Rg</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="2">
+           <widget class="QLabel" name="label_24">
+            <property name="text">
+             <string>Ã
+</string>
+            </property>
+           </widget>
+          </item>
+          <item row="6" column="0">
+           <widget class="QLabel" name="label_20">
+            <property name="text">
+             <string>Oscillations</string>
+            </property>
+           </widget>
+          </item>
+          <item row="4" column="0">
+           <widget class="QLabel" name="label_18">
+            <property name="text">
+             <string>Computation Time</string>
+            </property>
+           </widget>
+          </item>
+          <item row="4" column="2">
+           <widget class="QLabel" name="label_27">
+            <property name="text">
+             <string>secs</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="label_16">
+            <property name="text">
+             <string>I(Q=0)</string>
+            </property>
+           </widget>
+          </item>
+          <item row="3" column="2">
+           <widget class="QLabel" name="label_26">
+            <property name="text">
+             <string>Ã
+-1</string>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="0">
+           <widget class="QLabel" name="label_22">
+            <property name="text">
+             <string>Parameter</string>
+            </property>
+           </widget>
+          </item>
+          <item row="8" column="0">
+           <widget class="QLabel" name="label_29">
+            <property name="text">
+             <string>1-sigma positive fraction</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QLineEdit" name="iQ0Value">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="5" column="0">
+           <widget class="QLabel" name="label_19">
+            <property name="text">
+             <string>Chi^2/dof</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QLineEdit" name="rgValue">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="5" column="1">
+           <widget class="QLineEdit" name="chiDofValue">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="4" column="1">
+           <widget class="QLineEdit" name="computationTimeValue">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+          <item row="7" column="0">
+           <widget class="QLabel" name="label_21">
+            <property name="text">
+             <string>Positive Fraction</string>
+            </property>
+           </widget>
+          </item>
+          <item row="8" column="1">
+           <widget class="QLineEdit" name="sigmaPosFractionValue">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
Index: src/sas/qtgui/Perspectives/__init__.py
===================================================================
--- src/sas/qtgui/Perspectives/__init__.py	(revision b3e8629bf18b74f4214d6552e620fcbc4e73a28c)
+++ src/sas/qtgui/Perspectives/__init__.py	(revision fa81e94af983227efded2314c2c544f98e2c56ba)
@@ -4,7 +4,9 @@
 from .Fitting.FittingPerspective import FittingWindow
 from .Invariant.InvariantPerspective import InvariantWindow
+from .Inversion.InversionPerspective import InversionWindow
 
 PERSPECTIVES = {
     FittingWindow.name: FittingWindow,
     InvariantWindow.name: InvariantWindow,
+    InversionWindow.name: InversionWindow
 }
