Index: .gitignore
===================================================================
--- .gitignore	(revision 948484ffa0773a2d027c6f6d73e5e3f34bd2f2fe)
+++ .gitignore	(revision 846a063c3d1b922e497d091978379ff03b85d35e)
@@ -29,4 +29,6 @@
 default_categories.json
 /setup.cfg
+**/UI/*.py
+!**/UI/__init__.py
 
 # doc build
Index: run.py
===================================================================
--- run.py	(revision a3221b61cb987c01cf74c03ef08544847fe3aa6a)
+++ run.py	(revision a3221b61cb987c01cf74c03ef08544847fe3aa6a)
@@ -141,4 +141,8 @@
     sys.path.append(build_path)
 
+    # Run the UI conversion tool
+    import sas.qtgui.convertUI
+
+
 if __name__ == "__main__":
     # Need to add absolute path before actual prepare call,
Index: src/sas/qtgui/Perspectives/Inversion/DMaxExplorerWidget.py
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/DMaxExplorerWidget.py	(revision f4480f0b7b106ef818f94ad2879e54baa53e4a6a)
+++ src/sas/qtgui/Perspectives/Inversion/DMaxExplorerWidget.py	(revision b0ba43ee4b198d4f5a834c93888985b18c20c071)
@@ -8,6 +8,4 @@
 
 # global
-import sys
-import os
 import logging
 import numpy as np
@@ -42,4 +40,5 @@
         super(DmaxWindow, self).__init__()
         self.setupUi(self)
+        self.parent = parent
 
         self.setWindowTitle("Dââ Explorer")
@@ -50,16 +49,16 @@
 
         self.plot = PlotterWidget(self, self)
-        self.hasPlot = None
+        self.hasPlot = False
         self.verticalLayout.insertWidget(0, self.plot)
 
         # Let's choose the Standard Item Model.
         self.model = QtGui.QStandardItemModel(self)
-        self.mapper = None
+        self.mapper = QtWidgets.QDataWidgetMapper(self)
 
         # Add validators on line edits
         self.setupValidators()
 
-        # # Connect buttons to slots.
-        # # Needs to be done early so default values propagate properly.
+        # Connect buttons to slots.
+        # Needs to be done early so default values propagate properly.
         self.setupSlots()
 
@@ -82,11 +81,18 @@
 
     def setupModel(self):
+        self.model.blockSignals(True)
         self.model.setItem(W.NPTS, QtGui.QStandardItem(str(self.nfunc)))
+        self.model.blockSignals(False)
+        self.model.blockSignals(True)
         self.model.setItem(W.DMIN, QtGui.QStandardItem("{:.1f}".format(0.9*self.pr_state.d_max)))
+        self.model.blockSignals(False)
+        self.model.blockSignals(True)
         self.model.setItem(W.DMAX, QtGui.QStandardItem("{:.1f}".format(1.1*self.pr_state.d_max)))
+        self.model.blockSignals(False)
+        self.model.blockSignals(True)
         self.model.setItem(W.VARIABLE, QtGui.QStandardItem( "ÏÂ²/dof"))
+        self.model.blockSignals(False)
 
     def setupMapper(self):
-        self.mapper = QtWidgets.QDataWidgetMapper(self)
         self.mapper.setOrientation(QtCore.Qt.Vertical)
         self.mapper.setModel(self.model)
@@ -111,7 +117,13 @@
         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()))
+        try:
+            dmin = float(self.model.item(W.DMIN).text())
+            dmax = float(self.model.item(W.DMAX).text())
+            npts = float(self.model.item(W.NPTS).text())
+            xs = np.linspace(dmin, dmax, npts)
+        except ValueError as e:
+            msg = ("An input value is not correctly formatted. Please check {}"
+                   .format(e.message))
+            logger.error(msg)
 
         original = self.pr_state.d_max
@@ -132,5 +144,4 @@
                 msg = "ExploreDialog: inversion failed "
                 msg += "for D_max=%s\n%s" % (str(x), ex)
-                print(msg)
                 logger.error(msg)
 
@@ -142,5 +153,4 @@
             msg = "ExploreDialog: inversion failed "
             msg += "for D_max=%s\n%s" % (str(x), ex)
-            print(msg)
             logger.error(msg)
 
@@ -151,14 +161,14 @@
         if plotter == "ÏÂ²/dof":
             ys = chi2
-            y_label = "\chi^2/dof"
+            y_label = "\\chi^2/dof"
             y_unit = "a.u."
         elif plotter == "I(Q=0)":
             ys = iq0
             y_label = "I(q=0)"
-            y_unit = "\AA^{-1}"
+            y_unit = "\\AA^{-1}"
         elif plotter == "Rg":
             ys = rg
             y_label = "R_g"
-            y_unit = "\AA"
+            y_unit = "\\AA"
         elif plotter == "Oscillation parameter":
             ys = osc
@@ -168,5 +178,5 @@
             ys = bck
             y_label = "Bckg"
-            y_unit = "\AA^{-1}"
+            y_unit = "\\AA^{-1}"
         elif plotter == "Positive Fraction":
             ys = pos
@@ -175,5 +185,5 @@
         else:
             ys = pos_err
-            y_label = "P^{+}_{1\sigma}"
+            y_label = "P^{+}_{1\\sigma}"
             y_unit = "a.u."
 
@@ -188,2 +198,7 @@
         data._yunit = y_unit
         self.plot.plot(data=data, marker="-")
+
+    def closeEvent(self, event):
+        """Override close event"""
+        self.parent.dmaxWindow = None
+        event.accept()
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 6da860acc5670055935e7bdc8678d3827d1a057f)
@@ -1,4 +1,4 @@
 import math
-import pylab
+import logging
 import numpy as np
 
@@ -12,4 +12,7 @@
 GROUP_ID_IQ_DATA = r"$I_{obs}(q)$"
 GROUP_ID_PR_FIT = r"$P_{fit}(r)$"
+PR_PLOT_PTS = 51
+
+logger = logging.getLogger(__name__)
 
 
@@ -19,7 +22,4 @@
     No QStandardModelIndex here.
     """
-
-    # TODO: Add way to change this value
-    _pr_n_pts = 51
 
     def __init__(self, data=None):
@@ -37,5 +37,5 @@
         """ data setter """
         self._data = value
-        self.data_is_loaded = True
+        self.data_is_loaded = (self._data is not None)
 
     def isLoadedData(self):
@@ -63,5 +63,5 @@
             maxq = pr.q_max
 
-        x = pylab.arange(minq, maxq, maxq / 301.0)
+        x = np.arange(minq, maxq, maxq / 301.0)
         y = np.zeros(len(x))
         err = np.zeros(len(x))
@@ -73,5 +73,5 @@
             except:
                 err[i] = 1.0
-                print(("Error getting error", value, x[i]))
+                logger.log(("Error getting error", value, x[i]))
 
         new_plot = Data1D(x, y)
@@ -89,5 +89,5 @@
         # 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)
+            x = np.arange(minq, maxq, maxq / 301.0)
             y = np.zeros(len(x))
             err = np.zeros(len(x))
@@ -99,5 +99,5 @@
                 except:
                     err[i] = 1.0
-                    print(("Error getting error", value, x[i]))
+                    logger.log(("Error getting error", value, x[i]))
 
             new_plot = Data1D(x, y)
@@ -113,63 +113,9 @@
         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)
+        x = np.arange(0.0, pr.d_max, pr.d_max / PR_PLOT_PTS)
 
         y = np.zeros(len(x))
@@ -193,11 +139,4 @@
             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)
@@ -209,47 +148,8 @@
         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.scale = "linear"
         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):
Index: src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py	(revision aed053275d5f293f1f6495be4c78e4b18fc52b41)
+++ src/sas/qtgui/Perspectives/Inversion/InversionPerspective.py	(revision b0ba43ee4b198d4f5a834c93888985b18c20c071)
@@ -1,5 +1,3 @@
-import sys
 import logging
-import pylab
 import numpy as np
 
@@ -15,6 +13,8 @@
 
 # pr inversion calculation elements
-from sas.sascalc.dataloader.data_info import Data1D
 from sas.sascalc.pr.invertor import Invertor
+# Batch calculation display
+from sas.qtgui.Utilities.GridPanel import BatchInversionOutputPanel
+
 
 def is_float(value):
@@ -25,12 +25,14 @@
         return 0.0
 
+
 NUMBER_OF_TERMS = 10
 REGULARIZATION = 0.0001
 BACKGROUND_INPUT = 0.0
 MAX_DIST = 140.0
-
-# TODO: Modify plot references, don't just send new
-# TODO: Update help with batch capabilities
-# TODO: Method to export results in some meaningful way
+DICT_KEYS = ["Calculator", "PrPlot", "DataPlot"]
+
+logger = logging.getLogger(__name__)
+
+
 class InversionWindow(QtWidgets.QDialog, Ui_PrInversion):
     """
@@ -50,52 +52,52 @@
 
         self._manager = parent
-        self._model_item = QtGui.QStandardItem()
-        self.communicate = GuiUtils.Communicate()
+        self.communicate = parent.communicator()
+        self.communicate.dataDeletedSignal.connect(self.removeData)
 
         self.logic = InversionLogic()
 
-        # Reference to Dmax window
-        self.dmaxWindow = None
-
         # The window should not close
-        self._allow_close = False
-
+        self._allowClose = False
+
+        # Visible data items
         # current QStandardItem showing on the panel
         self._data = None
-        # current Data1D as referenced by self._data
-        self._data_set = None
-
-        # p(r) calculator
+        # Reference to Dmax window for self._data
+        self.dmaxWindow = None
+        # p(r) calculator for self._data
         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 = {}
+        # Default to background estimate
+        self._calculator.est_bck = True
+        # plots of self._data
+        self.prPlot = None
+        self.dataPlot = None
+        # suggested nTerms
+        self.nTermsSuggested = NUMBER_OF_TERMS
+
+        # Calculation threads used by all data items
+        self.calcThread = None
+        self.estimationThread = None
+        self.estimationThreadNT = None
+        self.isCalculating = False
+
+        # Mapping for all data items
+        # Dictionary mapping data to all parameters
+        self._dataList = {}
         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()
-
-        # dict of models for quick update after calculation
-        # {item:model}
-        self._models = {}
-
-        self.calculateAllButton.setEnabled(False)
-        self.calculateThisButton.setEnabled(False)
-
-        # 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.updateDataList(datum)
+
+        self.dataDeleted = False
 
         self.model = QtGui.QStandardItemModel(self)
         self.mapper = QtWidgets.QDataWidgetMapper(self)
+
+        # Batch fitting parameters
+        self.isBatch = False
+        self.batchResultsWindow = None
+        self.batchResults = {}
+        self.batchComplete = []
 
         # Add validators
@@ -124,5 +126,11 @@
         """
         assert isinstance(value, bool)
-        self._allow_close = value
+        self._allowClose = value
+
+    def isClosable(self):
+        """
+        Allow outsiders close this widget
+        """
+        return self._allowClose
 
     def closeEvent(self, event):
@@ -130,5 +138,8 @@
         Overwrite QDialog close method to allow for custom widget close
         """
-        if self._allow_close:
+        # Close report widgets before closing/minimizing main widget
+        self.closeDMax()
+        self.closeBatchResults()
+        if self._allowClose:
             # reset the closability flag
             self.setClosable(value=False)
@@ -141,4 +152,12 @@
             self.setWindowState(QtCore.Qt.WindowMinimized)
 
+    def closeDMax(self):
+        if self.dmaxWindow is not None:
+            self.dmaxWindow.close()
+
+    def closeBatchResults(self):
+        if self.batchResultsWindow is not None:
+            self.batchResultsWindow.close()
+
     ######################################################################
     # Initialization routines
@@ -149,4 +168,5 @@
         self.calculateAllButton.clicked.connect(self.startThreadAll)
         self.calculateThisButton.clicked.connect(self.startThread)
+        self.stopButton.clicked.connect(self.stopCalculation)
         self.removeButton.clicked.connect(self.removeData)
         self.helpButton.clicked.connect(self.help)
@@ -157,18 +177,18 @@
         self.explorerButton.clicked.connect(self.openExplorerWindow)
 
-        self.backgroundInput.editingFinished.connect(
-            lambda: self._calculator.set_est_bck(int(is_float(self.backgroundInput.text()))))
-        self.minQInput.editingFinished.connect(
+        self.backgroundInput.textChanged.connect(
+            lambda: self.set_background(self.backgroundInput.text()))
+        self.minQInput.textChanged.connect(
             lambda: self._calculator.set_qmin(is_float(self.minQInput.text())))
-        self.regularizationConstantInput.editingFinished.connect(
+        self.regularizationConstantInput.textChanged.connect(
             lambda: self._calculator.set_alpha(is_float(self.regularizationConstantInput.text())))
-        self.maxDistanceInput.editingFinished.connect(
+        self.maxDistanceInput.textChanged.connect(
             lambda: self._calculator.set_dmax(is_float(self.maxDistanceInput.text())))
-        self.maxQInput.editingFinished.connect(
+        self.maxQInput.textChanged.connect(
             lambda: self._calculator.set_qmax(is_float(self.maxQInput.text())))
-        self.slitHeightInput.editingFinished.connect(
+        self.slitHeightInput.textChanged.connect(
             lambda: self._calculator.set_slit_height(is_float(self.slitHeightInput.text())))
-        self.slitWidthInput.editingFinished.connect(
-            lambda: self._calculator.set_slit_width(is_float(self.slitHeightInput.text())))
+        self.slitWidthInput.textChanged.connect(
+            lambda: self._calculator.set_slit_width(is_float(self.slitWidthInput.text())))
 
         self.model.itemChanged.connect(self.model_changed)
@@ -227,38 +247,36 @@
         Update boxes with initial values
         """
-        item = QtGui.QStandardItem("")
-        self.model.setItem(WIDGETS.W_FILENAME, item)
-        item = QtGui.QStandardItem(str(BACKGROUND_INPUT))
-        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(str(NUMBER_OF_TERMS))
-        self.model.setItem(WIDGETS.W_NO_TERMS, item)
-        item = QtGui.QStandardItem(str(REGULARIZATION))
-        self.model.setItem(WIDGETS.W_REGULARIZATION, item)
-        item = QtGui.QStandardItem(str(MAX_DIST))
-        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)
+        bgd_item = QtGui.QStandardItem(str(BACKGROUND_INPUT))
+        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT, bgd_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_QMIN, blank_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_QMAX, blank_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_SLIT_WIDTH, blank_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_SLIT_HEIGHT, blank_item)
+        no_terms_item = QtGui.QStandardItem(str(NUMBER_OF_TERMS))
+        self.model.setItem(WIDGETS.W_NO_TERMS, no_terms_item)
+        reg_item = QtGui.QStandardItem(str(REGULARIZATION))
+        self.model.setItem(WIDGETS.W_REGULARIZATION, reg_item)
+        max_dist_item = QtGui.QStandardItem(str(MAX_DIST))
+        self.model.setItem(WIDGETS.W_MAX_DIST, max_dist_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_RG, blank_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_I_ZERO, blank_item)
+        bgd_item = QtGui.QStandardItem(str(BACKGROUND_INPUT))
+        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT, bgd_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_COMP_TIME, blank_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_CHI_SQUARED, blank_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_OSCILLATION, blank_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_POS_FRACTION, blank_item)
+        blank_item = QtGui.QStandardItem("")
+        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION, blank_item)
 
     def setupWindow(self):
@@ -284,8 +302,20 @@
         Enable buttons when data is present, else disable them
         """
-        self.calculateAllButton.setEnabled(self.logic.data_is_loaded)
-        self.calculateThisButton.setEnabled(self.logic.data_is_loaded)
+        self.calculateAllButton.setEnabled(len(self._dataList) > 1
+                                           and not self.isBatch
+                                           and not self.isCalculating)
+        self.calculateThisButton.setEnabled(self.logic.data_is_loaded
+                                            and not self.isBatch
+                                            and not self.isCalculating)
         self.removeButton.setEnabled(self.logic.data_is_loaded)
-        self.explorerButton.setEnabled(self.logic.data_is_loaded)
+        self.explorerButton.setEnabled(self.logic.data_is_loaded
+                                       and np.all(self.logic.data.dy != 0))
+        self.stopButton.setVisible(self.isCalculating)
+        self.regConstantSuggestionButton.setEnabled(
+            self.logic.data_is_loaded and
+            self._calculator.suggested_alpha != self._calculator.alpha)
+        self.noOfTermsSuggestionButton.setEnabled(
+            self.logic.data_is_loaded and
+            self._calculator.nfunc != self.nTermsSuggested)
 
     def populateDataComboBox(self, filename, data_ref):
@@ -307,41 +337,23 @@
             self.regConstantSuggestionButton.text()))
 
-    def displayChange(self):
-        ref_item = self.dataList.itemData(self.dataList.currentIndex())
-        self._model_item = ref_item
-        self.setCurrentData(ref_item)
-        self.setCurrentModel(ref_item)
-
-    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)
-        if self.dmaxWindow is not None:
-            self.dmaxWindow = None
-        self.dataList.removeItem(self.dataList.currentIndex())
-        self.dataList.setCurrentIndex(0)
-        # Last file removed
-        if not self._data_list:
-            self._data = None
-            self.pr_plot = None
-            self._data_set = None
-            self.calculateThisButton.setEnabled(False)
-            self.calculateAllButton.setEnabled(False)
-            self.explorerButton.setEnabled(False)
+    def displayChange(self, data_index=0):
+        """Switch to another item in the data list"""
+        if self.dataDeleted:
+            return
+        self.updateDataList(self._data)
+        self.setCurrentData(self.dataList.itemData(data_index))
 
     ######################################################################
     # GUI Interaction Events
 
-    def setCurrentModel(self, ref_item):
-        '''update the current model with stored values'''
-        if ref_item in self._models:
-            self.model = self._models[ref_item]
-
-    def update_calculator(self):
+    def updateCalculator(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)
+        self._calculator.set_x(self.logic.data.x)
+        self._calculator.set_y(self.logic.data.y)
+        self._calculator.set_err(self.logic.data.dy)
+        self.set_background(self.backgroundInput.text())
+
+    def set_background(self, value):
+        self._calculator.background = is_float(value)
 
     def model_changed(self):
@@ -350,21 +362,13 @@
             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)
+            logger.warning(msg)
             self.setClosable(True)
             self.close()
-            InversionWindow.__init__(self.parent(), list(self._data_list.keys()))
+            InversionWindow.__init__(self.parent(), list(self._dataList.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, self.pr_plot, title)
-        if self.data_plot is not None:
-            title = self.data_plot.name
-            GuiUtils.updateModelItemWithPlot(self._data, self.data_plot, title)
         if self.dmaxWindow is not None:
-             self.dmaxWindow.pr_state = self._calculator
-             self.dmaxWindow.nfunc = self.getNFunc()
-
-        self.mapper.toFirst()
+            self.dmaxWindow.nfunc = self.getNFunc()
+            self.dmaxWindow.pr_state = self._calculator
+        self.mapper.toLast()
 
     def help(self):
@@ -383,9 +387,14 @@
         Toggle the background between manual and estimated
         """
-        sender = self.sender()
-        if sender is self.estimateBgd:
+        if self.estimateBgd.isChecked():
+            self.manualBgd.setChecked(False)
             self.backgroundInput.setEnabled(False)
+            self._calculator.set_est_bck = True
+        elif self.manualBgd.isChecked():
+            self.estimateBgd.setChecked(False)
+            self.backgroundInput.setEnabled(True)
+            self._calculator.set_est_bck = False
         else:
-            self.backgroundInput.setEnabled(True)
+            pass
 
     def openExplorerWindow(self):
@@ -394,6 +403,32 @@
         """
         from .DMaxExplorerWidget import DmaxWindow
-        self.dmaxWindow = DmaxWindow(self._calculator, self.getNFunc(), self)
+        self.dmaxWindow = DmaxWindow(pr_state=self._calculator,
+                                     nfunc=self.getNFunc(),
+                                     parent=self)
         self.dmaxWindow.show()
+
+    def showBatchOutput(self):
+        """
+        Display the batch output in tabular form
+        :param output_data: Dictionary mapping filename -> P(r) instance
+        """
+        if self.batchResultsWindow is None:
+            self.batchResultsWindow = BatchInversionOutputPanel(
+                parent=self, output_data=self.batchResults)
+        else:
+            self.batchResultsWindow.setupTable(self.batchResults)
+        self.batchResultsWindow.show()
+
+    def stopCalculation(self):
+        """ Stop all threads, return to the base state and update GUI """
+        self.stopCalcThread()
+        self.stopEstimationThread()
+        self.stopEstimateNTThread()
+        # Show any batch calculations that successfully completed
+        if self.isBatch and self.batchResultsWindow is not None:
+            self.showBatchOutput()
+        self.isBatch = False
+        self.isCalculating = False
+        self.updateGuiValues()
 
     ######################################################################
@@ -410,30 +445,34 @@
         if not isinstance(data_item, list):
             msg = "Incorrect type passed to the P(r) Perspective"
-            raise AttributeError
+            raise AttributeError(msg)
 
         for data in data_item:
-            if data in self._data_list.keys():
+            if data in self._dataList.keys():
                 # Don't add data if it's already in
-                return
+                continue
             # 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
-            self.populateDataComboBox(self._data_set.filename, data)
-            self.setCurrentData(data)
-
-            # Estimate initial values from data
-            self.performEstimate()
-            self.logic = InversionLogic(self._data_set)
-
+            self.logic.data = GuiUtils.dataFromItem(data)
             # 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._models[data] = self.model
-            self.model_item = data
-
-        self.enableButtons()
+            self._calculator.set_qmin(qmin)
+            self._calculator.set_qmax(qmax)
+            self.updateDataList(data)
+            self.populateDataComboBox(self.logic.data.filename, data)
+        self.dataList.setCurrentIndex(len(self.dataList) - 1)
+        self.setCurrentData(data)
+
+    def updateDataList(self, dataRef):
+        """Save the current data state of the window into self._data_list"""
+        if dataRef is None:
+            return
+        self._dataList[dataRef] = {
+            DICT_KEYS[0]: self._calculator,
+            DICT_KEYS[1]: self.prPlot,
+            DICT_KEYS[2]: self.dataPlot
+        }
+        # Update batch results window when finished
+        self.batchResults[self.logic.data.filename] = self._calculator
+        if self.batchResultsWindow is not None:
+            self.showBatchOutput()
 
     def getNFunc(self):
@@ -442,5 +481,6 @@
             nfunc = int(self.noOfTermsInput.text())
         except ValueError:
-            logging.error("Incorrect number of terms specified: %s" %self.noOfTermsInput.text())
+            logger.error("Incorrect number of terms specified: %s"
+                          %self.noOfTermsInput.text())
             self.noOfTermsInput.setText(str(NUMBER_OF_TERMS))
             nfunc = NUMBER_OF_TERMS
@@ -448,27 +488,132 @@
 
     def setCurrentData(self, data_ref):
-        """Get the current data and display as necessary"""
-
+        """Get the data by reference and display as necessary"""
         if data_ref is None:
             return
-
         if not isinstance(data_ref, QtGui.QStandardItem):
             msg = "Incorrect type passed to the P(r) Perspective"
-            raise AttributeError
-
+            raise AttributeError(msg)
         # 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]
+        self.logic.data = GuiUtils.dataFromItem(data_ref)
+        self._calculator = self._dataList[data_ref].get(DICT_KEYS[0])
+        self.prPlot = self._dataList[data_ref].get(DICT_KEYS[1])
+        self.dataPlot = self._dataList[data_ref].get(DICT_KEYS[2])
+        self.performEstimate()
+
+    def updateGuiValues(self):
+        pr = self._calculator
+        out = self._calculator.out
+        cov = self._calculator.cov
+        elapsed = self._calculator.elapsed
+        alpha = self._calculator.suggested_alpha
+        self.model.setItem(WIDGETS.W_QMIN,
+                           QtGui.QStandardItem("{:.4g}".format(pr.get_qmin())))
+        self.model.setItem(WIDGETS.W_QMAX,
+                           QtGui.QStandardItem("{:.4g}".format(pr.get_qmax())))
+        self.model.setItem(WIDGETS.W_BACKGROUND_INPUT,
+                           QtGui.QStandardItem("{:.3g}".format(pr.background)))
+        self.model.setItem(WIDGETS.W_BACKGROUND_OUTPUT,
+                           QtGui.QStandardItem("{:.3g}".format(pr.background)))
+        self.model.setItem(WIDGETS.W_COMP_TIME,
+                           QtGui.QStandardItem("{:.4g}".format(elapsed)))
+        self.model.setItem(WIDGETS.W_MAX_DIST,
+                           QtGui.QStandardItem("{:.4g}".format(pr.get_dmax())))
+        self.regConstantSuggestionButton.setText("{:-3.2g}".format(alpha))
+        self.noOfTermsSuggestionButton.setText(
+            "{:n}".format(self.nTermsSuggested))
+
+        if isinstance(pr.chi2, np.ndarray):
+            self.model.setItem(WIDGETS.W_CHI_SQUARED,
+                               QtGui.QStandardItem("{:.3g}".format(pr.chi2[0])))
+        if out is not None:
+            self.model.setItem(WIDGETS.W_RG,
+                               QtGui.QStandardItem("{:.3g}".format(pr.rg(out))))
+            self.model.setItem(WIDGETS.W_I_ZERO,
+                               QtGui.QStandardItem(
+                                   "{:.3g}".format(pr.iq0(out))))
+            self.model.setItem(WIDGETS.W_OSCILLATION, QtGui.QStandardItem(
+                "{:.3g}".format(pr.oscillations(out))))
+            self.model.setItem(WIDGETS.W_POS_FRACTION, QtGui.QStandardItem(
+                "{:.3g}".format(pr.get_positive(out))))
+            if cov is not None:
+                self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION,
+                                   QtGui.QStandardItem(
+                                       "{:.3g}".format(
+                                           pr.get_pos_err(out, cov))))
+        if self.prPlot is not None:
+            title = self.prPlot.name
+            GuiUtils.updateModelItemWithPlot(self._data, self.prPlot, title)
+            self.communicate.plotRequestedSignal.emit([self.prPlot])
+        if self.dataPlot is not None:
+            title = self.dataPlot.name
+            GuiUtils.updateModelItemWithPlot(self._data, self.dataPlot, title)
+            self.communicate.plotRequestedSignal.emit([self.dataPlot])
+        self.enableButtons()
+
+    def removeData(self, data_list=None):
+        """Remove the existing data reference from the P(r) Persepective"""
+        self.dataDeleted = True
+        self.batchResults = {}
+        if not data_list:
+            data_list = [self._data]
+        self.closeDMax()
+        for data in data_list:
+            self._dataList.pop(data)
+        self._data = None
+        length = len(self.dataList)
+        for index in reversed(range(length)):
+            if self.dataList.itemData(index) in data_list:
+                self.dataList.removeItem(index)
+        # Last file removed
+        self.dataDeleted = False
+        if len(self._dataList) == 0:
+            self.prPlot = None
+            self.dataPlot = None
+            self.logic.data = None
+            self._calculator = Invertor()
+            self.closeBatchResults()
+            self.nTermsSuggested = NUMBER_OF_TERMS
+            self.noOfTermsSuggestionButton.setText("{:n}".format(
+                self.nTermsSuggested))
+            self.regConstantSuggestionButton.setText("{:-3.2g}".format(
+                REGULARIZATION))
+            self.updateGuiValues()
+            self.setupModel()
+        else:
+            self.dataList.setCurrentIndex(0)
+            self.updateGuiValues()
 
     ######################################################################
     # Thread Creators
+
     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()
+        self.isCalculating = True
+        self.isBatch = True
+        self.batchComplete = []
+        self.calculateAllButton.setText("Calculating...")
+        self.enableButtons()
+        self.batchResultsWindow = BatchInversionOutputPanel(
+            parent=self, output_data=self.batchResults)
+        self.performEstimate()
+
+    def startNextBatchItem(self):
+        self.isBatch = False
+        for index in range(len(self._dataList)):
+            if index not in self.batchComplete:
+                self.dataList.setCurrentIndex(index)
+                self.isBatch = True
+                # Add the index before calculating in case calculation fails
+                self.batchComplete.append(index)
+                break
+        if self.isBatch:
+            self.performEstimate()
+        else:
+            # If no data sets left, end batch calculation
+            self.isCalculating = False
+            self.batchComplete = []
+            self.calculateAllButton.setText("Calculate All")
+            self.showBatchOutput()
+            self.enableButtons()
 
     def startThread(self):
@@ -479,17 +624,25 @@
 
         # 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()
+        self.isCalculating = True
+        self.enableButtons()
+        self.updateCalculator()
+        # Disable calculation buttons to prevent thread interference
+
+        # If the thread is already started, stop it
+        self.stopCalcThread()
+
         pr = self._calculator.clone()
         nfunc = self.getNFunc()
-        self.calc_thread = CalcPr(pr, nfunc,
-                                  error_func=self._threadError,
-                                  completefn=self._calculateCompleted,
-                                  updatefn=None)
-        self.calc_thread.queue()
-        self.calc_thread.ready(2.5)
+        self.calcThread = CalcPr(pr, nfunc,
+                                 error_func=self._threadError,
+                                 completefn=self._calculateCompleted,
+                                 updatefn=None)
+        self.calcThread.queue()
+        self.calcThread.ready(2.5)
+
+    def stopCalcThread(self):
+        """ Stops a thread if it exists and is running """
+        if self.calcThread is not None and self.calcThread.isrunning():
+            self.calcThread.stop()
 
     def performEstimateNT(self):
@@ -499,8 +652,9 @@
         from .Thread import EstimateNT
 
+        self.updateCalculator()
+
         # If a thread is already started, stop it
-        if (self.estimation_thread is not None and
-                self.estimation_thread.isrunning()):
-            self.estimation_thread.stop()
+        self.stopEstimateNTThread()
+
         pr = self._calculator.clone()
         # Skip the slit settings for the estimation
@@ -510,10 +664,15 @@
         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)
+        self.estimationThreadNT = EstimateNT(pr, nfunc,
+                                             error_func=self._threadError,
+                                             completefn=self._estimateNTCompleted,
+                                             updatefn=None)
+        self.estimationThreadNT.queue()
+        self.estimationThreadNT.ready(2.5)
+
+    def stopEstimateNTThread(self):
+        if (self.estimationThreadNT is not None and
+                self.estimationThreadNT.isrunning()):
+            self.estimationThreadNT.stop()
 
     def performEstimate(self):
@@ -523,18 +682,20 @@
         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)
+        self.stopEstimationThread()
+
+        self.estimationThread = EstimatePr(self._calculator.clone(),
+                                           self.getNFunc(),
+                                           error_func=self._threadError,
+                                           completefn=self._estimateCompleted,
+                                           updatefn=None)
+        self.estimationThread.queue()
+        self.estimationThread.ready(2.5)
+
+    def stopEstimationThread(self):
+        """ Stop the estimation thread if it exists and is running """
+        if (self.estimationThread is not None and
+                self.estimationThread.isrunning()):
+            self.estimationThread.stop()
 
     ######################################################################
@@ -554,10 +715,8 @@
         """
         alpha, message, elapsed = output_tuple
-        # Save useful info
-        self.model.setItem(WIDGETS.W_COMP_TIME, QtGui.QStandardItem("{:.4g}".format(elapsed)))
-        self.regConstantSuggestionButton.setText("{:-3.2g}".format(alpha))
-        self.regConstantSuggestionButton.setEnabled(True)
+        self._calculator.alpha = alpha
+        self._calculator.elapsed += self._calculator.elapsed
         if message:
-            logging.info(message)
+            logger.info(message)
         self.performEstimateNT()
 
@@ -576,12 +735,15 @@
         """
         nterms, alpha, message, elapsed = output_tuple
+        self._calculator.elapsed += elapsed
+        self._calculator.suggested_alpha = alpha
+        self.nTermsSuggested = nterms
         # Save useful info
-        self.noOfTermsSuggestionButton.setText("{:n}".format(nterms))
-        self.noOfTermsSuggestionButton.setEnabled(True)
-        self.regConstantSuggestionButton.setText("{:.3g}".format(alpha))
-        self.regConstantSuggestionButton.setEnabled(True)
-        self.model.setItem(WIDGETS.W_COMP_TIME, QtGui.QStandardItem("{:.2g}".format(elapsed)))
+        self.updateGuiValues()
         if message:
-            logging.info(message)
+            logger.info(message)
+        if self.isBatch:
+            self.acceptAlpha()
+            self.acceptNoTerms()
+            self.startThread()
 
     def _calculateCompleted(self, out, cov, pr, elapsed):
@@ -605,44 +767,29 @@
         pr.elapsed = elapsed
 
-        # Show result on control panel
-        self.model.setItem(WIDGETS.W_RG, QtGui.QStandardItem("{:.3g}".format(pr.rg(out))))
-        self.model.setItem(WIDGETS.W_I_ZERO, QtGui.QStandardItem("{:.3g}".format(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("{:.3g}".format(pr.background)))
-        self.model.setItem(WIDGETS.W_CHI_SQUARED, QtGui.QStandardItem("{:.3g}".format(pr.chi2[0])))
-        self.model.setItem(WIDGETS.W_COMP_TIME, QtGui.QStandardItem("{:.2g}".format(elapsed)))
-        self.model.setItem(WIDGETS.W_OSCILLATION, QtGui.QStandardItem("{:.3g}".format(pr.oscillations(out))))
-        self.model.setItem(WIDGETS.W_POS_FRACTION, QtGui.QStandardItem("{:.3g}".format(pr.get_positive(out))))
-        self.model.setItem(WIDGETS.W_SIGMA_POS_FRACTION,
-                           QtGui.QStandardItem("{:.3g}".format(pr.get_pos_err(out, cov))))
-
         # Save Pr invertor
         self._calculator = pr
-        # Append data to data list
-        self._data_list[self._data] = self._calculator.clone()
-
-        # Update model dict
-        self._models[self.model_item] = self.model
-
-        # 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
+
+        # Update P(r) and fit plots
+        self.prPlot = self.logic.newPRPlot(out, self._calculator, cov)
+        self.prPlot.filename = self.logic.data.filename
+        self.dataPlot = self.logic.new1DPlot(out, self._calculator)
+        self.dataPlot.filename = self.logic.data.filename
+
+        # Udpate internals and GUI
+        self.updateDataList(self._data)
+        if self.isBatch:
+            self.batchComplete.append(self.dataList.currentIndex())
+            self.startNextBatchItem()
         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
+            self.isCalculating = False
+        self.updateGuiValues()
+
+    def _threadError(self, error):
+        """
+            Call-back method for calculation errors
+        """
+        logger.error(error)
+        if self.isBatch:
+            self.startNextBatchItem()
         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)
+            self.stopCalculation()
Index: src/sas/qtgui/Perspectives/Inversion/UI/TabbedInversionUI.ui
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/UI/TabbedInversionUI.ui	(revision 8f83719fd67e6a72acdcd666e7bf1e2326b52cd0)
+++ src/sas/qtgui/Perspectives/Inversion/UI/TabbedInversionUI.ui	(revision 72ecbdf22c121ae7a0f43b886ce56a9b503fed04)
@@ -317,5 +317,5 @@
            <widget class="QLabel" name="label_22">
             <property name="text">
-             <string>      </string>
+             <string/>
             </property>
            </widget>
@@ -754,4 +754,11 @@
      </item>
      <item>
+      <widget class="QPushButton" name="stopButton">
+       <property name="text">
+        <string>Stop P(r)</string>
+       </property>
+      </widget>
+     </item>
+     <item>
       <spacer name="horizontalSpacer">
        <property name="orientation">
Index: src/sas/qtgui/Perspectives/Inversion/UI/convert_all.bat
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/UI/convert_all.bat	(revision e7651ffca46f55b1709d858d9c911c162adc332a)
+++ src/sas/qtgui/Perspectives/Inversion/UI/convert_all.bat	(revision e7651ffca46f55b1709d858d9c911c162adc332a)
@@ -0,0 +1,7 @@
+@echo off
+
+for %%f in (*.ui) do (
+  call pyuic4.bat %%f > %%~nf.py
+)
+
+
Index: src/sas/qtgui/Perspectives/Inversion/UI/convert_all.sh
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/UI/convert_all.sh	(revision e7651ffca46f55b1709d858d9c911c162adc332a)
+++ src/sas/qtgui/Perspectives/Inversion/UI/convert_all.sh	(revision e7651ffca46f55b1709d858d9c911c162adc332a)
@@ -0,0 +1,6 @@
+# UI -> PY
+for filename in *.ui; do
+  pyuic4 $filename > "`basename "$filename" .ui`.py"
+done
+
+
Index: src/sas/qtgui/Perspectives/Inversion/UI/convert_ui.sh
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/UI/convert_ui.sh	(revision e7651ffca46f55b1709d858d9c911c162adc332a)
+++ src/sas/qtgui/Perspectives/Inversion/UI/convert_ui.sh	(revision e7651ffca46f55b1709d858d9c911c162adc332a)
@@ -0,0 +1,1 @@
+pyuic.bat -w $1.ui > $1.py
Index: src/sas/qtgui/Perspectives/Inversion/UnitTesting/InversionPerspectiveTest.py
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/UnitTesting/InversionPerspectiveTest.py	(revision 50bfab0745a9a59df918aa1ba6a00bf46be39c11)
+++ src/sas/qtgui/Perspectives/Inversion/UnitTesting/InversionPerspectiveTest.py	(revision 72ecbdf22c121ae7a0f43b886ce56a9b503fed04)
@@ -1,18 +1,13 @@
-import sys
+import time
 import unittest
-import logging
 from unittest.mock import MagicMock
 
 from PyQt5 import QtGui, QtWidgets
-from PyQt5.QtTest import QTest
-from PyQt5 import QtCore
-
-import sas.qtgui.path_prepare
+
+from sas.qtgui.Utilities.GuiUtils import *
 from sas.qtgui.Perspectives.Inversion.InversionPerspective import InversionWindow
-from sas.sascalc.dataloader.loader import Loader
+from sas.qtgui.Perspectives.Inversion.InversionUtils import WIDGETS
 from sas.qtgui.Plotting.PlotterData import Data1D
 
-from sas.qtgui.MainWindow.DataManager import DataManager
-import sas.qtgui.Utilities.LocalConfig
 import sas.qtgui.Utilities.GuiUtils as GuiUtils
 
@@ -20,28 +15,51 @@
 app = QtWidgets.QApplication(sys.argv)
 
+
+class dummy_manager(object):
+    HELP_DIRECTORY_LOCATION = "html"
+    communicate = Communicate()
+
+    def communicator(self):
+        return self.communicate
+
+
 class InversionTest(unittest.TestCase):
-    '''Test the Inversion Interface'''
+    """ Test the Inversion Perspective GUI """
+
     def setUp(self):
-        '''Create the InversionWindow'''
-        self.widget = InversionWindow(None)
+        """ Create the InversionWindow """
+        self.widget = InversionWindow(dummy_manager())
+        self.widget.show()
+        self.fakeData1 = GuiUtils.HashableStandardItem("A")
+        self.fakeData2 = GuiUtils.HashableStandardItem("B")
+        reference_data1 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0])
+        reference_data1.filename = "Test A"
+        reference_data2 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0])
+        reference_data2.filename = "Test B"
+        GuiUtils.updateModelItem(self.fakeData1, reference_data1)
+        GuiUtils.updateModelItem(self.fakeData2, reference_data2)
 
     def tearDown(self):
-        '''Destroy the InversionWindow'''
+        """ Destroy the InversionWindow """
+        self.widget.setClosable(False)
         self.widget.close()
         self.widget = None
 
-    def testDefaults(self):
-        '''Test the GUI in its default state'''
+    def removeAllData(self):
+        """ Cleanup method to restore widget to its base state """
+        if len(self.widget.dataList) > 0:
+            remove_me = list(self.widget._dataList.keys())
+            self.widget.removeData(remove_me)
+
+    def baseGUIState(self):
+        """ Testing base state of Inversion """
+        # base class information
         self.assertIsInstance(self.widget, QtWidgets.QWidget)
         self.assertEqual(self.widget.windowTitle(), "P(r) Inversion Perspective")
-        self.assertEqual(self.widget.model.columnCount(), 1)
-        self.assertEqual(self.widget.model.rowCount(), 22)
-        self.assertFalse(self.widget.calculateAllButton.isEnabled())
-        self.assertFalse(self.widget.calculateThisButton.isEnabled())
+        self.assertFalse(self.widget.isClosable())
+        self.assertFalse(self.widget.isCalculating)
+        # mapper
         self.assertIsInstance(self.widget.mapper, QtWidgets.QDataWidgetMapper)
-        # make sure the model is assigned and at least the data is mapped
-        self.assertEqual(self.widget.mapper.model(), self.widget.model)
         self.assertNotEqual(self.widget.mapper.mappedSection(self.widget.dataList), -1)
-
         # validators
         self.assertIsInstance(self.widget.noOfTermsInput.validator(), QtGui.QIntValidator)
@@ -52,74 +70,153 @@
         self.assertIsInstance(self.widget.slitHeightInput.validator(), QtGui.QDoubleValidator)
         self.assertIsInstance(self.widget.slitWidthInput.validator(), QtGui.QDoubleValidator)
-
         # model
         self.assertEqual(self.widget.model.rowCount(), 22)
-
-    def testSetData(self):
-        ''' Check if sending data works as expected'''
-        # Create dummy data
-        item1 = GuiUtils.HashableStandardItem("A")
-        item2 = GuiUtils.HashableStandardItem("B")
-        reference_data = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0])
-        GuiUtils.updateModelItem(item1, [reference_data])
-        GuiUtils.updateModelItem(item2, [reference_data])
-        self.widget.performEstimate = MagicMock()
-        self.widget.setData([item1, item2])
-
-        # Test the globals
-        self.assertEqual(len(self.widget._data_list), 2)
-        self.assertEqual(len(self.widget.data_plot_list), 2)
-        self.assertEqual(len(self.widget.pr_plot_list), 2)
-        self.assertEqual(self.widget.dataList.count(), 2)
-
-        # See that the buttons are now enabled
-        self.assertTrue(self.widget.calculateAllButton.isEnabled())
+        self.assertEqual(self.widget.model.columnCount(), 1)
+        self.assertEqual(self.widget.mapper.model(), self.widget.model)
+        # buttons
+        self.assertFalse(self.widget.calculateThisButton.isEnabled())
+        self.assertFalse(self.widget.removeButton.isEnabled())
+        self.assertTrue(self.widget.stopButton.isEnabled())
+        self.assertFalse(self.widget.stopButton.isVisible())
+        self.assertFalse(self.widget.regConstantSuggestionButton.isEnabled())
+        self.assertFalse(self.widget.noOfTermsSuggestionButton.isEnabled())
+        self.assertFalse(self.widget.explorerButton.isEnabled())
+        self.assertTrue(self.widget.helpButton.isEnabled())
+        # extra windows and charts
+        self.assertIsNone(self.widget.dmaxWindow)
+        self.assertIsNone(self.widget.prPlot)
+        self.assertIsNone(self.widget.dataPlot)
+        # threads
+        self.assertIsNone(self.widget.calcThread)
+        self.assertIsNone(self.widget.estimationThread)
+        self.assertIsNone(self.widget.estimationThreadNT)
+
+    def baseBatchState(self):
+        """ Testing the base batch fitting state """
+        self.assertTrue(self.widget.allowBatch())
+        self.assertFalse(self.widget.isBatch)
+        self.assertIsNone(self.widget.batchResultsWindow)
+        self.assertFalse(self.widget.calculateAllButton.isEnabled())
+        self.assertEqual(len(self.widget.batchResults), 0)
+        self.assertEqual(len(self.widget.batchComplete), 0)
+        self.widget.closeBatchResults()
+        self.assertIsNone(self.widget.batchResultsWindow)
+
+    def zeroDataSetState(self):
+        """ Testing the base data state of the GUI """
+        # data variables
+        self.assertIsNone(self.widget._data)
+        self.assertEqual(len(self.widget._dataList), 0)
+        self.assertEqual(self.widget.nTermsSuggested, 10)
+        # inputs
+        self.assertEqual(len(self.widget.dataList), 0)
+        self.assertEqual(self.widget.backgroundInput.text(), "0.0")
+        self.assertEqual(self.widget.minQInput.text(), "")
+        self.assertEqual(self.widget.maxQInput.text(), "")
+        self.assertEqual(self.widget.regularizationConstantInput.text(), "0.0001")
+        self.assertEqual(self.widget.noOfTermsInput.text(), "10")
+        self.assertEqual(self.widget.maxDistanceInput.text(), "140.0")
+
+    def oneDataSetState(self):
+        """ Testing the base data state of the GUI """
+        # Test the globals after first sent
+        self.assertEqual(len(self.widget._dataList), 1)
+        self.assertEqual(self.widget.dataList.count(), 1)
+        # See that the buttons are now enabled properly
+        self.widget.enableButtons()
+        self.assertFalse(self.widget.calculateAllButton.isEnabled())
         self.assertTrue(self.widget.calculateThisButton.isEnabled())
         self.assertTrue(self.widget.removeButton.isEnabled())
         self.assertTrue(self.widget.explorerButton.isEnabled())
 
-    def notestRemoveData(self):
-        ''' Test data removal from widget '''
-        # Create dummy data
-        item1 = GuiUtils.HashableStandardItem("A")
-        item2 = GuiUtils.HashableStandardItem("B")
-        reference_data1 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0])
-        reference_data2 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0])
-        GuiUtils.updateModelItem(item1, [reference_data1])
-        GuiUtils.updateModelItem(item2, [reference_data2])
-        self.widget.performEstimate = MagicMock()
-        self.widget.setData([item1, item2])
-
+    def twoDataSetState(self):
+        """ Testing the base data state of the GUI """
+        # Test the globals after first sent
+        self.assertEqual(len(self.widget._dataList), 2)
+        self.assertEqual(self.widget.dataList.count(), 2)
+        # See that the buttons are now enabled properly
+        self.widget.enableButtons()
+        self.assertTrue(self.widget.calculateThisButton.isEnabled())
+        self.assertTrue(self.widget.calculateAllButton.isEnabled())
+        self.assertTrue(self.widget.removeButton.isEnabled())
+        self.assertTrue(self.widget.explorerButton.isEnabled())
+
+    def testDefaults(self):
+        """ Test the GUI in its default state """
+        self.baseGUIState()
+        self.zeroDataSetState()
+        self.baseBatchState()
+        self.removeAllData()
+
+    def testAllowBatch(self):
+        """ Batch P(r) Tests """
+        self.baseBatchState()
+        self.widget.setData([self.fakeData1])
+        self.oneDataSetState()
+        self.widget.setData([self.fakeData2])
+        self.twoDataSetState()
+        self.widget.calculateAllButton.click()
+        self.assertTrue(self.widget.isCalculating)
+        self.assertTrue(self.widget.isBatch)
+        self.assertTrue(self.widget.stopButton.isVisible())
+        self.assertTrue(self.widget.stopButton.isEnabled())
+        self.assertIsNotNone(self.widget.batchResultsWindow)
+        self.assertTrue(self.widget.batchResultsWindow.cmdHelp.isEnabled())
+        self.assertEqual(self.widget.batchResultsWindow.tblParams.columnCount(), 9)
+        self.assertEqual(self.widget.batchResultsWindow.tblParams.rowCount(), 2)
+        # Test stop button
+        self.widget.stopButton.click()
+        self.assertTrue(self.widget.batchResultsWindow.isVisible())
+        self.assertFalse(self.widget.stopButton.isVisible())
+        self.assertTrue(self.widget.stopButton.isEnabled())
+        self.assertFalse(self.widget.isBatch)
+        self.assertFalse(self.widget.isCalculating)
+        self.widget.batchResultsWindow.close()
+        self.assertIsNone(self.widget.batchResultsWindow)
+        # Last test
+        self.removeAllData()
+        self.baseBatchState()
+
+    def testSetData(self):
+        """ Check if sending data works as expected """
+        self.zeroDataSetState()
+        self.widget.setData([self.fakeData1])
+        self.oneDataSetState()
+        self.widget.setData([self.fakeData1])
+        self.oneDataSetState()
+        self.widget.setData([self.fakeData2])
+        self.twoDataSetState()
+        self.removeAllData()
+        self.zeroDataSetState()
+        self.removeAllData()
+
+    def testRemoveData(self):
+        """ Test data removal from widget """
+        self.widget.setData([self.fakeData1, self.fakeData2])
+        self.twoDataSetState()
         # Remove data 0
         self.widget.removeData()
-
-        # Test the globals
-        self.assertEqual(len(self.widget._data_list), 1)
-        self.assertEqual(len(self.widget.data_plot_list), 1)
-        self.assertEqual(len(self.widget.pr_plot_list), 1)
-        self.assertEqual(self.widget.dataList.count(), 1)
-
-
-    def testAllowBatch(self):
-        ''' Batch is allowed for this perspective'''
-        self.assertTrue(self.widget.allowBatch())
-
-    def notestModelChanged(self):
-        ''' test the model update '''
-        # unfinished functionality
-        pass
-
-    def notestHelp(self):
-        ''' test help widget show '''
-        # unfinished functionality
-        pass
-
-    def testOpenExplorerWindow(self):
-        ''' open Dx window '''
-        self.widget.openExplorerWindow()
-        self.assertTrue(self.widget.dmaxWindow.isVisible())
+        self.oneDataSetState()
+        self.removeAllData()
+
+    def testClose(self):
+        """ Test methods related to closing the window """
+        self.assertFalse(self.widget.isClosable())
+        self.widget.close()
+        self.assertTrue(self.widget.isMinimized())
+        self.assertIsNone(self.widget.dmaxWindow)
+        self.assertIsNone(self.widget.batchResultsWindow)
+        self.widget.setClosable(False)
+        self.assertFalse(self.widget.isClosable())
+        self.widget.close()
+        self.assertTrue(self.widget.isMinimized())
+        self.widget.setClosable(True)
+        self.assertTrue(self.widget.isClosable())
+        self.widget.setClosable()
+        self.assertTrue(self.widget.isClosable())
+        self.removeAllData()
 
     def testGetNFunc(self):
-        ''' test nfunc getter '''
+        """ test nfunc getter """
         # Float
         self.widget.noOfTermsInput.setText("10.0")
@@ -132,5 +229,5 @@
             self.widget.noOfTermsInput.setText("")
             n = self.widget.getNFunc()
-            self.assertEqual(cm.output, ['ERROR:root:Incorrect number of terms specified: '])
+            self.assertEqual(cm.output, ['ERROR:sas.qtgui.Perspectives.Inversion.InversionPerspective:Incorrect number of terms specified: '])
         self.assertEqual(self.widget.getNFunc(), 10)
         # string
@@ -138,33 +235,73 @@
             self.widget.noOfTermsInput.setText("Nordvest Pizza")
             n = self.widget.getNFunc()
-            self.assertEqual(cm.output, ['ERROR:root:Incorrect number of terms specified: Nordvest Pizza'])
+            self.assertEqual(cm.output, ['ERROR:sas.qtgui.Perspectives.Inversion.InversionPerspective:Incorrect number of terms specified: Nordvest Pizza'])
         self.assertEqual(self.widget.getNFunc(), 10)
+        self.removeAllData()
 
     def testSetCurrentData(self):
-        ''' test current data setter '''
-        # Create dummy data
-        item1 = GuiUtils.HashableStandardItem("A")
-        item2 = GuiUtils.HashableStandardItem("B")
-        reference_data1 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0])
-        reference_data2 = Data1D(x=[0.1, 0.2], y=[0.0, 0.0], dy=[0.0, 0.0])
-        GuiUtils.updateModelItem(item1, [reference_data1])
-        GuiUtils.updateModelItem(item2, [reference_data2])
-        self.widget.performEstimate = MagicMock()
-        self.widget.setData([item1, item2])
+        """ test current data setter """
+        self.widget.setData([self.fakeData1, self.fakeData2])
 
         # Check that the current data is reference2
-        self.assertEqual(self.widget._data, item2)
-
+        self.assertEqual(self.widget._data, self.fakeData2)
         # Set the ref to none
         self.widget.setCurrentData(None)
-        self.assertEqual(self.widget._data, item2)
-
+        self.assertEqual(self.widget._data, self.fakeData2)
         # Set the ref to wrong type
         with self.assertRaises(AttributeError):
             self.widget.setCurrentData("Afandi Kebab")
-
-        # Set the reference to ref2
-        self.widget.setCurrentData(item1)
-        self.assertEqual(self.widget._data, item1)
+        # Set the reference to ref1
+        self.widget.setCurrentData(self.fakeData1)
+        self.assertEqual(self.widget._data, self.fakeData1)
+        self.removeAllData()
+
+    def testModelChanged(self):
+        """ Test setting the input and the model and vice-versa """
+        # Initial values
+        self.assertEqual(self.widget._calculator.get_dmax(), 140.0)
+        self.assertEqual(self.widget._calculator.get_qmax(), -1.0)
+        self.assertEqual(self.widget._calculator.get_qmin(), -1.0)
+        self.assertEqual(self.widget._calculator.slit_height, 0.0)
+        self.assertEqual(self.widget._calculator.slit_width, 0.0)
+        self.assertEqual(self.widget._calculator.alpha, 0.0001)
+        # Set new values
+        self.widget.maxDistanceInput.setText("1.0")
+        self.widget.maxQInput.setText("3.0")
+        self.widget.minQInput.setText("5.0")
+        self.widget.slitHeightInput.setText("7.0")
+        self.widget.slitWidthInput.setText("9.0")
+        self.widget.regularizationConstantInput.setText("11.0")
+        # Check new values
+        self.assertEqual(self.widget._calculator.get_dmax(), 1.0)
+        self.assertEqual(self.widget._calculator.get_qmax(), 3.0)
+        self.assertEqual(self.widget._calculator.get_qmin(), 5.0)
+        self.assertEqual(self.widget._calculator.slit_height, 7.0)
+        self.assertEqual(self.widget._calculator.slit_width, 9.0)
+        self.assertEqual(self.widget._calculator.alpha, 11.0)
+        # Change model directly
+        self.widget.model.setItem(WIDGETS.W_MAX_DIST, QtGui.QStandardItem("2.0"))
+        self.widget.model.setItem(WIDGETS.W_QMIN, QtGui.QStandardItem("4.0"))
+        self.widget.model.setItem(WIDGETS.W_QMAX, QtGui.QStandardItem("6.0"))
+        self.widget.model.setItem(WIDGETS.W_SLIT_HEIGHT, QtGui.QStandardItem("8.0"))
+        self.widget.model.setItem(WIDGETS.W_SLIT_WIDTH, QtGui.QStandardItem("10.0"))
+        self.widget.model.setItem(WIDGETS.W_REGULARIZATION, QtGui.QStandardItem("12.0"))
+        # Check values
+        self.assertEqual(self.widget._calculator.get_dmax(), 2.0)
+        self.assertEqual(self.widget._calculator.get_qmin(), 4.0)
+        self.assertEqual(self.widget._calculator.get_qmax(), 6.0)
+        self.assertEqual(self.widget._calculator.slit_height, 8.0)
+        self.assertEqual(self.widget._calculator.slit_width, 10.0)
+        self.assertEqual(self.widget._calculator.alpha, 12.0)
+        self.removeAllData()
+
+    def testOpenExplorerWindow(self):
+        """ open Dx window """
+        self.assertIsNone(self.widget.dmaxWindow)
+        self.assertFalse(self.widget.explorerButton.isEnabled())
+        self.widget.openExplorerWindow()
+        self.assertIsNotNone(self.widget.dmaxWindow)
+        self.assertTrue(self.widget.dmaxWindow.isVisible())
+        self.assertTrue(self.widget.dmaxWindow.windowTitle() == "Dââ Explorer")
+
 
 if __name__ == "__main__":
Index: src/sas/qtgui/Perspectives/Inversion/media/pr_help.rst
===================================================================
--- src/sas/qtgui/Perspectives/Inversion/media/pr_help.rst	(revision 417c03f075685f86777838c8c717dec8e0ed8965)
+++ src/sas/qtgui/Perspectives/Inversion/media/pr_help.rst	(revision 417c03f075685f86777838c8c717dec8e0ed8965)
@@ -68,4 +68,22 @@
 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
 
+.. _Batch_Pr_Mode:
+
+Batch P(r) inversion
+--------------------
+
+The p(r) calculator accepts any number of data sets, and supports batch fitting
+for all data sets in the p(r) data set combo box. Switching between data sets in
+the combo box allows for individual fits and review of fit parameters. The
+displayed plots will also update to include the latest fit results for the
+selected data set.
+
+The 'Calculate All' button will run the p(r) calculation for each file,
+sequentially. The calculator will estimate the number of terms, background, and
+regularization constant for each file prior to running the p(r) calculation.
+The final results for all data sets are then presented in a savable data table.
+
+.. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+
 Reference
 ---------
@@ -76,3 +94,3 @@
 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
 
-.. note::  This help document was last modified by Paul Butler, 05 September, 2016
+.. note::  This help document was last modified by Jeff Krzywon, 18 April 2018
Index: src/sas/qtgui/Utilities/GridPanel.py
===================================================================
--- src/sas/qtgui/Utilities/GridPanel.py	(revision aed053275d5f293f1f6495be4c78e4b18fc52b41)
+++ src/sas/qtgui/Utilities/GridPanel.py	(revision aed053275d5f293f1f6495be4c78e4b18fc52b41)
@@ -410,2 +410,78 @@
             index += 1
 
+
+class BatchInversionOutputPanel(BatchOutputPanel):
+    """
+        Class for stateless grid-like printout of P(r) parameters for any number
+        of data sets
+    """
+    def __init__(self, parent = None, output_data=None):
+
+        super(BatchInversionOutputPanel, self).__init__(parent, output_data)
+        _translate = QtCore.QCoreApplication.translate
+        self.setWindowTitle(_translate("GridPanelUI", "Batch P(r) Results"))
+
+    def setupTable(self, data):
+        """
+        Create tablewidget items and show them, based on params
+        """
+        # headers
+        param_list = ['Filename', 'Rg [Ã
+]', 'Chi^2/dof', 'I(Q=0)', 'Oscillations',
+                      'Background [Ã
+^-1]', 'P+ Fraction', 'P+1-theta Fraction',
+                      'Calc. Time [sec]']
+
+        keys = data.keys()
+        rows = len(keys)
+        columns = len(param_list)
+        self.tblParams.setColumnCount(columns)
+        self.tblParams.setRowCount(rows)
+
+        for i, param in enumerate(param_list):
+            self.tblParams.setHorizontalHeaderItem(i, QtWidgets.QTableWidgetItem(param))
+
+        # first - Chi2 and data filename
+        for i_row, (filename, pr) in enumerate(data.items()):
+            out = pr.out
+            cov = pr.cov
+            if out is None:
+                logging.warning("P(r) for {} did not converge.".format(filename))
+                continue
+            self.tblParams.setItem(i_row, 0, QtWidgets.QTableWidgetItem(
+                "{}".format(filename)))
+            self.tblParams.setItem(i_row, 1, QtWidgets.QTableWidgetItem(
+                "{:.3g}".format(pr.rg(out))))
+            self.tblParams.setItem(i_row, 2, QtWidgets.QTableWidgetItem(
+                "{:.3g}".format(pr.chi2[0])))
+            self.tblParams.setItem(i_row, 3, QtWidgets.QTableWidgetItem(
+                "{:.3g}".format(pr.iq0(out))))
+            self.tblParams.setItem(i_row, 4, QtWidgets.QTableWidgetItem(
+                "{:.3g}".format(pr.oscillations(out))))
+            self.tblParams.setItem(i_row, 5, QtWidgets.QTableWidgetItem(
+                "{:.3g}".format(pr.background)))
+            self.tblParams.setItem(i_row, 6, QtWidgets.QTableWidgetItem(
+                "{:.3g}".format(pr.get_positive(out))))
+            self.tblParams.setItem(i_row, 7, QtWidgets.QTableWidgetItem(
+                "{:.3g}".format(pr.get_pos_err(out, cov))))
+            self.tblParams.setItem(i_row, 8, QtWidgets.QTableWidgetItem(
+                "{:.2g}".format(pr.elapsed)))
+
+        self.tblParams.resizeColumnsToContents()
+
+    @classmethod
+    def onHelp(cls):
+        """
+        Open a local url in the default browser
+        """
+        location = GuiUtils.HELP_DIRECTORY_LOCATION
+        url = "/user/sasgui/perspectives/pr/pr_help.html#batch-pr-mode"
+        try:
+            webbrowser.open('file://' + os.path.realpath(location + url))
+        except webbrowser.Error as ex:
+            logging.warning("Cannot display help. %s" % ex)
+
+    def closeEvent(self, event):
+        """Tell the parent window the window closed"""
+        self.parent.batchResultsWindow = None
+        event.accept()
Index: src/sas/sascalc/pr/invertor.py
===================================================================
--- src/sas/sascalc/pr/invertor.py	(revision 8f83719fd67e6a72acdcd666e7bf1e2326b52cd0)
+++ src/sas/sascalc/pr/invertor.py	(revision 6da860acc5670055935e7bdc8678d3827d1a057f)
@@ -481,5 +481,5 @@
             float(chi2)
         except:
-            chi2 = [-1.0]
+            chi2 = -1.0
         self.chi2 = chi2
 
