Commits

iorodeo committed fcd73a1

Worked on unifying the colorimeter GUI components.

Comments (0)

Files changed (12)

python/Colorimeter/colorimeter_common/__init__.py

-import file_tools
-import table_widget

python/Colorimeter/colorimeter_common/constants.py

+# Development
+DEVEL_FAKE_MEASURE = True 
+
+# Serial ports
+DFLT_PORT_WINDOWS = 'com1' 
+DFLT_PORT_LINUX = '/dev/ttyACM0' 
+
+# LEDs
+DFLT_LED_COLOR = 'red'
+COLOR2LED_DICT = {'red':0,'green':1,'blue': 2,'white': 3} 
+
+# Data table
+TABLE_MIN_ROW_COUNT = 4 
+TABLE_COL_COUNT = 2 
+
+# Plotting
+PLOT_FIGURE_NUM = 1
+PLOT_BAR_WIDTH = 0.8
+PLOT_TEXT_Y_OFFSET = 0.01
+PLOT_YLIM_ADJUST = 1.15
+
+# No value symbols
+NO_VALUE_SYMBOL_LABEL = '_nv_'
+NO_VALUE_SYMBOL_NUMBER = 'nan'
+
+# Data fit type
+FIT_TYPE = 'force_zero'

python/Colorimeter/colorimeter_common/file_tools.py

-from __future__ import print_function
-import os
-import yaml
-import time
-
-def getUserTestSolutionDir(userHome): 
-    return os.path.join(userHome,'.iorodeo_colorimeter','data')
-
-def getTestSolutionFilesFromDir(loc): 
-    testFiles = os.listdir(loc) 
-    testFiles = [name for name in testFiles if '.yaml' in name]
-    testFiles = [os.path.join(loc,name) for name in testFiles]
-    return testFiles
-
-def loadTestSolutionDict(fileList,tag=''):
-    """
-    Creates test solutions dictionary from list of test solution
-    files.
-    """
-    testDict = {}
-    testFiles = [name for name in fileList if '.yaml' in name]
-    for name in testFiles:
-        try:
-            data = importTestSolutionData(name)
-        except IOError, e:
-            continue
-        if data is None:
-            continue
-        if tag:
-            key = '{0} ({1})'.format(data['name'],tag)
-        else:
-            key = data['name']
-        testDict[key] = name
-    return testDict
-
-def loadUserTestSolutionDict(userHome):
-    """
-    Load dictionary of test solution names to test solution data files.
-    """
-    userDir = getUserTestSolutionDir(userHome)
-    fileList = getTestSolutionFilesFromDir(userDir)
-    testDict = loadTestSolutionDict(fileList)
-    return testDict
-
-def importTestSolutionData(fileName): 
-    """
-    Imports the test solution data with the given filename from the users
-    test solutions directory.
-    """
-    with open(fileName,'r') as fid:
-        data = yaml.load(fid)
-    return data
-
-def exportTestSolutionData(userHome, solutionName, dataList, color, dateStr):
-    """
-    Exports test solution data to the users directory. Data is saved 
-    as a yaml file.
-    """
-    fileName = getUniqueSolutionFileName(userHome, solutionName)
-    dataDict = {
-            'name': solutionName,
-            'date': dateStr,
-            'led' : color,
-            'values': [list(x) for x in dataList],
-            }
-    with open(fileName,'w') as fid: 
-        yaml.dump(dataDict,fid)
-
-def deleteTestSolution(userHome, solutionName):
-    """
-    Deletes test solution from user test solution directory.
-    """
-    testDict = loadUserTestSolutionDict(userHome)
-    fileName = testDict[solutionName]
-    os.remove(fileName)
-
-def isUniqueSolutionName(userHome, solutionName):
-    """
-    Checks to see whether test solution name is unique.
-    """
-    testSolutionDict = loadUserTestSolutionDict(userHome)
-    return solutionName not in testSolutionDict 
-
-def isUniqueSolutionFileName(userHome,fileName):
-    """
-    Check whether or not filename for test solution is unique.
-    """
-    userDir = getUserTestSolutionDir(userHome)
-    fileList = getTestSolutionFilesFromDir(userDir)
-    return fileName not in fileList
-
-def getUniqueSolutionFileName(userHome, solutionName):
-    """
-    Returns unique (human readable) filename for the given test solution 
-    name.
-    """
-    testSolutionDir = getUserTestSolutionDir(userHome)
-    fileNameBase  = "".join([x for x in solutionName if x.isalpha() or x.isdigit()])
-    fileName = os.path.join(testSolutionDir, '{0}.yaml'.format(fileNameBase))
-    done = False
-    cnt = 0
-    while not isUniqueSolutionFileName(userHome, fileName):
-        cnt += 1
-        fileName = os.path.join(testSolutionDir,'{0}_{1}.yaml'.format(fileNameBase,cnt))
-    return fileName
-

python/Colorimeter/colorimeter_common/import_export.py

+from __future__ import print_function
+import os
+import yaml
+import time
+
+def getUserTestSolutionDir(userHome): 
+    return os.path.join(userHome,'.iorodeo_colorimeter','data')
+
+def getTestSolutionFilesFromDir(loc): 
+    testFiles = os.listdir(loc) 
+    testFiles = [name for name in testFiles if '.yaml' in name]
+    testFiles = [os.path.join(loc,name) for name in testFiles]
+    return testFiles
+
+def loadTestSolutionDict(fileList,tag=''):
+    """
+    Creates test solutions dictionary from list of test solution
+    files.
+    """
+    testDict = {}
+    testFiles = [name for name in fileList if '.yaml' in name]
+    for name in testFiles:
+        try:
+            data = importTestSolutionData(name)
+        except IOError, e:
+            continue
+        if data is None:
+            continue
+        if tag:
+            key = '{0} ({1})'.format(data['name'],tag)
+        else:
+            key = data['name']
+        testDict[key] = name
+    return testDict
+
+def loadUserTestSolutionDict(userHome):
+    """
+    Load dictionary of test solution names to test solution data files.
+    """
+    userDir = getUserTestSolutionDir(userHome)
+    fileList = getTestSolutionFilesFromDir(userDir)
+    testDict = loadTestSolutionDict(fileList)
+    return testDict
+
+def importTestSolutionData(fileName): 
+    """
+    Imports the test solution data with the given filename from the users
+    test solutions directory.
+    """
+    with open(fileName,'r') as fid:
+        data = yaml.load(fid)
+    return data
+
+def exportTestSolutionData(userHome, solutionName, dataList, color, dateStr):
+    """
+    Exports test solution data to the users directory. Data is saved 
+    as a yaml file.
+    """
+    fileName = getUniqueSolutionFileName(userHome, solutionName)
+    dataDict = {
+            'name': solutionName,
+            'date': dateStr,
+            'led' : color,
+            'values': [list(x) for x in dataList],
+            }
+    with open(fileName,'w') as fid: 
+        yaml.dump(dataDict,fid)
+
+def deleteTestSolution(userHome, solutionName):
+    """
+    Deletes test solution from user test solution directory.
+    """
+    testDict = loadUserTestSolutionDict(userHome)
+    fileName = testDict[solutionName]
+    os.remove(fileName)
+
+def isUniqueSolutionName(userHome, solutionName):
+    """
+    Checks to see whether test solution name is unique.
+    """
+    testSolutionDict = loadUserTestSolutionDict(userHome)
+    return solutionName not in testSolutionDict 
+
+def isUniqueSolutionFileName(userHome,fileName):
+    """
+    Check whether or not filename for test solution is unique.
+    """
+    userDir = getUserTestSolutionDir(userHome)
+    fileList = getTestSolutionFilesFromDir(userDir)
+    return fileName not in fileList
+
+def getUniqueSolutionFileName(userHome, solutionName):
+    """
+    Returns unique (human readable) filename for the given test solution 
+    name.
+    """
+    testSolutionDir = getUserTestSolutionDir(userHome)
+    fileNameBase  = "".join([x for x in solutionName if x.isalpha() or x.isdigit()])
+    fileName = os.path.join(testSolutionDir, '{0}.yaml'.format(fileNameBase))
+    done = False
+    cnt = 0
+    while not isUniqueSolutionFileName(userHome, fileName):
+        cnt += 1
+        fileName = os.path.join(testSolutionDir,'{0}_{1}.yaml'.format(fileNameBase,cnt))
+    return fileName
+

python/Colorimeter/colorimeter_common/main_window.py

+from __future__ import print_function
+import os
+import functools
+import platform
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+
+from colorimeter_common import constants
+from colorimeter_serial import Colorimeter
+
+class MainWindowCommon(QtGui.QMainWindow):
+
+    def __init__(self,parent=None):
+        super(MainWindowCommon,self).__init__(parent)
+        print('MainWindowCommon.__init__')
+
+    def connectActions(self):
+        print('MainWindowCommon.connectActions')
+        self.portLineEdit.editingFinished.connect(self.portChanged_Callback)
+        self.connectPushButton.pressed.connect(self.connectPressed_Callback)
+        self.connectPushButton.clicked.connect(self.connectClicked_Callback)
+        self.calibratePushButton.pressed.connect(self.calibratePressed_Callback)
+        self.calibratePushButton.clicked.connect(self.calibrateClicked_Callback)
+        self.measurePushButton.clicked.connect(self.measureClicked_Callback)
+        self.measurePushButton.pressed.connect(self.measurePressed_Callback)
+        self.clearPushButton.pressed.connect(self.clearPressed_Callback)
+        self.clearPushButton.clicked.connect(self.clearClicked_Callback)
+
+        for color in constants.COLOR2LED_DICT:
+            button = getattr(self,'{0}RadioButton'.format(color))
+            callback = functools.partial(self.colorRadioButtonClicked_Callback, color)
+            button.clicked.connect(callback)
+        self.plotPushButton.clicked.connect(self.plotPushButtonClicked_Callback)
+
+        self.actionSave.triggered.connect(self.saveFile_Callback)
+        self.actionSave.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_S)
+        self.actionLoad.triggered.connect(self.loadFile_Callback)
+        self.actionLoad.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_L)
+        self.actionEditTestSolutions.triggered.connect(self.editTestSolutions_Callback)
+
+    def initialize(self):
+
+        print('MainWindowCommon.initialize')
+        self.dev = None
+        self.fig = None
+        self.isCalibrated = False
+
+        # Set default port based on system
+        osType = platform.system()
+        if osType == 'Linux': 
+            self.port = constants.DFLT_PORT_LINUX 
+        else: 
+            self.port = constants.DFLT_PORT_WINDOWS 
+        # Get users home directory
+        self.userHome = os.getenv('USERPROFILE')
+        if self.userHome is None:
+            self.userHome = os.getenv('HOME')
+        self.lastSaveDir = self.userHome
+        self.statusbar.showMessage('Not Connected')
+        self.portLineEdit.setText(self.port) 
+        self.setLEDColor(constants.DFLT_LED_COLOR)
+
+    def portChanged_Callback(self):
+        self.port = str(self.portLineEdit.text())
+
+    def connectPressed_Callback(self):
+        if self.dev == None:
+            self.connectPushButton.setText('Disconnect')
+            self.connectPushButton.setFlat(True)
+            self.portLineEdit.setEnabled(False)
+            self.statusbar.showMessage('Connecting...')
+
+    def connectClicked_Callback(self):
+        """
+        Connect/Disconnect to colorimeter device.
+        """
+        if self.dev == None:
+            try:
+                self.dev = Colorimeter(self.port)
+                self.numSamples = self.dev.getNumSamples()
+                connected = True
+            except Exception, e:
+                QtGui.QMessageBox.critical(self,'Error', str(e))
+                self.connectPushButton.setText('Connect')
+                self.statusbar.showMessage('Not Connected')
+                self.portLineEdit.setEnabled(True)
+                connected = False
+        else:
+            self.connectPushButton.setText('Connect')
+            try:
+                self.cleanUpAndCloseDevice()
+            except Exception, e:
+                QtGui.QMessageBox.critical(self,'Error', str(e))
+            connected = False
+            self.isCalibrated = False
+        self.updateWidgetEnabled()
+
+    def saveFile_Callback(self):
+        pass
+
+    def loadFile_Callback(self):
+        pass
+
+    def editTestSolutions_Callback(self):
+        pass
+
+

python/Colorimeter/colorimeter_common/standard_curve.py

+import numpy 
+
+def getLinearFit(xList,yList,fitType='force_zero',numPts=500): 
+    """
+    Compuetes the coefficient (slope of absorbance vs concentration)
+    """
+    if fitType == 'force_zero': 
+        xArray = numpy.array(xList)
+        yArray = numpy.array(yList)
+        numer = (xArray*yArray).sum()
+        denom = (xArray*xArray).sum()
+        slope = numer/denom
+        xFit = numpy.linspace(min(xList), max(xList),numPts)
+        yFit = slope*xFit
+    else:
+        polyFit = numpy.polyfit(xList,yList,1)
+        xFit = numpy.linspace(min(xList), max(xList), numPts)
+        yFit = numpy.polyval(polyFit, xFit)
+        slope = polyFit[0]
+    return slope,xFit,yFit
+    
+
+def getCoefficient(abso,conc,fitType='force_zero'):
+    """
+    Returns the calibration coefficient given absorbance and concentration
+    values for a standard curve.
+    """
+    slope, dummy0, dummy1 = getLinearFit(abso,conc,fitType=fitType)
+    return 1.0/slope

python/Colorimeter/colorimeter_common/table_widget.py

 from PyQt4 import QtCore
 from PyQt4 import QtGui
+from constants import TABLE_MIN_ROW_COUNT
+from constants import TABLE_COL_COUNT
 
-MIN_ROW_COUNT = 1
-COL_COUNT = 2 
 
 class ColorimeterTableWidget(QtGui.QTableWidget):
 
     def __init__(self,parent=None):
         super(ColorimeterTableWidget,self).__init__(parent=parent)
         self.measIndex = 0
-        self.minRowCount = MIN_ROW_COUNT
+        self.minRowCount = TABLE_MIN_ROW_COUNT
         self.contextMenuEvent = self.ContextMenu_Callback
         self.copyAction = QtGui.QAction(self)
         self.copyAction.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_C)
         self.addAction(self.backspaceAction)
         self.itemChanged.connect(self.itemChanged_Callback)
 
-        self.setColumnCount(COL_COUNT)
+        self.setColumnCount(TABLE_COL_COUNT)
         self.setRowCount(self.minRowCount)
+        self.updateFlag = True
 
     def itemChanged_Callback(self,item):
-        if item.column() == 0:
+        if self.updateFlag:
             self.updateFunc()
 
     def ContextMenu_Callback(self,event):
                 self.measIndex-=1
             self.removeRow(ind)
 
-        if self.rowCount() < MIN_ROW_COUNT:
-            self.setRowCount(MIN_ROW_COUNT)
-            for row in range(self.measIndex,MIN_ROW_COUNT): 
-                for col in range(0,COL_COUNT): 
+        if self.rowCount() < TABLE_MIN_ROW_COUNT:
+            self.setRowCount(TABLE_MIN_ROW_COUNT)
+            for row in range(self.measIndex,TABLE_MIN_ROW_COUNT): 
+                for col in range(0,TABLE_COL_COUNT): 
                     tableItem = QtGui.QTableWidgetItem() 
                     tableItem.setFlags(QtCore.Qt.NoItemFlags) 
                     self.setItem(row,col,tableItem)
         Copies data from the table widget to the clipboard based on the current
         selection.
         """
-        selectedList = self.getTableWidgetSelectedList()
+        selectedList = self.getSelectedList()
 
         # Create string to send to clipboard
         clipboardList = []
             selectedList.append(rowList)
         return selectedList
 
-    def cleanDataTable(self,setup=False,msg=''):
+    def clean(self,setup=False,msg=''):
         """
         Removes any existing data from the table widget. If setup is False then
         I dialog request confirmation if presented. 
             return True
 
         if reply == QtGui.QMessageBox.Yes:
-            #if self.fig is not None:
-            #    plt.close(self.fig)
-            #    self.fig = None
-            self.setRowCount(MIN_ROW_COUNT)
-            self.setColumnCount(COL_COUNT)
-            for row in range(MIN_ROW_COUNT+1):
-                for col in range(COL_COUNT+1):
+            self.updateFlag = False
+            self.setRowCount(TABLE_MIN_ROW_COUNT)
+            self.setColumnCount(TABLE_COL_COUNT)
+            for row in range(TABLE_MIN_ROW_COUNT+1):
+                for col in range(TABLE_COL_COUNT+1):
                     tableItem = QtGui.QTableWidgetItem()
                     tableItem.setFlags(QtCore.Qt.NoItemFlags)
                     self.setItem(row,col,tableItem)
             self.measIndex = 0
+            self.updateFlag = True
             return True
         else:
             return False
 
-    def addDataToTable(self,item0,item1,selectEdit=False):
+    def addData(self,item0,item1,selectAndEdit=False):
         """
         Added data to table widget. If selectEdit is set to True then the
         concetration element is selected and opened for editing by the
         user.
         """
+        self.updateFlag = False
         rowCount = self.measIndex+1
         if rowCount > self.minRowCount:
-            self.tableWidget.setRowCount(rowCount)
+            self.setRowCount(rowCount)
         tableItem = QtGui.QTableWidgetItem()
         tableItem.setText(item1)
         tableItem.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
-        self.tableWidget.setItem(self.measIndex,1,tableItem)
+        self.setItem(self.measIndex,1,tableItem)
 
         tableItem = QtGui.QTableWidgetItem()
         tableItem.setText(item0)
-        self.tableWidget.setItem(self.measIndex,0,tableItem)
-        if selectEdit:
+        self.setItem(self.measIndex,0,tableItem)
+        if selectAndEdit:
             tableItem.setSelected(True)
-            self.tableWidget.setCurrentCell(self.measIndex,0)
-            self.tableWidget.editItem(self.tableWidget.currentItem()) 
+            self.setCurrentCell(self.measIndex,0)
+            self.editItem(self.currentItem()) 
         self.measIndex+=1
+        self.updateFlag = True
+
+    def getData(self,noValueInclude=True,noValueSymb=None):
+        """
+        Get data from table widget. Data is returned as a list as rows
+        from the data table.
+        """
+        dataList = []
+        for i in range(self.measIndex):
+            item0 = self.item(i,0)
+            try:
+                value0 = str(item0.text())
+            except AttributeError:
+                value0 = None
+            if not value0: 
+                if noValueInclude: 
+                    if noValueSymb is None:
+                        value0 = '{0}'.format(item0.row()+1)
+                    else:
+                        value0 = NO_VALUE_SYMBOL
+                else:
+                    continue
+            item1 = self.item(i,1)
+            if item1 is None:
+                continue
+            try:
+                value1 = str(item1.text())
+            except AttributeError:
+                continue
+            dataList.append((value0,value1))
+        return dataList
 
     def updateFunc(self):
         pass

python/Colorimeter/colorimeter_measurement_gui/colorimeter_measurement.py

 from __future__ import print_function
 import os 
 import sys 
-import platform
+#import platform
 import functools
 import random 
 import time
 import yaml
 import numpy
 import pkg_resources
-
 # TEMPORARY - FOR DEVELOPMENT ##################
 import random
 ################################################
 import matplotlib
 matplotlib.use('Qt4Agg')
 import matplotlib.pyplot as plt 
+plt.ion()
 
 from PyQt4 import QtCore
 from PyQt4 import QtGui
 
 from colorimeter_measurement_ui import Ui_MainWindow 
-from colorimeter_serial import Colorimeter
-from colorimeter_common import file_tools
+#from colorimeter_serial import Colorimeter
+from colorimeter_common import constants 
+from colorimeter_common import import_export 
+from colorimeter_common import standard_curve
+from colorimeter_common.main_window import MainWindowCommon
 
-DEVEL_FAKE_MEASURE = True 
-
-DFLT_PORT_WINDOWS = 'com1' 
-DFLT_PORT_LINUX = '/dev/ttyACM0' 
-DFLT_LED_COLOR = 'red'
-COLOR2LED_DICT = {'red':0,'green':1,'blue': 2,'white': 3} 
-
-TABLE_MIN_ROW_COUNT = 1
-TABLE_COL_COUNT = 2 
-
-PLOT_FIGURE_NUM = 1
-PLOT_BAR_WIDTH = 0.8
-PLOT_TEXT_Y_OFFSET = 0.01
-PLOT_YLIM_ADJUST = 1.15
-
-NO_VALUE_SYMBOL = '_NV_'
-
-class MeasurementMainWindow(QtGui.QMainWindow, Ui_MainWindow):
+#class MeasurementMainWindow(QtGui.QMainWindow, Ui_MainWindow):
+class MeasurementMainWindow(MainWindowCommon, Ui_MainWindow):
 
     def __init__(self,parent=None):
         super(MeasurementMainWindow,self).__init__(parent)
         """
         Connect actions for widgets in measurement GUI.
         """
-        self.portLineEdit.editingFinished.connect(self.portChanged_Callback)
-        self.connectPushButton.pressed.connect(self.connectPressed_Callback)
-        self.connectPushButton.clicked.connect(self.connectClicked_Callback)
-        self.calibratePushButton.pressed.connect(self.calibratePressed_Callback)
-        self.calibratePushButton.clicked.connect(self.calibrateClicked_Callback)
-        self.measurePushButton.clicked.connect(self.measureClicked_Callback)
-        self.measurePushButton.pressed.connect(self.measurePressed_Callback)
-        self.clearPushButton.pressed.connect(self.clearPressed_Callback)
-        self.clearPushButton.clicked.connect(self.clearClicked_Callback)
+        super(MeasurementMainWindow,self).connectActions()
+        #self.portLineEdit.editingFinished.connect(self.portChanged_Callback)
+        #self.connectPushButton.pressed.connect(self.connectPressed_Callback)
+        #self.connectPushButton.clicked.connect(self.connectClicked_Callback)
+        #self.calibratePushButton.pressed.connect(self.calibratePressed_Callback)
+        #self.calibratePushButton.clicked.connect(self.calibrateClicked_Callback)
+        #self.measurePushButton.clicked.connect(self.measureClicked_Callback)
+        #self.measurePushButton.pressed.connect(self.measurePressed_Callback)
+        #self.clearPushButton.pressed.connect(self.clearPressed_Callback)
+        #self.clearPushButton.clicked.connect(self.clearClicked_Callback)
+
+        #for color in constants.COLOR2LED_DICT:
+        #    button = getattr(self,'{0}RadioButton'.format(color))
+        #    callback = functools.partial(self.colorRadioButtonClicked_Callback, color)
+        #    button.clicked.connect(callback)
+        #self.plotPushButton.clicked.connect(self.plotPushButtonClicked_Callback)
+
         self.testSolutionComboBox.currentIndexChanged.connect(
                 self.testSolutionChanged_Callback
                 )
-
-        for color in COLOR2LED_DICT:
-            button = getattr(self,'{0}RadioButton'.format(color))
-            callback = functools.partial(self.colorRadioButton_Clicked, color)
-            button.clicked.connect(callback)
-        self.plotPushButton.clicked.connect(self.plotPushButton_Clicked)
-
         self.actionIncludeDefaultTestSolutions.toggled.connect(
                 self.populateTestSolutionComboBox
                 )
                 self.populateTestSolutionComboBox
                 )
 
-        self.actionSave.triggered.connect(self.saveFile_Callback)
-        self.actionSave.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_S)
-        self.actionLoad.triggered.connect(self.loadFile_Callback)
-        self.actionLoad.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_L)
-        self.actionEditTestSolutions.triggered.connect(self.editTestSolutions_Callback)
-
-        #self.tableWidget.contextMenuEvent = self.tableWidgetContextMenu_Callback
-
-        #self.tableWidget_CopyAction = QtGui.QAction(self.tableWidget)
-        #self.tableWidget_CopyAction.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_C)
-        #self.tableWidget_CopyAction.triggered.connect(self.copyTableWidgetData)
-        #self.tableWidget.addAction(self.tableWidget_CopyAction)
-
-        #self.tableWidget_DeleteAction = QtGui.QAction(self.tableWidget)
-        #self.tableWidget_DeleteAction.setShortcut(QtCore.Qt.Key_Delete)
-        #self.tableWidget_DeleteAction.triggered.connect(self.deleteTableWidgetData)
-        #self.tableWidget.addAction(self.tableWidget_DeleteAction)
-
-        #self.tableWidget_BackspaceAction = QtGui.QAction(self.tableWidget)
-        #self.tableWidget_BackspaceAction.setShortcut(QtCore.Qt.Key_Backspace)
-        #self.tableWidget_BackspaceAction.triggered.connect(self.deleteTableWidgetData)
-        #self.tableWidget.addAction(self.tableWidget_BackspaceAction)
-
-        #self.tableWidget.itemChanged.connect(self.tableWidgetItemChanged_Callback)
-
+        #self.actionSave.triggered.connect(self.saveFile_Callback)
+        #self.actionSave.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_S)
+        #self.actionLoad.triggered.connect(self.loadFile_Callback)
+        #self.actionLoad.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_L)
+        #self.actionEditTestSolutions.triggered.connect(self.editTestSolutions_Callback)
 
     def initialize(self):
         """
         Initializes the measurement GUI. Sets buttons to default data, sets initial port
         based on OS, get the users home directory. etc. 
         """
-
-        self.dev = None
-        #self.measIndex = 0
-        self.isCalibrated = False
+        super(MeasurementMainWindow,self).initialize()
+        #self.dev = None
+        #self.fig = None
+        #self.isCalibrated = False
         self.coeff = None
-        self.fig = None
-        plt.ion()
         
-        # Set default port based on system
-        osType = platform.system()
-        if osType == 'Linux': 
-            self.port = DFLT_PORT_LINUX 
-        else: 
-            self.port = DFLT_PORT_WINDOWS 
-        self.portLineEdit.setText(self.port) 
-
-        # Get users home directory
-        self.userHome = os.getenv('USERPROFILE')
-        if self.userHome is None:
-            self.userHome = os.getenv('HOME')
-        self.lastSaveDir = self.userHome
-        self.statusbar.showMessage('Not Connected')
-
-        # Set default value for LED color
-        self.setLEDColor(DFLT_LED_COLOR)
+        ## Set default port based on system
+        #osType = platform.system()
+        #if osType == 'Linux': 
+        #    self.port = constants.DFLT_PORT_LINUX 
+        #else: 
+        #    self.port = constants.DFLT_PORT_WINDOWS 
+        ## Get users home directory
+        #self.userHome = os.getenv('USERPROFILE')
+        #if self.userHome is None:
+        #    self.userHome = os.getenv('HOME')
+        #self.lastSaveDir = self.userHome
+        #self.statusbar.showMessage('Not Connected')
+        #self.portLineEdit.setText(self.port) 
+        #self.setLEDColor(constants.DFLT_LED_COLOR)
 
         # Set up data table
         self.tableWidget.updateFunc = self.updatePlot
-        self.tableWidget.cleanDataTable(setup=True)
+        self.tableWidget.clean(setup=True)
         concentrationStr = QtCore.QString.fromUtf8("Concentration (\xc2\xb5M)")
         self.tableWidget.setHorizontalHeaderLabels(('Sample', concentrationStr)) 
         self.tableWidget.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
 
         self.updateWidgetEnabled()
 
-    #def tableWidgetItemChanged_Callback(self,item):
-    #    print('tableWidgetItemChanged_Callback')
-    #    if item.column() == 0:
-    #        self.updatePlot()
-
-    #def tableWidgetContextMenu_Callback(self,event):
-    #    """
-    #    Callback function for the table widget context menus. Currently
-    #    handles copy and delete actions.
-    #    """
-    #    menu = QtGui.QMenu(self)
-    #    copyAction = menu.addAction("Copy")
-    #    deleteAction = menu.addAction("Delete")
-    #    action = menu.exec_(self.tableWidget.mapToGlobal(event.pos()))
-    #    if action == copyAction:
-    #        self.copyTableWidgetData()
-    #    if action == deleteAction:
-    #        self.deleteTableWidgetData()
-
-    #def deleteTableWidgetData(self):
-    #    """
-    #    Deletes data from the table widget based on the current selection.
-    #    """
-    #    removeList = []
-    #    for i in range(self.tableWidget.rowCount()):
-    #        item0 = self.tableWidget.item(i,0)
-    #        item1 = self.tableWidget.item(i,1)
-    #        if self.tableWidget.isItemSelected(item0):
-    #            if not self.tableWidget.isItemSelected(item1):
-    #                item0.setText("")
-    #        if self.tableWidget.isItemSelected(item1):
-    #            removeList.append(item1.row())
-
-    #    for ind in reversed(removeList):
-    #        if self.measIndex > 0:
-    #            self.measIndex-=1
-    #        self.tableWidget.removeRow(ind)
-
-    #    if self.tableWidget.rowCount() < TABLE_MIN_ROW_COUNT:
-    #        self.tableWidget.setRowCount(TABLE_MIN_ROW_COUNT)
-    #        for row in range(self.measIndex,TABLE_MIN_ROW_COUNT): 
-    #            for col in range(0,TABLE_COL_COUNT): 
-    #                tableItem = QtGui.QTableWidgetItem() 
-    #                tableItem.setFlags(QtCore.Qt.NoItemFlags) 
-    #                self.tableWidget.setItem(row,col,tableItem)
-
-    #    if plt.fignum_exists(PLOT_FIGURE_NUM):
-    #        self.updatePlot()
-
-    #def copyTableWidgetData(self): 
-    #    """
-    #    Copies data from the table widget to the clipboard based on the current
-    #    selection.
-    #    """
-    #    selectedList = self.getTableWidgetSelectedList()
-
-    #    # Create string to send to clipboard
-    #    clipboardList = []
-    #    for j, rowList in enumerate(selectedList):
-    #        for i, value in enumerate(rowList):
-    #            if not value:
-    #                clipboardList.append('{0}'.format(j))
-    #            else:
-    #                clipboardList.append(value)
-    #            if i < len(rowList)-1:
-    #                clipboardList.append(" ")
-    #        clipboardList.append('\r\n')
-    #    clipboardStr = ''.join(clipboardList)
-    #    clipboard = QtGui.QApplication.clipboard()
-    #    clipboard.setText(clipboardStr)
-
-    #def getTableWidgetSelectedList(self):
-    #    """
-    #    Returns list of select items in the table widget. Note, assumes that
-    #    selection mode for the table is ContiguousSelection.
-    #    """
-    #    selectedList = []
-    #    for i in range(self.tableWidget.rowCount()): 
-    #        rowList = []
-    #        for j in range(self.tableWidget.columnCount()):
-    #            item = self.tableWidget.item(i,j)
-    #            if self.tableWidget.isItemSelected(item):
-    #                rowList.append(str(item.text()))
-    #        selectedList.append(rowList)
-    #    return selectedList
-
     def testSolutionChanged_Callback(self,index):
         print('testSolutionChanged_Callback', index)
         self.updateTestSolution(index)
             testSolutionDict.update(self.user_TestSolutionDict)
             testSolutionDict.update(self.default_TestSolutionDict)
             pathName = testSolutionDict[itemText]
-            data = file_tools.importTestSolutionData(pathName)
+            data = import_export.importTestSolutionData(pathName)
             self.coeff = getCoefficientFromData(data)
             self.coefficientLineEdit.setText('{0:1.1f}'.format(1.0e6*self.coeff))
             self.setLEDColor(data['led'])
         self.updateWidgetEnabled()
 
-    def portChanged_Callback(self):
-        self.port = str(self.portLineEdit.text())
+#    def portChanged_Callback(self):
+#        self.port = str(self.portLineEdit.text())
 
-    def connectPressed_Callback(self):
-        if self.dev == None:
-            self.connectPushButton.setText('Disconnect')
-            self.connectPushButton.setFlat(True)
-            self.portLineEdit.setEnabled(False)
-            self.statusbar.showMessage('Connecting...')
+#    def connectPressed_Callback(self):
+#        if self.dev == None:
+#            self.connectPushButton.setText('Disconnect')
+#            self.connectPushButton.setFlat(True)
+#            self.portLineEdit.setEnabled(False)
+#            self.statusbar.showMessage('Connecting...')
 
-    def connectClicked_Callback(self):
-        if self.dev == None:
-            try:
-                self.dev = Colorimeter(self.port)
-                self.numSamples = self.dev.getNumSamples()
-                connected = True
-            except Exception, e:
-                QtGui.QMessageBox.critical(self,'Error', str(e))
-                self.connectPushButton.setText('Connect')
-                self.statusbar.showMessage('Not Connected')
-                self.portLineEdit.setEnabled(True)
-                connected = False
-        else:
-            self.connectPushButton.setText('Connect')
-            try:
-                self.cleanUpAndCloseDevice()
-            except Exception, e:
-                QtGui.QMessageBox.critical(self,'Error', str(e))
-            connected = False
-        self.updateWidgetEnabled()
+#    def connectClicked_Callback(self):
+#        if self.dev == None:
+#            try:
+#                self.dev = Colorimeter(self.port)
+#                self.numSamples = self.dev.getNumSamples()
+#                connected = True
+#            except Exception, e:
+#                QtGui.QMessageBox.critical(self,'Error', str(e))
+#                self.connectPushButton.setText('Connect')
+#                self.statusbar.showMessage('Not Connected')
+#                self.portLineEdit.setEnabled(True)
+#                connected = False
+#        else:
+#            self.connectPushButton.setText('Connect')
+#            try:
+#                self.cleanUpAndCloseDevice()
+#            except Exception, e:
+#                QtGui.QMessageBox.critical(self,'Error', str(e))
+#            connected = False
+#        self.updateWidgetEnabled()
 
-
-#    def cleanDataTable(self,setup=False,msg=''):
-#        """
-#        Removes any existing data from the table widget. If setup is False then
-#        I dialog request confirmation if presented. 
-#        """
-#        if setup:
-#            reply = QtGui.QMessageBox.Yes
-#        elif len(self.tableWidget.item(0,1).text()):
-#            reply = QtGui.QMessageBox.question( self, 'Message', msg, 
-#                    QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
-#        else: 
-#            return True
-#
-#        if reply == QtGui.QMessageBox.Yes:
-#            if self.fig is not None:
-#                plt.close(self.fig)
-#                self.fig = None
-#            self.tableWidget.setRowCount(TABLE_MIN_ROW_COUNT)
-#            #self.tableWidget.setColumnCount(TABLE_COL_COUNT)
-#            for row in range(TABLE_MIN_ROW_COUNT+1):
-#                for col in range(TABLE_COL_COUNT+1):
-#                    tableItem = QtGui.QTableWidgetItem()
-#                    tableItem.setFlags(QtCore.Qt.NoItemFlags)
-#                    self.tableWidget.setItem(row,col,tableItem)
-#            self.measIndex = 0
-#            return True
-#        else:
-#            return False
-
-    def colorRadioButton_Clicked(self,color):
+    def colorRadioButtonClicked_Callback(self,color):
         if len(self.tableWidget.item(0,1).text()):
             chn_msg = "Changing channels will clear all data. Continue?"
-            response = self.tableWidget.cleanDataTable(msg=chn_msg)
+            response = self.tableWidget.clean(msg=chn_msg)
             if not response:
+                self.closeFigure()
                 self.setLEDColor(self.currentColor)
         self.currentColor = color
 
-    def plotPushButton_Clicked(self):
+    def plotPushButtonClicked_Callback(self):
         self.updatePlot(create=True)
 
     def calibratePressed_Callback(self):
 
     def calibrateClicked_Callback(self):
         print('calibratePushButton_Clicked')
-        if not DEVEL_FAKE_MEASURE:
+        if not constants.DEVEL_FAKE_MEASURE:
             self.dev.calibrate()
         self.isCalibrated = True
         self.calibratePushButton.setFlat(False)
         self.statusbar.showMessage('Connected, Mode: Measuring...')
 
     def measureClicked_Callback(self):
-
         rowCount = self.tableWidget.measIndex+1
-        ledNumber = COLOR2LED_DICT[self.currentColor]
-
-        if DEVEL_FAKE_MEASURE:  
+        ledNumber = constants.COLOR2LED_DICT[self.currentColor]
+        if constants.DEVEL_FAKE_MEASURE:  
             conc = random.random()
         else:
             freq, trans, absorb = self.dev.getMeasurement()
 
         concStr = '{0:1.2f}'.format(conc)
         self.measurePushButton.setFlat(False)
-
-
-        if rowCount > TABLE_MIN_ROW_COUNT:
-            self.tableWidget.setRowCount(rowCount)
-
-        # Put measurement into table
-        tableItemFlags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
-        tableItem = QtGui.QTableWidgetItem()
-        tableItem.setText(concStr)
-        tableItem.setFlags(tableItemFlags)
-        self.tableWidget.setItem(self.measIndex,1,tableItem)
-        self.tableWidget.setCurrentItem(tableItem)
-        tableItem.setSelected(False)
-
-        # Select Sample table cell for data entry
-        tableItemFlags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
-        tableItemFlags |= QtCore.Qt.ItemIsEditable
-        tableItem = QtGui.QTableWidgetItem()
-        tableItem.setFlags(tableItemFlags)
-        tableItem.setSelected(True)
-        self.tableWidget.setItem(self.measIndex,0,tableItem)
-        self.tableWidget.setCurrentCell(self.measIndex,0)
-        self.tableWidget.editItem(self.tableWidget.currentItem()) 
-
-        self.measIndex+=1
-
-        if plt.fignum_exists(PLOT_FIGURE_NUM):
-            self.updatePlot()
+        self.tableWidget.addData('',concStr,selectAndEdit=True)
+        self.updatePlot()
         self.updateWidgetEnabled()
 
     def clearPressed_Callback(self):
     def clearClicked_Callback(self):
         if len(self.tableWidget.item(0,1).text()):
             erase_msg = "Clear all data?"
-            self.tableWidget.cleanDataTable(msg=erase_msg)
+            rsp = self.tableWidget.clean(msg=erase_msg)
+            if rsp:
+                self.closeFigure()
         self.clearPushButton.setFlat(False) 
         self.updateWidgetEnabled()
 
+    def closeFigure(self): 
+        if self.fig is not None and plt.fignum_exists(constants.PLOT_FIGURE_NUM): 
+            plt.close(self.fig)
+            self.fig = None
+
     def saveFile_Callback(self):
         # Ensure that there is data to save.
-        if self.measIndex <= 0: 
+        if self.tableWidget.measIndex <= 0: 
             errMsgTitle = 'Save Error'
             errMsg = 'No data to save'
             QtGui.QMessageBox.warning(self,errMsgTitle, errMsg)
         self.lastSaveDir =  os.path.split(fileName)[0]
         print(fileName)
 
-        dataList = self.getTableData(noValueSymb=NO_VALUE_SYMBOL) 
+        dataList = self.tableWidget.getData(noValueSymb=constants.NO_VALUE_SYMBOL_LABEL) 
         timeStr = time.strftime('%Y-%m-%d %H:%M:%S %Z') 
         headerList = [ 
                 '# {0}%s'.format(timeStr), 
         if self.dev is None:
             self.measurePushButton.setEnabled(False)
             self.calibratePushButton.setEnabled(False)
-            if self.measIndex > 0:
+            if self.tableWidget.measIndex > 0:
                 self.clearPushButton.setEnabled(True)
                 self.plotPushButton.setEnabled(True)
                 self.tableWidget.setEnabled(True)
 
     def updatePlot(self,create=False):
 
-        if self.measIndex == 0:
-            #  We don't have any measurements - close any existing plot and
-            #  return
-            if plt.fignum_exists(PLOT_FIGURE_NUM):
-                plt.close(self.fig)
-                self.fig = None
+        # Only create new figure is asked to do so
+        if not create and not plt.fignum_exists(constants.PLOT_FIGURE_NUM):
             return
 
-        if not create and not plt.fignum_exists(PLOT_FIGURE_NUM):
-            return
+        # Check if there is any data to plot
+        dataList = self.tableWidget.getData()
+        dataList = [(x,float(y)) for x,y in dataList]
+        if not dataList:
+            self.closeFigure()
+            return 
 
-        dataList = self.getTableData()
+        # Unpack data 
         labelList, concList = zip(*dataList)
 
         # Create plot showing bar graph of data
         posList = range(1,len(concList)+1)
-        xlim = (posList[0]-0.5*PLOT_BAR_WIDTH, posList[-1]+1.5*PLOT_BAR_WIDTH)
-        ylim = (0,PLOT_YLIM_ADJUST*max(concList))
+        xlim = (
+                posList[0]  - 0.5*constants.PLOT_BAR_WIDTH, 
+                posList[-1] + 1.5*constants.PLOT_BAR_WIDTH,
+                )
+        ylim = (0,constants.PLOT_YLIM_ADJUST*max(concList))
 
         plt.clf()
-        self.fig = plt.figure(PLOT_FIGURE_NUM)
+        self.fig = plt.figure(constants.PLOT_FIGURE_NUM)
         self.fig.canvas.manager.set_window_title('Colorimeter Measurement: Concentration Plot')
         ax = self.fig.add_subplot(111)
-        ax.bar(posList,concList,width=PLOT_BAR_WIDTH,color='b',linewidth=2)
+        ax.bar(posList,concList,width=constants.PLOT_BAR_WIDTH,color='b',linewidth=2)
 
         for pos, value in zip(posList, concList): 
-            textXPos = pos + 0.5*PLOT_BAR_WIDTH
-            textYPos = value + PLOT_TEXT_Y_OFFSET
+            textXPos = pos + 0.5*constants.PLOT_BAR_WIDTH
+            textYPos = value + constants.PLOT_TEXT_Y_OFFSET
             valueStr = '{0:1.3f}'.format(value)
             ax.text(textXPos,textYPos, valueStr, ha ='center', va ='bottom') 
 
         ax.set_xlim(*xlim)
         ax.set_ylim(*ylim)
-        ax.set_xticks([x+0.5*PLOT_BAR_WIDTH for x in posList])
+        ax.set_xticks([x+0.5*constants.PLOT_BAR_WIDTH for x in posList])
         ax.set_xticklabels(labelList)
         ax.set_ylabel('Concentration')
         ax.set_xlabel('Samples')
         plt.draw() 
 
-    def getTableData(self,noValueSymb=None):
-
-        concList = []
-        for i in range(self.tableWidget.rowCount()):
-            item = self.tableWidget.item(i,1)
-            try:
-                value = float(item.text())
-                concList.append(value)
-            except ValueError, e:
-                errMsgTitle = 'Plot Error'
-                errMsg = 'Unable to convert value to float: {0}'.format(str(e))
-                QtGui.QMessageBox.warning(self,errMsgTitle, errMsg)
-                return
-
-        labelList = []
-        for i in range(self.tableWidget.rowCount()):
-            item = self.tableWidget.item(i,0)
-            label = str(item.text())
-            if not label:
-                if noValueSymb is None:
-                    labelList.append('{0}'.format(item.row()+1))
-                else:
-                    labelList.append(NO_VALUE_SYMBOL)
-            else:
-                labelList.append(label)
-
-        dataList = zip(labelList, concList)
-        return dataList
-
     def setLEDColor(self,color):
         self.currentColor = color
         button = getattr(self, '{0}RadioButton'.format(self.currentColor))
         #fileList = getTestSolutionFilesFromDir(default_TestSolutionDir)
         ## ---------------------------------------------------------------
         fileList = self.getTestSolutionFilesFromResources()
-        return file_tools.loadTestSolutionDict(fileList,tag='D')
+        return import_export.loadTestSolutionDict(fileList,tag='D')
 
     def loadUserTestSolutionDict(self):
         """
         Load the dictionary mapping test solution name to test solution data
         file from the user's directory.
         """
-        userTestSolutionDir = file_tools.getUserTestSolutionDir(self.userHome)
-        fileList = file_tools.getTestSolutionFilesFromDir(userTestSolutionDir)
-        return file_tools.loadTestSolutionDict(fileList,tag='U')
+        userTestSolutionDir = import_export.getUserTestSolutionDir(self.userHome)
+        fileList = import_export.getTestSolutionFilesFromDir(userTestSolutionDir)
+        return import_export.loadTestSolutionDict(fileList,tag='U')
 
     def getTestSolutionFilesFromResources(self): 
         """
     Compuetes the coefficient (slope of absorbance vs concentration)
     """
     values = data['values']
-    xList, yList = zip(*values)
-    xArray = numpy.array(xList)
-    yArray = numpy.array(yList)
-    numer = (xArray*yArray).sum()
-    denom = (xArray*xArray).sum()
-    slope = numer/denom
-    coeff = 1.0/slope
-    return coeff 
+    abso, conc = zip(*values)
+    coeff = standard_curve.getCoefficient(abso,conc,fitType=constants.FIT_TYPE)
+    return coeff
 
 def getResourcePath(relative_path): 
     """

python/Colorimeter/colorimeter_plot_gui/colorimeter_plot_gui.py

-
 import os 
 import sys 
 import math
-import platform
+#import platform
 import functools
 import random 
 import time
 from PyQt4 import QtGui
 
 from colorimeter_plot_gui_ui import Ui_MainWindow 
-from colorimeter_serial import Colorimeter
-from colorimeter_common import file_tools
+#from colorimeter_serial import Colorimeter
+from colorimeter_common import constants
+from colorimeter_common import import_export 
+from colorimeter_common import standard_curve
+from colorimeter_common.main_window import MainWindowCommon
 
-DEVEL_FAKE_MEASURE = True 
-DFLT_PORT_WINDOWS = 'com1' 
-DFLT_PORT_LINUX = '/dev/ttyACM0' 
-DFLT_LED_COLOR = 'red'
-COLOR2LED_DICT = {'red':0,'green':1,'blue': 2,'white': 3} 
-TABLE_MIN_ROW_COUNT = 4
-TABLE_COL_COUNT = 2
-FIT_TYPE = 'force_zero'
-PLOT_FIGURE_NUM = 1
-NO_VALUE_SYMB = 'nan'
-
-class ColorimeterPlotMainWindow(QtGui.QMainWindow, Ui_MainWindow):
+#class ColorimeterPlotMainWindow(QtGui.QMainWindow, Ui_MainWindow):
+class ColorimeterPlotMainWindow(MainWindowCommon, Ui_MainWindow):
 
     def __init__(self,parent=None):
         super(ColorimeterPlotMainWindow,self).__init__(parent)
-        self.color2LED_Dict = COLOR2LED_DICT
         self.setupUi(self)
         self.connectActions()
         self.initialize()
 
     def connectActions(self):
-        self.portLineEdit.editingFinished.connect(self.portChanged_Callback)
-        self.connectPushButton.pressed.connect(self.connectPressed_Callback)
-        self.connectPushButton.clicked.connect(self.connectClicked_Callback)
-        self.calibratePushButton.pressed.connect(self.calibratePressed_Callback)
-        self.calibratePushButton.clicked.connect(self.calibrateClicked_Callback)
-        self.measurePushButton.clicked.connect(self.measureClicked_Callback)
-        self.measurePushButton.pressed.connect(self.measurePressed_Callback)
-        self.clearPushButton.pressed.connect(self.clearPressed_Callback)
-        self.clearPushButton.clicked.connect(self.clearClicked_Callback)
+        super(ColorimeterPlotMainWindow,self).connectActions()
+        #self.portLineEdit.editingFinished.connect(self.portChanged_Callback)
+        #self.connectPushButton.pressed.connect(self.connectPressed_Callback)
+        #self.connectPushButton.clicked.connect(self.connectClicked_Callback)
+        #self.calibratePushButton.pressed.connect(self.calibratePressed_Callback)
+        #self.calibratePushButton.clicked.connect(self.calibrateClicked_Callback)
+        #self.measurePushButton.clicked.connect(self.measureClicked_Callback)
+        #self.measurePushButton.pressed.connect(self.measurePressed_Callback)
+        #self.clearPushButton.pressed.connect(self.clearPressed_Callback)
+        #self.clearPushButton.clicked.connect(self.clearClicked_Callback)
 
-        for color in self.color2LED_Dict:
-            button = getattr(self,'{0}RadioButton'.format(color))
-            callback = functools.partial(self.colorRadioButtonClicked_Callback, color)
-            button.clicked.connect(callback)
-        self.plotPushButton.clicked.connect(self.plotPushButtonClicked_Callback)
+        #for color in constants.COLOR2LED_DICT:
+        #    button = getattr(self,'{0}RadioButton'.format(color))
+        #    callback = functools.partial(self.colorRadioButtonClicked_Callback, color)
+        #    button.clicked.connect(callback)
+        #self.plotPushButton.clicked.connect(self.plotPushButtonClicked_Callback)
 
-        self.actionSave.triggered.connect(self.saveFile_Callback)
-        self.actionSave.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_S)
-
-        self.actionLoad.triggered.connect(self.loadFile_Callback)
-        self.actionLoad.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_L)
+        #self.actionSave.triggered.connect(self.saveFile_Callback)
+        #self.actionSave.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_S)
+        #self.actionLoad.triggered.connect(self.loadFile_Callback)
+        #self.actionLoad.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_L)
+        #self.actionEditTestSolutions.triggered.connect(self.editTestSolutions_Callback)
 
         self.actionExport.triggered.connect(self.exportData_Callback)
         self.actionExport.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_E)
-
         self.actionImport.triggered.connect(self.importData_Callback)
         self.actionImport.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_I)
-        self.actionEditTestSolutions.triggered.connect(self.editTestSolutions_Callback)
-
         itemDelegate = DoubleItemDelegate(self.tableWidget)
         self.tableWidget.setItemDelegateForColumn(0,itemDelegate)
 
-        self.tableWidget.contextMenuEvent = self.tableWidgetContextMenu_Callback
-
-        self.tableWidget_CopyAction = QtGui.QAction(self.tableWidget)
-        self.tableWidget_CopyAction.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_C)
-        self.tableWidget_CopyAction.triggered.connect(self.copyTableWidgetData)
-        self.tableWidget.addAction(self.tableWidget_CopyAction)
-
-        self.tableWidget_DeleteAction = QtGui.QAction(self.tableWidget)
-        self.tableWidget_DeleteAction.setShortcut(QtCore.Qt.Key_Delete)
-        self.tableWidget_DeleteAction.triggered.connect(self.deleteTableWidgetData)
-        self.tableWidget.addAction(self.tableWidget_DeleteAction)
-
-        self.tableWidget_BackspaceAction = QtGui.QAction(self.tableWidget)
-        self.tableWidget_BackspaceAction.setShortcut(QtCore.Qt.Key_Backspace)
-        self.tableWidget_BackspaceAction.triggered.connect(self.deleteTableWidgetData)
-        self.tableWidget.addAction(self.tableWidget_BackspaceAction)
 
     def editTestSolutions_Callback(self):
         print('editTestSolutions_Callback')
 
+    def importData_Callback(self):
+        print('importData_Callback')
+
         
     def initialize(self):
-        osType = platform.system()
-        if osType == 'Linux': 
-            self.port = DFLT_PORT_LINUX 
-        else: 
-            self.port = DFLT_PORT_WINDOWS 
-        self.userHome = os.getenv('USERPROFILE')
-        if self.userHome is None:
-            self.userHome = os.getenv('HOME')
-        self.lastLogDir = self.userHome
-            
-        self.portLineEdit.setText(self.port) 
-        self.measIndex = 0
-        self.dev = None
-        self.statusbar.showMessage('Not Connected')
-        self.isCalibrated = False
-        self.fig = None
-        self.setLEDColor(DFLT_LED_COLOR)
+        super(ColorimeterPlotMainWindow,self).initialize()
+        #self.dev = None
+        #self.fig = None
+        #self.isCalibrated = False
+
+        #osType = platform.system()
+        #if osType == 'Linux': 
+        #    self.port = constants.DFLT_PORT_LINUX 
+        #else: 
+        #    self.port = constants.DFLT_PORT_WINDOWS 
+        #self.userHome = os.getenv('USERPROFILE')
+        #if self.userHome is None:
+        #    self.userHome = os.getenv('HOME')
+        #self.lastSaveDir = self.userHome
+        #self.statusbar.showMessage('Not Connected')
+        #self.portLineEdit.setText(self.port) 
+        #self.setLEDColor(constants.DFLT_LED_COLOR)
 
         # Set up data table
-        self.cleanDataTable(setup=True)
-        self.cleanDataTable()
-        self.isCalibrated = False
+        self.tableWidget.clean(setup=True)
 
         self.tableWidget.setHorizontalHeaderLabels(('Concentration','Absorbance')) 
         self.tableWidget.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch)
                 )
         self.updateWidgetEnabled()
 
-    def tableWidgetContextMenu_Callback(self,event):
-        """
-        Callback function for the table widget context menus. Currently
-        handles copy and delete actions.
-        """
-        menu = QtGui.QMenu(self)
-        copyAction = menu.addAction("Copy")
-        deleteAction = menu.addAction("Delete")
-        action = menu.exec_(self.tableWidget.mapToGlobal(event.pos()))
-        if action == copyAction:
-            self.copyTableWidgetData()
-        if action == deleteAction:
-            self.deleteTableWidgetData()
+#    def portChanged_Callback(self):
+#        self.port = str(self.portLineEdit.text())
 
-    def deleteTableWidgetData(self):
-        """
-        Deletes data from the table widget based on the current selection.
-        """
-        removeList = []
-        for i in range(self.tableWidget.rowCount()):
-            item0 = self.tableWidget.item(i,0)
-            item1 = self.tableWidget.item(i,1)
-            if self.tableWidget.isItemSelected(item0):
-                if not self.tableWidget.isItemSelected(item1):
-                    item0.setText("")
-            if self.tableWidget.isItemSelected(item1):
-                removeList.append(item1.row())
+#    def connectPressed_Callback(self):
+#        if self.dev == None:
+#            self.connectPushButton.setText('Disconnect')
+#            self.connectPushButton.setFlat(True)
+#            self.portLineEdit.setEnabled(False)
+#            self.statusbar.showMessage('Connecting...')
 
-        for ind in reversed(removeList):
-            if self.measIndex > 0:
-                self.measIndex-=1
-            self.tableWidget.removeRow(ind)
-
-        if self.tableWidget.rowCount() < TABLE_MIN_ROW_COUNT:
-            self.tableWidget.setRowCount(TABLE_MIN_ROW_COUNT)
-            for row in range(self.measIndex,TABLE_MIN_ROW_COUNT): 
-                for col in range(0,TABLE_COL_COUNT): 
-                    tableItem = QtGui.QTableWidgetItem() 
-                    tableItem.setFlags(QtCore.Qt.NoItemFlags) 
-                    self.tableWidget.setItem(row,col,tableItem)
-
-        if plt.fignum_exists(PLOT_FIGURE_NUM):
-            self.updatePlot()
-
-    def copyTableWidgetData(self): 
-        """
-        Copies data from the table widget to the clipboard based on the current
-        selection.
-        """
-        selectedList = self.getTableWidgetSelectedList()
-
-        # Create string to send to clipboard
-        clipboardList = []
-        for j, rowList in enumerate(selectedList):
-            for i, value in enumerate(rowList):
-                if not value:
-                    clipboardList.append('{0}'.format(j))
-                else:
-                    clipboardList.append(value)
-                if i < len(rowList)-1:
-                    clipboardList.append(" ")
-            clipboardList.append(os.linesep)
-        clipboardStr = ''.join(clipboardList)
-        clipboard = QtGui.QApplication.clipboard()
-        clipboard.setText(clipboardStr)
-
-    def getTableWidgetSelectedList(self):
-        """
-        Returns list of select items in the table widget. Note, assumes that
-        selection mode for the table is ContiguousSelection.
-        """
-        selectedList = []
-        for i in range(self.tableWidget.rowCount()): 
-            rowList = []
-            for j in range(self.tableWidget.columnCount()):
-                item = self.tableWidget.item(i,j)
-                if self.tableWidget.isItemSelected(item):
-                    rowList.append(str(item.text()))
-            selectedList.append(rowList)
-        return selectedList
-
-
-    def portChanged_Callback(self):
-        self.port = str(self.portLineEdit.text())
-
-    def connectPressed_Callback(self):
-        if self.dev == None:
-            self.connectPushButton.setText('Disconnect')
-            self.connectPushButton.setFlat(True)
-            self.portLineEdit.setEnabled(False)
-            self.statusbar.showMessage('Connecting...')
-
-    def connectClicked_Callback(self):
-        """
-        Connect/Disconnect to colorimeter device.
-        """
-        if self.dev == None:
-            try:
-                self.dev = Colorimeter(self.port)
-                self.numSamples = self.dev.getNumSamples()
-                connected = True
-            except Exception, e:
-                QtGui.QMessageBox.critical(self,'Error', str(e))
-                self.connectPushButton.setText('Connect')
-                self.statusbar.showMessage('Not Connected')
-                self.portLineEdit.setEnabled(True)
-                connected = False
-        else:
-            self.connectPushButton.setText('Connect')
-            try:
-                self.cleanUpAndCloseDevice()
-            except Exception, e:
-                QtGui.QMessageBox.critical(self,'Error', str(e))
-            connected = False
-            self.isCalibrated = False
-        self.updateWidgetEnabled()
+#    def connectClicked_Callback(self):
+#        """
+#        Connect/Disconnect to colorimeter device.
+#        """
+#        if self.dev == None:
+#            try:
+#                self.dev = Colorimeter(self.port)
+#                self.numSamples = self.dev.getNumSamples()
+#                connected = True
+#            except Exception, e:
+#                QtGui.QMessageBox.critical(self,'Error', str(e))
+#                self.connectPushButton.setText('Connect')
+#                self.statusbar.showMessage('Not Connected')
+#                self.portLineEdit.setEnabled(True)
+#                connected = False
+#        else:
+#            self.connectPushButton.setText('Connect')
+#            try:
+#                self.cleanUpAndCloseDevice()
+#            except Exception, e:
+#                QtGui.QMessageBox.critical(self,'Error', str(e))
+#            connected = False
+#            self.isCalibrated = False
+#        self.updateWidgetEnabled()
 
     def colorRadioButtonClicked_Callback(self,color):
         """
         """
         if len(self.tableWidget.item(0,1).text()):
             chn_msg = "Changing channels will clear all data. Continue?"
-            response = self.cleanDataTable(msg=chn_msg)
+            response = self.tableWidget.clean(msg=chn_msg)
             if not response:
                 color = self.currentColor
-                button = getattr(self,'{0}RadioButton'.format(color))
-                button.setChecked(True)
+                self.setLEDColor(color)
         self.currentColor = color
-        print(color)
 
     def plotPushButtonClicked_Callback(self):
         """
         Plots the data in the table widget. 
         """
-        dataList = self.getTableData()
+        dataList = self.tableWidget.getData(noValueInclude=False)
+        dataList = dataListToFloat(dataList)
         if not dataList:
             return
-        yList = [x for x,y in dataList]
-        xList = [y for x,y in dataList]
 
-        if FIT_TYPE == 'force_zero': 
+        xList = [x for x,y in dataList]
+        yList = [y for x,y in dataList]
+
+        if constants.FIT_TYPE == 'force_zero': 
             xArray = numpy.array(xList)
             yArray = numpy.array(yList)
             numer = (xArray*yArray).sum()
             slope = polyFit[0]
 
         plt.clf()
-        self.fig = plt.figure(PLOT_FIGURE_NUM)
+        self.fig = plt.figure(constants.PLOT_FIGURE_NUM)
         self.fig.canvas.manager.set_window_title('Colorimeter Plot')
         ax = self.fig.add_subplot(111)
         hFit = ax.plot(xFit,yFit,'r')
         """
         Takes a measurement from the colorimeter. 
         """
-        if DEVEL_FAKE_MEASURE:
+        if constants.DEVEL_FAKE_MEASURE:
             abso = (random.random(),)*4
         else:
             freq, trans, abso = self.dev.getMeasurement()
         self.measurePushButton.setFlat(False)
-        ledNumber = COLOR2LED_DICT[self.currentColor]
-        self.addDataToTable(abso[ledNumber],'',selectEdit=True)
+        ledNumber = constants.COLOR2LED_DICT[self.currentColor]
+        absoStr = '{0:1.2f}'.format(abso[ledNumber])
+        self.tableWidget.addData('',absoStr,selectAndEdit=True)
         self.updateWidgetEnabled()
 
     def calibratePressed_Callback(self):
         self.statusbar.showMessage('Connected, Mode: Calibrating...')
 
     def calibrateClicked_Callback(self):
-        if not DEVEL_FAKE_MEASURE: 
+        if not constants.DEVEL_FAKE_MEASURE: 
             self.dev.calibrate()
         self.isCalibrated = True
         self.calibratePushButton.setFlat(False)
     def clearClicked_Callback(self):
         if len(self.tableWidget.item(0,1).text()):
             erase_msg = "Clear all data?"
-            self.cleanDataTable(msg=erase_msg)
+            self.tableWidget.clean(msg=erase_msg)
         self.clearPushButton.setFlat(False)
         self.updateWidgetEnabled()
 
         filename = dialog.getSaveFileName(
                    None,
                    'Select data file',
-                   self.lastLogDir,
+                   self.lastSaveDir,
                    options=QtGui.QFileDialog.DontUseNativeDialog,
                    )              
         filename = str(filename)
         if not filename:
             return
-        self.lastLogDir =  os.path.split(filename)[0]
+        self.lastSaveDir =  os.path.split(filename)[0]
 
         timeStr = time.strftime('%Y-%m-%d %H:%M:%S %Z') 
         header = [
 
         with open(filename,'w') as f:
             f.write(os.linesep.join(header))
-            f.write(os.linessep)
+            f.write(os.linesep)
             for x,y in dataList:
-                f.write('{0}\t\t{1}{2}'.format(x,y,os.linesep))
+                f.write('{0}  {1}{2}'.format(x,y,os.linesep))
             f.write('{0}'.format(os.linesep))
 
     def loadFile_Callback(self):
         filename = dialog.getOpenFileName(
                    None,
                    'Select data file',
-                   self.lastLogDir,
+                   self.lastSaveDir,
                    options=QtGui.QFileDialog.DontUseNativeDialog,
                    )              
         filename = str(filename)
             return
 
         solutionName = str(solutionName)
-        if not file_tools.isUniqueSolutionName(self.userHome,solutionName):
+        if not import_export.isUniqueSolutionName(self.userHome,solutionName):
             msg = 'User Test solution, {0}, already exists - overwrite?'.format(solutionName)
             reply = QtGui.QMessageBox.question(
                     self,
             if reply == QtGui.QMessageBox.No:
                 return
             else:
-                file_tools.deleteTestSolution(self.userHome,solutionName)
+                import_export.deleteTestSolution(self.userHome,solutionName)
 
         dateStr = time.strftime('%Y-%m-%d %H:%M:%S %Z')
-        file_tools.exportTestSolutionData(
+        import_export.exportTestSolutionData(
                 self.userHome,
                 solutionName,
                 dataList,
                 dateStr,
                 )
 
-    def importData_Callback(self):
-        print('importData_Callback')
-
-    def getTableData(self,addNoValueSymb=False):
-        """
-        Get data items from table widget - replace missing concentratino
-        values with the no value symbol.
-        """
-        dataList = []
-        for i in range(self.measIndex):
-            concTableItem = self.tableWidget.item(i,0)
-            absoTableItem = self.tableWidget.item(i,1)
-            try:
-                abso = float(absoTableItem.text())
-            except ValueError, e:
-                continue
-            try:
-                conc = float(concTableItem.text())
-            except ValueError, e:
-                if addNoValueSymb:
-                    conc = NO_VALUE_SYMB 
-                else:
-                    continue
-            dataList.append((abso,conc))
-        return dataList
-
     def setTableData(self,dataList):
         """
         Set data item in the table widget from the given dataList.
         """
-        self.cleanDataTable(setup=True)
+        print('setTableData')
+        self.tableWidget.clean(setup=True)
         for abso, conc in dataList:
-            self.addDataToTable(abso,conc)
+            concStr = str(conc)
+            absoStr = '{0:1.2f}'.format(abso)
+            self.tableWidget.addData(concStr,absoStr)
 
     def loadDataFromFile(self,filename):
         """
                     color = line[line.index('LED')+1].lower()
                 except IndexError, e:
                     continue
-                if color in COLOR2LED_DICT:
+                if color in constants.COLOR2LED_DICT:
                     ledColor = color
                 continue
             if line[0] == '#':
             self.calibratePushButton.setEnabled(False)
             self.measurePushButton.setEnabled(False)
             self.ledColorWidget.setEnabled(False)
-            if self.measIndex > 0:
+            if self.tableWidget.measIndex > 0:
                 self.tableWidget.setEnabled(True)
                 self.plotPushButton.setEnabled(True)
                 self.clearPushButton.setEnabled(True)
                 self.measurePushButton.setEnabled(True)
                 self.tableWidget.setEnabled(True)
             else:
-                if self.measIndex > 0:
+                if self.tableWidget.measIndex > 0:
                     self.tableWidget.setEnabled(True)
                     self.plotPushButton.setEnabled(True)
                     self.clearPushButton.setEnabled(True)
         self.currentColor = color
         
 
-    def addDataToTable(self,abso,conc,selectEdit=False):
-        """
-        Added data to table widget. If selectEdit is set to True then the
-        concetration element is selected and opened for editing by the
-        user.
-        """
-        rowCount = self.measIndex+1
-        absoStr = '{0:1.2f}'.format(abso)
-        if type(conc) == float and not math.isnan(conc):
-            concStr = '{0:f}'.format(conc)
-        else:
-            concStr = ''
-
-        if rowCount > TABLE_MIN_ROW_COUNT:
-            self.tableWidget.setRowCount(rowCount)
-        tableItem = QtGui.QTableWidgetItem()
-        tableItem.setText(absoStr)
-        tableItem.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled)
-        self.tableWidget.setItem(self.measIndex,1,tableItem)
-
-        tableItem = QtGui.QTableWidgetItem()
-        tableItem.setText(concStr)
-        self.tableWidget.setItem(self.measIndex,0,tableItem)
-        if selectEdit:
-            tableItem.setSelected(True)
-            self.tableWidget.setCurrentCell(self.measIndex,0)
-            self.tableWidget.editItem(self.tableWidget.currentItem()) 
-        self.measIndex+=1
-
-    def cleanDataTable(self,setup=False,msg=''):
-        """
-        Removes all data form the data table widget. If setup is False the user
-        is prompted and asked if they really want to clear all data.
-        """
-        if setup:
-            reply = QtGui.QMessageBox.Yes
-        elif len(self.tableWidget.item(0,1).text()):
-            reply = QtGui.QMessageBox.question(self,'Message', msg, 
-                    QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
-        else: 
-            return True
-        if reply == QtGui.QMessageBox.Yes:
-            self.tableWidget.setRowCount(TABLE_MIN_ROW_COUNT)
-            self.tableWidget.setColumnCount(TABLE_COL_COUNT)
-            for row in range(0,TABLE_MIN_ROW_COUNT):
-                for col in range(0,TABLE_COL_COUNT):
-                    tableItem = QtGui.QTableWidgetItem()
-                    tableItem.setFlags(QtCore.Qt.NoItemFlags)
-                    self.tableWidget.setItem(row,col,tableItem)
-            self.measIndex = 0
-            return True
-        else:
-            return False
 
     def closeEvent(self,event):
         if self.dev is not None:
     def main(self):
         self.show()
 
+def dataListToFloat(dataList):
+    dataListFloat = []
+    for x,y in dataList:
+        try:
+            x, y = float(x), float(y)
+        except ValueError:
+            continue
+        dataListFloat.append((x,y))
+    return dataListFloat
+
 def plotGuiMain():
     """
     Entry point for plotting gui

python/Colorimeter/colorimeter_plot_gui/colorimeter_plot_gui.ui

       </property>
       <layout class="QHBoxLayout" name="horizontalLayout_4">
        <item>
-        <widget class="QTableWidget" name="tableWidget">
+        <widget class="ColorimeterTableWidget" name="tableWidget">
          <property name="editTriggers">
           <set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::DoubleClicked|QAbstractItemView::SelectedClicked</set>
          </property>
    </property>
   </action>
  </widget>
+ <customwidgets>