1. iorodeo
  2. colorimeter

Commits

iorodeo  committed 396ac41

Added nonlinear fitting capability to plotting and measurement programs.

  • Participants
  • Parent commits 8d4e4e8
  • Branches default

Comments (0)

Files changed (5)

File python/Colorimeter/colorimeter/gui/measure/measure.py

View file
  • Ignore whitespace
     def __init__(self,parent=None):
         super(MeasureMainWindow,self).__init__(parent)
         self.setupUi(self)
+        self.connectActions()
         self.initialize()
-        self.connectActions()
 
     def connectActions(self):
         super(MeasureMainWindow,self).connectActions()
         coeffValidator.fixup = self.coeffFixup
         self.coefficientLineEdit.setValidator(coeffValidator)
 
+        self.sampleUnitsActionGroup = QtGui.QActionGroup(self)
+        self.sampleUnitsActionGroup.addAction(self.actionSampleUnitsUM)
+        self.sampleUnitsActionGroup.addAction(self.actionSampleUnitsPPM)
+        self.sampleUnitsActionGroup.setExclusive(True)
+        self.sampleUnitsActionGroup.triggered.connect(self.sampleUnitsChanged_Callback)
+
+
     def initialize(self):
         super(MeasureMainWindow,self).initialize()
         self.coeff = None
         self.fitType = 'linear'
         self.fitParam = None
+        self.testSolutionIndex = 1
+        self.user_TestSolutionDict = {}
+        self.default_TestSolutionDict = {} 
         self.noValueSymbol = constants.NO_VALUE_SYMBOL_LABEL
         self.aboutText = constants.MEASURE_ABOUT_TEXT
 
         # Set up data table
         self.tableWidget.clean(setup=True)
         self.tableWidget.updateFunc = self.updatePlot
-        concentrationStr = QtCore.QString.fromUtf8("Concentration (\xc2\xb5M)")
-        self.tableWidget.setHorizontalHeaderLabels(('Sample', concentrationStr)) 
 
         # Set startup state for including test solution.
         self.actionIncludeDefaultTestSolutions.setChecked(True)
         self.actionIncludeUserTestSolutions.setChecked(True)
         self.updateTestSolutionDicts()
         self.populateTestSolutionComboBox()
-        self.testSolutionIndex = 1
         self.testSolutionComboBox.setCurrentIndex(
                 self.testSolutionIndex
                 )
+
+        self.setSampleUnits('um')
         self.updateWidgetEnabled()
 
     def coeffEditingFinished_Callback(self):
                         self.testSolutionIndex
                         )
 
+    def sampleUnitsChanged_Callback(self):
+        if self.actionSampleUnitsUM.isChecked():
+            self.setSampleUnits('uM')
+        else:
+            self.setSampleUnits('ppm')
+
     def updateTestSolution(self,index):
         if index <= 0:
             self.coeffLEDWidget.setEnabled(True)
+            self.sampleUnitsActionGroup.setEnabled(True)
             self.coefficientLineEdit.setText("")
             self.coeff = None
         else:
             self.coeffLEDWidget.setEnabled(False)
+            self.sampleUnitsActionGroup.setEnabled(False)
             itemText = str(self.testSolutionComboBox.itemText(index))
             testSolutionDict = {}
             testSolutionDict.update(self.user_TestSolutionDict)
             testSolutionDict.update(self.default_TestSolutionDict)
-            pathName = testSolutionDict[itemText]
+            try:
+                pathName = testSolutionDict[itemText]
+            except KeyError:
+                return
             data = import_export.importTestSolutionData(pathName)
             if not self.checkImportData(data):
                 return
             self.fitType = data['fitType']
             self.fitParams = data['fitParams']
+            self.setSampleUnits(data['concentrationUnits'])
             self.coeff = getCoefficientFromData(data,self.fitType,self.fitParams)
             if self.fitType == 'linear':
                 self.coefficientLineEdit.setText('{0:1.1f}'.format(1.0e6*self.coeff))
     def getMeasurement(self):
         ledNumber = constants.COLOR2LED_DICT[self.currentColor]
         if constants.DEVEL_FAKE_MEASURE:  
-            conc = random.random()
+            absorb = random.random()
+            conc = self.getConcentration(absorb) 
         else:
             freq, trans, absorb = self.dev.getMeasurement()
             conc = self.getConcentration(absorb[ledNumber])
-
-
         concStr = '{0:1.2f}'.format(conc)
         self.measurePushButton.setFlat(False)
         self.tableWidget.addData('',concStr,selectAndEdit=True)
     def getConcentration(self,absorb):
         if self.fitType == 'linear':
             conc = absorb/self.coeff
-        elif self.fitType == 'polynomial':
-            pass
+        else:
+            conc = nonlinear_fit.getValueFromFit(self.coeff,absorb)
+        return conc
 
     def getSaveFileHeader(self):
         timeStr = time.strftime('%Y-%m-%d %H:%M:%S %Z') 
                 return False
         return True
 
+    def setSampleUnits(self,units):
+        if units.lower() == 'um':
+            self.actionSampleUnitsUM.setChecked(True)
+            self.actionSampleUnitsPPM.setChecked(False)
+            concentrationStr = QtCore.QString.fromUtf8("Concentration (\xc2\xb5M)")
+            self.sampleUnits = units
+        else:
+            self.actionSampleUnitsUM.setChecked(False)
+            self.actionSampleUnitsPPM.setChecked(True)
+            concentrationStr = QtCore.QString('Concentration (ppm)')
+            self.sampleUnits = units
+        self.tableWidget.setHorizontalHeaderLabels(('Sample', concentrationStr)) 
+
 def dataListToLabelAndFloat(dataList):
     dataListNew = []
     for x,y in dataList:

File python/Colorimeter/colorimeter/gui/measure/measure.ui

View file
  • Ignore whitespace
      <addaction name="actionIncludeUserTestSolutions"/>
      <addaction name="actionIncludeDefaultTestSolutions"/>
     </widget>
+    <widget class="QMenu" name="menuSample_Units">
+     <property name="title">
+      <string>Sample Units</string>
+     </property>
+     <addaction name="actionSampleUnitsUM"/>
+     <addaction name="actionSampleUnitsPPM"/>
+    </widget>
     <addaction name="menuInclude"/>
     <addaction name="actionEditTestSolutions"/>
+    <addaction name="menuSample_Units"/>
    </widget>
    <widget class="QMenu" name="menu_Help">
     <property name="title">
     <string>About</string>
    </property>
   </action>
+  <action name="actionSampleUnitsUM">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>uM</string>
+   </property>
+  </action>
+  <action name="actionSampleUnitsPPM">
+   <property name="checkable">
+    <bool>true</bool>
+   </property>
+   <property name="text">
+    <string>ppm</string>
+   </property>
+  </action>
  </widget>
  <customwidgets>
   <customwidget>

File python/Colorimeter/colorimeter/gui/measure/measure_ui.py

View file
  • Ignore whitespace
 
 # Form implementation generated from reading ui file 'measure.ui'
 #
-# Created: Thu Jul 26 11:23:32 2012
+# Created: Wed Dec 19 18:16:45 2012
 #      by: PyQt4 UI code generator 4.7.2
 #
 # WARNING! All changes made in this file will be lost!
         self.menuOptions.setObjectName("menuOptions")
         self.menuInclude = QtGui.QMenu(self.menuOptions)
         self.menuInclude.setObjectName("menuInclude")
+        self.menuSample_Units = QtGui.QMenu(self.menuOptions)
+        self.menuSample_Units.setObjectName("menuSample_Units")
         self.menu_Help = QtGui.QMenu(self.menubar)
         self.menu_Help.setObjectName("menu_Help")
         MainWindow.setMenuBar(self.menubar)
         self.actionEditTestSolutions.setObjectName("actionEditTestSolutions")
         self.actionAbout = QtGui.QAction(MainWindow)
         self.actionAbout.setObjectName("actionAbout")
+        self.actionSampleUnitsUM = QtGui.QAction(MainWindow)
+        self.actionSampleUnitsUM.setCheckable(True)
+        self.actionSampleUnitsUM.setObjectName("actionSampleUnitsUM")
+        self.actionSampleUnitsPPM = QtGui.QAction(MainWindow)
+        self.actionSampleUnitsPPM.setCheckable(True)
+        self.actionSampleUnitsPPM.setObjectName("actionSampleUnitsPPM")
         self.menuFile.addAction(self.actionSave)
         self.menuFile.addAction(self.actionLoad)
         self.menuInclude.addAction(self.actionIncludeUserTestSolutions)
         self.menuInclude.addAction(self.actionIncludeDefaultTestSolutions)
+        self.menuSample_Units.addAction(self.actionSampleUnitsUM)
+        self.menuSample_Units.addAction(self.actionSampleUnitsPPM)
         self.menuOptions.addAction(self.menuInclude.menuAction())
         self.menuOptions.addAction(self.actionEditTestSolutions)
+        self.menuOptions.addAction(self.menuSample_Units.menuAction())
         self.menu_Help.addAction(self.actionAbout)
         self.menubar.addAction(self.menuFile.menuAction())
         self.menubar.addAction(self.menuOptions.menuAction())
         self.menuFile.setTitle(QtGui.QApplication.translate("MainWindow", "&File", None, QtGui.QApplication.UnicodeUTF8))
         self.menuOptions.setTitle(QtGui.QApplication.translate("MainWindow", "&Options", None, QtGui.QApplication.UnicodeUTF8))
         self.menuInclude.setTitle(QtGui.QApplication.translate("MainWindow", "Include", None, QtGui.QApplication.UnicodeUTF8))
+        self.menuSample_Units.setTitle(QtGui.QApplication.translate("MainWindow", "Sample Units", None, QtGui.QApplication.UnicodeUTF8))
         self.menu_Help.setTitle(QtGui.QApplication.translate("MainWindow", "&Help", None, QtGui.QApplication.UnicodeUTF8))
         self.actionReloadTestSolutions.setText(QtGui.QApplication.translate("MainWindow", "Reload Test Solutions", None, QtGui.QApplication.UnicodeUTF8))
         self.actionIncludeUserTestSolutions.setText(QtGui.QApplication.translate("MainWindow", "User Test Solutions", None, QtGui.QApplication.UnicodeUTF8))
         self.actionRemoveTestSolution.setText(QtGui.QApplication.translate("MainWindow", "Remove Test Solution...", None, QtGui.QApplication.UnicodeUTF8))
         self.actionEditTestSolutions.setText(QtGui.QApplication.translate("MainWindow", "Edit User Test Solutions...", None, QtGui.QApplication.UnicodeUTF8))
         self.actionAbout.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionSampleUnitsUM.setText(QtGui.QApplication.translate("MainWindow", "uM", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionSampleUnitsPPM.setText(QtGui.QApplication.translate("MainWindow", "ppm", None, QtGui.QApplication.UnicodeUTF8))
 
 from colorimeter.table_widget import ColorimeterTableWidget

File python/Colorimeter/colorimeter/gui/plot/plot.py

View file
  • Ignore whitespace
         self.fitTypeActionGroup.addAction(self.actionFitTypePolynomial4)
         self.fitTypeActionGroup.addAction(self.actionFitTypePolynomial5)
         self.fitTypeActionGroup.setExclusive(True)
-
-        self.actionFitTypeLinear.triggered.connect(self.fitTypeChanged_Callback)
-        self.actionFitTypePolynomial2.triggered.connect(self.fitTypeChanged_Callback)
-        self.actionFitTypePolynomial3.triggered.connect(self.fitTypeChanged_Callback)
-        self.actionFitTypePolynomial4.triggered.connect(self.fitTypeChanged_Callback)
-        self.actionFitTypePolynomial5.triggered.connect(self.fitTypeChanged_Callback)
+        self.fitTypeActionGroup.triggered.connect(self.fitTypeChanged_Callback)
 
         self.concUnitsActionGroup = QtGui.QActionGroup(self)
         self.concUnitsActionGroup.addAction(self.actionConcentrationUnitsUM)
         self.concUnitsActionGroup.addAction(self.actionConcentrationUnitsPPM)
         self.concUnitsActionGroup.setExclusive(True)
-
-        self.actionConcentrationUnitsUM.triggered.connect(self.concUnitsChanged_Callback)
-        self.actionConcentrationUnitsPPM.triggered.connect(self.concUnitsChanged_Callback)
+        self.concUnitsActionGroup.triggered.connect(self.concUnitsChanged_Callback)
 
         itemDelegate = DoubleItemDelegate(self.tableWidget)
         self.tableWidget.setItemDelegateForColumn(0,itemDelegate)
             self.closeFigure()
             return
         xList,yList = zip(*dataList)
+        #yList,xList = zip(*dataList)
 
         fitType, fitParams = self.getFitTypeAndParams()
 
         elif fitType == 'polynomial':
             order = fitParams
             if len(dataList) > order:
-                coeff, yFit, xFit = nonlinear_fit.getPolynomialFit(
+                coeff, xFit, yFit = nonlinear_fit.getPolynomialFit(
+                        xList,
                         yList,
-                        xList,
                         order=order,
                         numPts=constants.PLOT_FIT_NUM_PTS,
                         )
 
         if haveFit:
             hFit = ax.plot(xFit,yFit,'r')
+
         ax.plot(xList,yList,'ob')
         ax.grid('on')
         units = self.getConcentrationUnits()

File python/Colorimeter/colorimeter/nonlinear_fit.py

View file
  • Ignore whitespace
 import numpy 
+import scipy.interpolate
+import collections
 
 def getPolynomialFit(xList,yList,order=3,numPts=500): 
-    """
-    Compuetes the coefficient (slope of absorbance vs concentration)
-    """
-    coeff = numpy.polyfit(xList,yList,order)
+
+    # Get unique x,y value pairs and sort
+    valDict = {}
+    for x,y in zip(xList,yList):
+        try:
+            valDict[x].append(y)
+        except KeyError:
+            valDict[x] = [y]
+
+    for k,v in valDict.items():
+        valDict[k] = numpy.mean(v)
+
+    sortedVals = sorted(valDict.items())
+    xSorted, ySorted = zip(*sortedVals)
+
+    # Remove non monotonic parts of data
+    xTrim, yTrim = [xSorted[0]], [ySorted[0]] 
+    yLast = ySorted[0]
+    for x,y in sortedVals[1:]:
+        if y < yLast:
+            break
+        xTrim.append(x)
+        yTrim.append(y)
+        yLast = y
+
+    # Fit data and remove any non-monotonic section  
+    fitCoeff, xFit, yFit = polyFitThruZero(xTrim,yTrim,order,numPts) 
+    ind = numpy.arange(yFit.shape[0])
+    yFitDiff = yFit[1:] - yFit[:-1]
+    maskNeg = yFitDiff < 0
+    indNeg =ind[maskNeg]
+
+    try:
+        firstNeg= indNeg[0]
+        xFitTrim = xFit[:firstNeg]
+        yFitTrim = yFit[:firstNeg]
+    except IndexError:
+        xFitTrim = xFit
+        yFitTrim = yFit
+
+    # Invert monotonic portion of polynomial fit using interpolation function
+    interpFunc = scipy.interpolate.interp1d(yFitTrim, xFitTrim,kind='linear') 
+    yFitMin = yFitTrim.min()
+    yFitMax = yFitTrim.max()
+    fitCoeff = (interpFunc, yFitMin, yFitMax)
+    return fitCoeff, xFitTrim, yFitTrim
+    
+
+def getValueFromFit(fitCoeff,inputValue,numPts=500):
+    interpFunc, minVal, maxVal = fitCoeff
+    if (inputValue < minVal) or (inputValue > maxVal):
+        raise ValueError, 'input Value outside of interpolator range'
+    outputValue = interpFunc(inputValue)
+    return float(outputValue)
+
+
+def polyFitThruZero(xList,yList,order,numPts):
+
+    A = numpy.zeros((len(xList),order))
+    xArray = numpy.array(xList)
+    yArray = numpy.array(yList)
+    for i in range(order):
+        A[:,i] = xArray**(i+1)
+
+    result = numpy.linalg.lstsq(A,yArray)
+    coeff = result[0]
+
     xFit = numpy.linspace(min(xList), max(xList), numPts)
-    yFit = numpy.polyval(coeff, xFit)
-    return coeff,xFit,yFit
+    AFit = numpy.zeros((numPts,order))
+    for i in range(order):
+        AFit[:,i] = xFit**(i+1)
+
+    yFit = numpy.dot(AFit,coeff)
+
+    return coeff, xFit, yFit
+
+
+    
+
+
+