Index: src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py
===================================================================
--- src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
+++ src/sas/qtgui/Perspectives/Corfunc/CorfuncPerspective.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
@@ -0,0 +1,358 @@
+"""
+This module provides the intelligence behind the gui interface for Corfunc.
+"""
+# pylint: disable=E1101
+
+# global
+from PyQt5 import QtCore
+from PyQt5 import QtGui, QtWidgets
+
+# sas-global
+# pylint: disable=import-error, no-name-in-module
+import sas.qtgui.Utilities.GuiUtils as GuiUtils
+from sas.sascalc.corfunc.corfunc_calculator import CorfuncCalculator
+# pylint: enable=import-error, no-name-in-module
+
+# local
+from .UI.CorfuncPanel import Ui_CorfuncDialog
+from .CorfuncUtils import WIDGETS as W
+
+from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg \
+    as FigureCanvas
+from matplotlib.figure import Figure
+from numpy.linalg.linalg import LinAlgError
+
+
+class MyMplCanvas(FigureCanvas):
+    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
+    def __init__(self, model, width=5, height=4, dpi=100):
+        self.model = model
+        self.fig = Figure(figsize=(width, height), dpi=dpi)
+        self.axes = self.fig.add_subplot(111)
+
+        FigureCanvas.__init__(self, self.fig)
+
+        self.data = None
+        self.extrap = None
+
+    def draw_q_space(self):
+        """Draw the Q space data in the plot window
+
+        This draws the q space data in self.data, as well
+        as the bounds set by self.qmin, self.qmax1, and self.qmax2.
+        It will also plot the extrpolation in self.extrap, if it exists."""
+
+        self.fig.clf()
+
+        self.axes = self.fig.add_subplot(111)
+        self.axes.set_xscale("log")
+        self.axes.set_yscale("log")
+
+        qmin = float(self.model.item(W.W_QMIN).text())
+        qmax1 = float(self.model.item(W.W_QMAX).text())
+        qmax2 = float(self.model.item(W.W_QCUTOFF).text())
+
+        if self.data:
+            self.axes.plot(self.data.x, self.data.y)
+            self.axes.axvline(qmin)
+            self.axes.axvline(qmax1)
+            self.axes.axvline(qmax2)
+            self.axes.set_xlim(min(self.data.x) / 2,
+                               max(self.data.x) * 1.5 - 0.5 * min(self.data.x))
+        if self.extrap:
+            self.axes.plot(self.extrap.x, self.extrap.y)
+
+        self.draw()
+
+    def draw_real_space(self):
+        """
+        This function draws the real space data onto the plot
+
+        The 1d correlation function in self.data, the 3d correlation function
+        in self.data3, and the interface distribution function in self.data_idf
+        are all draw in on the plot in linear cooredinates."""
+        self.fig.clf()
+
+        self.axes = self.fig.add_subplot(111)
+        self.axes.set_xscale("linear")
+        self.axes.set_yscale("linear")
+
+        if self.data:
+            data1, data3, data_idf = self.data
+            self.axes.plot(data1.x, data1.y, label="1D Correlation")
+            self.axes.plot(data3.x, data3.y, label="3D Correlation")
+            self.axes.plot(data_idf.x, data_idf.y,
+                           label="Interface Distribution Function")
+            self.axes.set_xlim(min(data1.x), max(data1.x) / 4)
+            self.axes.legend()
+
+        self.draw()
+
+
+class CorfuncWindow(QtWidgets.QDialog, Ui_CorfuncDialog):
+    """Displays the correlation function analysis of sas data."""
+    name = "Corfunc"  # For displaying in the combo box
+
+    trigger = QtCore.pyqtSignal(tuple)
+
+# pylint: disable=unused-argument
+    def __init__(self, parent=None):
+        super(CorfuncWindow, self).__init__()
+        self.setupUi(self)
+
+        self.setWindowTitle("Corfunc Perspective")
+
+        self.mapper = None
+        self.model = QtGui.QStandardItemModel(self)
+        self.communicate = GuiUtils.Communicate()
+        self._calculator = CorfuncCalculator()
+        self._allow_close = True
+
+        self._canvas = MyMplCanvas(self.model)
+        self._realplot = MyMplCanvas(self.model)
+        self.verticalLayout_7.insertWidget(0, self._canvas)
+        self.verticalLayout_7.insertWidget(1, self._realplot)
+
+        # Connect buttons to slots.
+        # Needs to be done early so default values propagate properly.
+        self.setup_slots()
+
+        # Set up the model.
+        self.setup_model()
+
+        # Set up the mapper
+        self.setup_mapper()
+
+    def setup_slots(self):
+        """Connect the buttons to their appropriate slots."""
+        self.extrapolateBtn.clicked.connect(self.extrapolate)
+        self.extrapolateBtn.setEnabled(False)
+        self.transformBtn.clicked.connect(self.transform)
+        self.transformBtn.setEnabled(False)
+
+        self.calculateBgBtn.clicked.connect(self.calculate_background)
+        self.calculateBgBtn.setEnabled(False)
+
+        self.model.itemChanged.connect(self.model_changed)
+
+        self.trigger.connect(self.finish_transform)
+
+    def setup_model(self):
+        """Populate the model with default data."""
+        self.model.setItem(W.W_QMIN,
+                           QtGui.QStandardItem("0.01"))
+        self.model.setItem(W.W_QMAX,
+                           QtGui.QStandardItem("0.20"))
+        self.model.setItem(W.W_QCUTOFF,
+                           QtGui.QStandardItem("0.22"))
+        self.model.setItem(W.W_BACKGROUND,
+                           QtGui.QStandardItem("0"))
+        self.model.setItem(W.W_TRANSFORM,
+                           QtGui.QStandardItem("Fourier"))
+        self.model.setItem(W.W_GUINIERA,
+                           QtGui.QStandardItem("0.0"))
+        self.model.setItem(W.W_GUINIERB,
+                           QtGui.QStandardItem("0.0"))
+        self.model.setItem(W.W_PORODK,
+                           QtGui.QStandardItem("0.0"))
+        self.model.setItem(W.W_PORODSIGMA,
+                           QtGui.QStandardItem("0.0"))
+        self.model.setItem(W.W_CORETHICK, QtGui.QStandardItem(str(0)))
+        self.model.setItem(W.W_INTTHICK, QtGui.QStandardItem(str(0)))
+        self.model.setItem(W.W_HARDBLOCK, QtGui.QStandardItem(str(0)))
+        self.model.setItem(W.W_CRYSTAL, QtGui.QStandardItem(str(0)))
+        self.model.setItem(W.W_POLY, QtGui.QStandardItem(str(0)))
+        self.model.setItem(W.W_PERIOD, QtGui.QStandardItem(str(0)))
+
+    def model_changed(self, _):
+        """Actions to perform when the data is updated"""
+        if not self.mapper:
+            return
+        self.mapper.toFirst()
+        self._canvas.draw_q_space()
+
+    def _update_calculator(self):
+        self._calculator.lowerq = float(self.model.item(W.W_QMIN).text())
+        qmax1 = float(self.model.item(W.W_QMAX).text())
+        qmax2 = float(self.model.item(W.W_QCUTOFF).text())
+        self._calculator.upperq = (qmax1, qmax2)
+        self._calculator.background = \
+            float(self.model.item(W.W_BACKGROUND).text())
+
+    def extrapolate(self):
+        """Extend the experiemntal data with guinier and porod curves."""
+        self._update_calculator()
+        try:
+            params, extrapolation, _ = self._calculator.compute_extrapolation()
+            self.model.setItem(W.W_GUINIERA, QtGui.QStandardItem(str(params['A'])))
+            self.model.setItem(W.W_GUINIERB, QtGui.QStandardItem(str(params['B'])))
+            self.model.setItem(W.W_PORODK, QtGui.QStandardItem(str(params['K'])))
+            self.model.setItem(W.W_PORODSIGMA,
+                               QtGui.QStandardItem(str(params['sigma'])))
+
+            self._canvas.extrap = extrapolation
+            self._canvas.draw_q_space()
+            self.transformBtn.setEnabled(True)
+        except (LinAlgError, ValueError):
+            message = "These is not enough data in the fitting range. "\
+                      "Try decreasing the upper Q, increasing the "\
+                      "cutoff Q, or increasing the lower Q."
+            QtWidgets.QMessageBox.warning(self, "Calculation Error",
+                                      message)
+            self._canvas.extrap = None
+            self._canvas.draw_q_space()
+
+
+    def transform(self):
+        """Calculate the real space version of the extrapolation."""
+        method = str(self.model.item(W.W_TRANSFORM).text()).lower()
+
+        extrap = self._canvas.extrap
+        background = float(self.model.item(W.W_BACKGROUND).text())
+
+        def updatefn(msg):
+            """Report progress of transformation."""
+            self.communicate.statusBarUpdateSignal.emit(msg)
+
+        def completefn(transforms):
+            """Extract the values from the transforms and plot"""
+            self.trigger.emit(transforms)
+
+        self._update_calculator()
+        self._calculator.compute_transform(extrap, method, background,
+                                           completefn, updatefn)
+
+
+    def finish_transform(self, transforms):
+        params = self._calculator.extract_parameters(transforms[0])
+        self.model.setItem(W.W_CORETHICK,
+                           QtGui.QStandardItem(str(params['d0'])))
+        self.model.setItem(W.W_INTTHICK,
+                           QtGui.QStandardItem(str(params['dtr'])))
+        self.model.setItem(W.W_HARDBLOCK,
+                           QtGui.QStandardItem(str(params['Lc'])))
+        self.model.setItem(W.W_CRYSTAL,
+                           QtGui.QStandardItem(str(params['fill'])))
+        self.model.setItem(W.W_POLY,
+                           QtGui.QStandardItem(str(params['A'])))
+        self.model.setItem(W.W_PERIOD,
+                           QtGui.QStandardItem(str(params['max'])))
+        self._realplot.data = transforms
+        self._realplot.draw_real_space()
+
+    def setup_mapper(self):
+        """Creating mapping between model and gui elements."""
+        self.mapper = QtWidgets.QDataWidgetMapper(self)
+        self.mapper.setOrientation(QtCore.Qt.Vertical)
+        self.mapper.setModel(self.model)
+
+        self.mapper.addMapping(self.qMin, W.W_QMIN)
+        self.mapper.addMapping(self.qMax1, W.W_QMAX)
+        self.mapper.addMapping(self.qMax2, W.W_QCUTOFF)
+        self.mapper.addMapping(self.bg, W.W_BACKGROUND)
+        self.mapper.addMapping(self.transformCombo, W.W_TRANSFORM)
+
+        self.mapper.addMapping(self.guinierA, W.W_GUINIERA)
+        self.mapper.addMapping(self.guinierB, W.W_GUINIERB)
+        self.mapper.addMapping(self.porodK, W.W_PORODK)
+        self.mapper.addMapping(self.porodSigma, W.W_PORODSIGMA)
+
+        self.mapper.addMapping(self.avgCoreThick, W.W_CORETHICK)
+        self.mapper.addMapping(self.avgIntThick, W.W_INTTHICK)
+        self.mapper.addMapping(self.avgHardBlock, W.W_HARDBLOCK)
+        self.mapper.addMapping(self.polydisp, W.W_POLY)
+        self.mapper.addMapping(self.longPeriod, W.W_PERIOD)
+        self.mapper.addMapping(self.localCrystal, W.W_CRYSTAL)
+
+        self.mapper.toFirst()
+
+    def calculate_background(self):
+        """Find a good estimate of the background value."""
+        self._update_calculator()
+        try:
+            background = self._calculator.compute_background()
+            temp = QtGui.QStandardItem(str(background))
+            self.model.setItem(W.W_BACKGROUND, temp)
+        except (LinAlgError, ValueError):
+            message = "These is not enough data in the fitting range. "\
+                      "Try decreasing the upper Q or increasing the cutoff Q"
+            QtWidgets.QMessageBox.warning(self, "Calculation Error",
+                                      message)
+
+
+    # pylint: disable=invalid-name
+    @staticmethod
+    def allowBatch():
+        """
+        We cannot perform corfunc analysis in batch at this time.
+        """
+        return False
+
+    def setData(self, data_item, is_batch=False):
+        """
+        Obtain a QStandardItem object and dissect it to get Data1D/2D
+        Pass it over to the calculator
+        """
+        if not isinstance(data_item, list):
+            msg = "Incorrect type passed to the Corfunc Perpsective"
+            raise AttributeError(msg)
+
+        if not isinstance(data_item[0], QtGui.QStandardItem):
+            msg = "Incorrect type passed to the Corfunc Perspective"
+            raise AttributeError(msg)
+
+        model_item = data_item[0]
+        data = GuiUtils.dataFromItem(model_item)
+        self._calculator.set_data(data)
+        self.calculateBgBtn.setEnabled(True)
+        self.extrapolateBtn.setEnabled(True)
+
+        self.model.setItem(W.W_GUINIERA, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_GUINIERB, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_PORODK, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_PORODSIGMA, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_CORETHICK, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_INTTHICK, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_HARDBLOCK, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_CRYSTAL, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_POLY, QtGui.QStandardItem(""))
+        self.model.setItem(W.W_PERIOD, QtGui.QStandardItem(""))
+
+        self._canvas.data = data
+        self._canvas.extrap = None
+        self._canvas.draw_q_space()
+        self.transformBtn.setEnabled(False)
+
+        self._realplot.data = None
+        self._realplot.draw_real_space()
+
+    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)
+            # Tell the MdiArea to close the container
+            self.parentWidget().close()
+            event.accept()
+        else:
+            event.ignore()
+            # Maybe we should just minimize
+            self.setWindowState(QtCore.Qt.WindowMinimized)
+
+    def title(self):
+        """
+        Window title function used by certain error messages.
+        Check DataExplorer.py, line 355
+        """
+        return "Corfunc Perspective"
+    # pylint: enable=invalid-name
Index: src/sas/qtgui/Perspectives/Corfunc/CorfuncUtils.py
===================================================================
--- src/sas/qtgui/Perspectives/Corfunc/CorfuncUtils.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
+++ src/sas/qtgui/Perspectives/Corfunc/CorfuncUtils.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
@@ -0,0 +1,18 @@
+from sas.qtgui.Utilities.GuiUtils import enum
+
+WIDGETS = enum( 'W_QMIN',
+                'W_QMAX',
+                'W_QCUTOFF',
+                'W_BACKGROUND',
+                'W_TRANSFORM',
+                'W_GUINIERA',
+                'W_GUINIERB',
+                'W_PORODK',
+                'W_PORODSIGMA',
+                'W_CORETHICK',
+                'W_INTTHICK',
+                'W_HARDBLOCK',
+                'W_CRYSTAL',
+                'W_POLY',
+                'W_PERIOD',
+                )
Index: src/sas/qtgui/Perspectives/Corfunc/UI/CorfuncPanel.ui
===================================================================
--- src/sas/qtgui/Perspectives/Corfunc/UI/CorfuncPanel.ui	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
+++ src/sas/qtgui/Perspectives/Corfunc/UI/CorfuncPanel.ui	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
@@ -0,0 +1,331 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CorfuncDialog</class>
+ <widget class="QDialog" name="CorfuncDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>738</width>
+    <height>633</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="1" column="0">
+    <layout class="QVBoxLayout" name="verticalLayout_7" stretch="1,0,0,0,0,0,0">
+     <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="QHBoxLayout" name="horizontalLayout_2">
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <item alignment="Qt::AlignHCenter">
+          <widget class="QLabel" name="label_8">
+           <property name="text">
+            <string>Guinier</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <layout class="QFormLayout" name="formLayout_2">
+           <item row="0" column="0">
+            <widget class="QLineEdit" name="guinierA">
+             <property name="readOnly">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QLabel" name="label_6">
+             <property name="text">
+              <string>A</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLineEdit" name="guinierB">
+             <property name="readOnly">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QLabel" name="label_7">
+             <property name="text">
+              <string>B</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_4">
+         <item alignment="Qt::AlignHCenter">
+          <widget class="QLabel" name="label_9">
+           <property name="text">
+            <string>Porod</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <layout class="QFormLayout" name="formLayout_3">
+           <item row="0" column="0">
+            <widget class="QLineEdit" name="porodK">
+             <property name="readOnly">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QLabel" name="label_10">
+             <property name="text">
+              <string>K</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLineEdit" name="porodSigma">
+             <property name="readOnly">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QLabel" name="label_11">
+             <property name="text">
+              <string> Ï </string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <widget class="QFrame" name="frame">
+       <property name="frameShape">
+        <enum>QFrame::StyledPanel</enum>
+       </property>
+       <property name="frameShadow">
+        <enum>QFrame::Raised</enum>
+       </property>
+       <property name="lineWidth">
+        <number>5</number>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="Line" name="line">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="0" column="1">
+        <widget class="QLabel" name="label_14">
+         <property name="text">
+          <string>Long Period</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="2">
+        <widget class="QLineEdit" name="avgIntThick"/>
+       </item>
+       <item row="0" column="3">
+        <widget class="QLabel" name="label_15">
+         <property name="text">
+          <string>Average Interface Thickness</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <widget class="QLineEdit" name="avgHardBlock"/>
+       </item>
+       <item row="1" column="1">
+        <widget class="QLabel" name="label_16">
+         <property name="text">
+          <string>Average Hard Block Thickness</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="2">
+        <widget class="QLineEdit" name="avgCoreThick"/>
+       </item>
+       <item row="1" column="3">
+        <widget class="QLabel" name="label_17">
+         <property name="text">
+          <string>Average Core Thickness</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0">
+        <widget class="QLineEdit" name="polydisp"/>
+       </item>
+       <item row="2" column="1">
+        <widget class="QLabel" name="label_18">
+         <property name="text">
+          <string>Polydispersity</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="2">
+        <widget class="QLineEdit" name="localCrystal"/>
+       </item>
+       <item row="2" column="3">
+        <widget class="QLabel" name="label_19">
+         <property name="text">
+          <string>Local Crystallinity</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="0">
+        <widget class="QLineEdit" name="longPeriod"/>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <widget class="Line" name="line_2">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_3">
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1">
+         <item alignment="Qt::AlignHCenter">
+          <widget class="QLabel" name="label_3">
+           <property name="text">
+            <string>Controls</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <layout class="QGridLayout" name="gridLayout_3">
+           <property name="sizeConstraint">
+            <enum>QLayout::SetMinimumSize</enum>
+           </property>
+           <item row="1" column="3">
+            <widget class="QLabel" name="label_12">
+             <property name="text">
+              <string>Background</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLineEdit" name="qMin">
+             <property name="toolTip">
+              <string>Values with q &lt; qmin will be used for Guinier back extrapolation</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QLabel" name="label">
+             <property name="text">
+              <string>Lower</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLineEdit" name="qMax1">
+             <property name="toolTip">
+              <string>Values with Uppder &lt; q &lt; Cutoff will be used for Porod forward extrapolation</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QLabel" name="label_2">
+             <property name="text">
+              <string>Upper</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="3">
+            <widget class="QLabel" name="label_5">
+             <property name="text">
+              <string>Transform Type</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QLabel" name="label_4">
+             <property name="text">
+              <string>Cutoff</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="2">
+            <widget class="QComboBox" name="transformCombo">
+             <item>
+              <property name="text">
+               <string>Fourier</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="1" column="2">
+            <widget class="QLineEdit" name="bg">
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLineEdit" name="qMax2">
+             <property name="toolTip">
+              <string>Values with Uppder &lt; q &lt; Cutoff will be used for Porod forward extrapolation</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="2" colspan="2">
+            <widget class="QPushButton" name="calculateBgBtn">
+             <property name="text">
+              <string>Calculate Background</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="4">
+            <widget class="QPushButton" name="extrapolateBtn">
+             <property name="text">
+              <string>Extrapolate</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="4">
+            <widget class="QPushButton" name="transformBtn">
+             <property name="text">
+              <string>Transform</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
Index: src/sas/qtgui/Perspectives/Corfunc/UI/__init__.py
===================================================================
--- src/sas/qtgui/Perspectives/Corfunc/UI/__init__.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
+++ src/sas/qtgui/Perspectives/Corfunc/UI/__init__.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
@@ -0,0 +1,1 @@
+#!/usr/bin/python
Index: src/sas/qtgui/Perspectives/Corfunc/UnitTesting/CorfuncTest.py
===================================================================
--- src/sas/qtgui/Perspectives/Corfunc/UnitTesting/CorfuncTest.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
+++ src/sas/qtgui/Perspectives/Corfunc/UnitTesting/CorfuncTest.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
@@ -0,0 +1,102 @@
+import sys
+import unittest
+import webbrowser
+from time import sleep
+
+from PyQt5 import QtGui, QtWidgets
+from PyQt5.QtTest import QTest
+from PyQt5 import QtCore
+
+import sas.qtgui.path_prepare
+from sas.qtgui.Perspectives.Corfunc.CorfuncPerspective import CorfuncWindow
+from sas.sascalc.dataloader.loader import Loader
+
+from sas.qtgui.MainWindow.DataManager import DataManager
+import sas.qtgui.Utilities.LocalConfig
+import sas.qtgui.Utilities.GuiUtils as GuiUtils
+
+if not QtWidgets.QApplication.instance():
+    app = QtWidgets.QApplication(sys.argv)
+
+
+class CorfuncTest(unittest.TestCase):
+    '''Test the Corfunc Interface'''
+    def setUp(self):
+        '''Create the CorfuncWindow'''
+        self.widget = CorfuncWindow(None)
+
+    def tearDown(self):
+        '''Destroy the CorfuncWindow'''
+        self.widget.close()
+        self.widget = None
+
+    def testDefaults(self):
+        '''Test the GUI in its default state'''
+        self.assertIsInstance(self.widget, QtWidgets.QWidget)
+        self.assertEqual(self.widget.windowTitle(), "Corfunc Perspective")
+        self.assertEqual(self.widget.model.columnCount(), 1)
+        self.assertEqual(self.widget.model.rowCount(), 15)
+
+class CorfuncDataTest(unittest.TestCase):
+    '''Test the actual Corfunc Usase'''
+    def setUp(self):
+        '''Create a CorfuncWindow and populate it with data'''
+        self.widget = CorfuncWindow(None)
+        path = "../test/corfunc/test/98929.txt"
+        f = Loader().load(path)
+        manager = DataManager()
+        new_data = manager.create_gui_data(f[0], path)
+        output = {new_data.id: new_data}
+        item = QtGui.QStandardItem(True)
+        object_item = QtGui.QStandardItem()
+        object_item.setData(QtCore.QVariant(new_data))
+        item.setChild(0, object_item)
+        self.widget.setData([item])
+
+    def tearDown(self):
+        '''Destroy the CorfuncWindow'''
+        self.widget.close()
+        self.widget = None
+
+    def testProcess(self):
+        """Test the full analysis path"""
+        self.assertEqual(float(self.widget.bg.text()), 0.0)
+
+        self.widget.qMin.setValue(0.01)
+        self.widget.qMax1.setValue(0.20)
+        self.widget.qMax2.setValue(0.22)
+        self.widget.transformCombo.setCurrentIndex(0)
+
+        self.widget.calculateBgBtn.click()
+        self.assertTrue(float(self.widget.bg.text()) > 0.2)
+
+        self.widget.extrapolateBtn.click()
+        self.assertTrue(float(self.widget.guinierA.text()) > 1)
+        self.assertTrue(float(self.widget.guinierB.text()) < -10000)
+        self.assertTrue(float(self.widget.porodK.text()) > 10)
+        self.assertTrue(float(self.widget.porodSigma.text()) > 10)
+
+        #################################################
+        # The testing framework does not seem to handle
+        # multi-threaded Qt.  Signals emitted from threads
+        # are not detected when run in the unittest, even
+        # though they ARE handled in the actual application.
+        #################################################
+        # sleep(1)
+        # self.widget.transformBtn.click()
+        # while float(self.widget.longPeriod.text()) == 0.0:
+        #     print("Waiting")
+        #     sleep(1)
+        # self.assertTrue(float(self.widget.longPeriod.text()) > 10)
+        # self.assertTrue(float(self.widget.polydisp.text()) > 0)
+        # self.assertTrue(float(self.widget.localCrystal.text()) > 0)
+        # self.assertTrue(float(self.widget.longPeriod.text()) >
+        #                 float(self.widget.avgHardBlock.text()) > 0)
+        # self.assertTrue(float(self.widget.longPeriod.text()) >
+        #                 float(self.widget.avgIntThick.text()) > 0)
+        # self.assertTrue(float(self.widget.longPeriod.text()) >
+        #                 float(self.widget.avgCoreThick.text()) > 0)
+
+
+if __name__ == "__main__":
+    unittest.main()
Index: src/sas/qtgui/Perspectives/Corfunc/__init__.py
===================================================================
--- src/sas/qtgui/Perspectives/Corfunc/__init__.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
+++ src/sas/qtgui/Perspectives/Corfunc/__init__.py	(revision d4881f6af1db66e68fbbc5d7b5c67fc1f7bc1ea7)
@@ -0,0 +1,1 @@
+#!/usr/bin/python
