Commits

iorodeo committed c2b4b16

Project creation.

Comments (0)

Files changed (9)

+syntax: glob
+*.pyc
+*~
+*.o
+*.egg-info
+*.swp
+*.scad
+*.stl
+*.pkl
+*.dxf
+*.komodo*
+*.egg-info
+.DS_Store
+build
+dist
+.komodo*
+deb_dist
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+

array_sensor_firmware/array_sensor_firmware.pde

+#include <Streaming.h>
+#include <avr/pgmspace.h>
+
+#define SI A0
+#define CLK A1
+#define AIN A6
+
+// defines for setting and clearing register bits
+#ifndef cbi
+#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
+#endif
+#ifndef sbi
+#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
+#endif
+
+//#define INTEGTIME 2 
+
+void setup() {
+    // set prescale to 16
+    sbi(ADCSRA,ADPS2) ;
+    cbi(ADCSRA,ADPS1) ;
+    cbi(ADCSRA,ADPS0) ;
+
+    // Setup serial communications
+    Serial.begin(115200);
+
+    // Set pinModes
+    pinMode(SI,OUTPUT);
+    pinMode(CLK,OUTPUT);
+
+    // Set clock and si lines low
+    digitalWrite(SI,LOW);
+    digitalWrite(CLK,LOW);
+    
+}
+
+void loop() {
+    static uint16_t buffer[768];
+    readArray(buffer);
+    while (Serial.available() > 0) {
+        byte cmd = Serial.read();
+        if (cmd == 'x') {
+            sendArray(buffer);
+        }
+    }
+}
+
+
+void sendArray(uint16_t *buffer) {
+    uint16_t cnt = 0;
+    while (cnt < 768) {
+        for (int i=0;i<30;i++) { 
+            Serial << _DEC(buffer[cnt]) << " ";
+            cnt++;
+            if (cnt >= 768) break;
+        }
+        Serial << endl;
+    }
+    Serial << endl;
+}
+
+
+void readArray(uint16_t *buffer) {
+
+    for (uint8_t j=0; j<2; j++) {
+        // Start cycle
+        digitalWrite(CLK,HIGH);
+        digitalWrite(CLK,LOW);
+        digitalWrite(SI,HIGH);
+
+        // Take reading
+        for (int i=0; i<770; i++) {
+            digitalWrite(CLK,HIGH);
+            if (i==0) {
+                digitalWrite(SI,LOW);
+            }
+            else {
+            }
+            digitalWrite(CLK,LOW);
+            if (i < 768) {
+                buffer[i] = analogRead(AIN);
+            }
+        }
+    }
+}
+

array_sensor_firmware/read.py

+import os
+import os.path
+import sys
+import time
+import serial
+import matplotlib.pylab as pylab
+
+BACKGROUND_FILE = 'background.txt'
+MM2NL = 5.0e3/54.8
+PIXEL2MM = 63.5e-3
+
+class ArrayReader(serial.Serial):
+
+    def __init__(self,**kwargs):
+        super(ArrayReader,self).__init__(**kwargs)
+        time.sleep(2.0)
+        
+
+    def getData(self):
+        self.write('x\n')
+        line_list = []
+        data_ok = True
+        for line in self.readlines():
+            line = line.split()
+            if line:
+                try:
+                    line = [int(x) for x in line]
+                    line_list.extend(line)
+                except:
+                    data_ok = False
+        if data_ok:
+            return pylab.array(line_list)
+        else:
+            return None
+
+
+def get_level(data,slope_threshold=1.5,window_size=15):
+    """
+    Automatically determine the fluid level from the slope change
+    """
+    found = False
+    n = data.shape[0]
+    for i in range(window_size,n-window_size):
+        window_ind  = range(i-window_size,i+window_size)
+        window_data = data[i-window_size:i+window_size]
+        fit = pylab.polyfit(window_ind, window_data,1)
+        if fit[0] > slope_threshold:
+            found = True
+            break
+    if found:
+        return i, data[i]
+    else:
+        return None
+        
+
+
+        
+        
+pylab.ion()
+reader = ArrayReader(port='/dev/ttyUSB0',baudrate=115200,timeout=0.05)
+
+
+
+# Get background
+if 1:
+    if os.path.isfile(BACKGROUND_FILE):
+        print 'reading background.txt'
+        background = pylab.loadtxt(BACKGROUND_FILE)
+    else:
+        print 'getting new background image for equalization'
+        numBackground = 5 
+        background = pylab.zeros((768,))
+        for i in range(0,numBackground):
+            print i 
+            data = reader.getData()
+            background = background + data
+        background = background/numBackground
+        print 
+
+    pylab.savetxt('background.txt',background)
+    delta = 500.0 - background
+else:
+    print 'background subtraction disabled'
+    delta = 0
+
+
+i = 0
+while 1:
+    data = reader.getData()
+    if data is None:
+        continue
+    data = data + delta
+    if i == 0:
+        pylab.figure(1)
+        h_line, = pylab.plot(data,linewidth=2)
+        h_level, = pylab.plot([0],[0], 'or')
+        h_level.set_visible(False)
+        pylab.grid('on')
+        pylab.ylim(0,1023)
+        pylab.xlim(0,768)
+        pylab.xlabel('pixel')
+        pylab.ylabel('intensity')
+    else:
+        h_line.set_ydata(data)
+        rval = get_level(data)
+        if rval:
+            ind, value = rval
+            fluid_level = ind*PIXEL2MM*MM2NL
+            print ind, fluid_level
+            h_level.set_visible(True)
+            h_level.set_xdata([ind])
+            h_level.set_ydata([value])
+        else:
+            h_level.set_visible(False)
+        
+    pylab.draw()
+    i += 1
+
+                    

array_sensor_gui/array_reader.py

+import time
+import serial
+import matplotlib.pylab as pylab
+
+class ArrayReader(serial.Serial):
+
+    def __init__(self,**kwargs):
+        super(ArrayReader,self).__init__(**kwargs)
+        time.sleep(2.0)
+
+    def getData(self):
+        self.write('x\n')
+        line_list = []
+        data_ok = True
+        for line in self.readlines():
+            line = line.split()
+            if line:
+                try:
+                    line = [int(x) for x in line]
+                    line_list.extend(line)
+                except:
+                    data_ok = False
+        if data_ok:
+            return pylab.array(line_list)
+        else:
+            return None

array_sensor_gui/array_sensor.py

+import sys
+import os
+import os.path
+import platform
+import time
+import serial
+import pylab
+from PyQt4 import QtCore
+from PyQt4 import QtGui
+from array_sensor_ui import Ui_ArraySensorMainWindow
+from array_reader import ArrayReader
+
+# Conversions
+MM2NL = 5.0e3/54.8
+PIXEL2MM = 63.5e-3
+ADC2VOLTS = 5.0/1024.0
+
+# Constants
+NUM_PIXEL = 768
+ADC_INT_MAX = 1024
+ADC_VOLTS_MAX = 5.0
+DEFAULT_LOG_FILE = 'array_sensor_data.txt'
+DEFAULT_BACKGROUND_FILE = 'background_data.txt'
+TIMER_INTERVAL_MS =  1000 
+NUM_BACKGROUND_AVG = 5
+BASELINE_PIXEL_LEVEL = 1.0
+DEFAULT_THRESHOLD = 1.5
+DETECTION_WINDOW = 15
+SENSOR_RANGE_NL = NUM_PIXEL*PIXEL2MM*MM2NL
+
+class Sensor_MainWindow(QtGui.QMainWindow, Ui_ArraySensorMainWindow):
+
+    def __init__(self,parent=None):
+        super(Sensor_MainWindow,self).__init__(parent)
+        self.setupUi(self)
+        self.connectActions()
+        self.setupTimer()
+        self.initialize()
+
+    def connectActions(self):
+        """
+        Connect pushbuttons to their actions
+        """
+        self.startPushButton.pressed.connect(self.startPressed_Callback)
+        self.startPushButton.clicked.connect(self.startClicked_Callback)
+        self.stopPushButton.clicked.connect(self.stopClicked_Callback)
+        self.serialPortLineEdit.editingFinished.connect(self.serialPortLineEdit_Callback)
+        self.thresholdLineEdit.editingFinished.connect(self.thresholdLineEdit_Callback)
+        self.takeBackgroundPushButton.clicked.connect(self.takeBackground_Callback)
+        self.saveBackgroundPushButton.clicked.connect(self.saveBackground_Callback)
+        self.loadBackgroundPushButton.clicked.connect(self.loadBackground_Callback)
+        self.deleteBackgroundPushButton.clicked.connect(self.deleteBackground_Callback)
+        self.setLogPushButton.clicked.connect(self.setLog_Callback)
+        self.startLogPushButton.clicked.connect(self.startLog_Callback)
+        self.debugRadioButton.clicked.connect(self.debug_Callback)
+        self.stopLogPushButton.clicked.connect(self.stopLog_Callback)
+
+    def setupTimer(self):
+        """
+        Setup timer object
+        """
+        self.timer = QtCore.QTimer()
+        self.timer.setInterval(TIMER_INTERVAL_MS)
+        self.timer.timeout.connect(self.timer_Callback)
+
+    def initialize(self):
+
+        # Set initial state
+        self.running = False
+        self.logging = False
+        self.haveBackground = False
+        self.debugLogging = False
+
+        # Background varialbles 
+        self.takeBackground = False
+        self.backgroungCount = 0
+        self.backgroundData = pylab.zeros((NUM_PIXEL,))
+
+        # Sensor data
+        self.sensor = None
+        self.pixelPosArray = pylab.arange(0,NUM_PIXEL)*PIXEL2MM
+
+        # Logging data
+        self.startTime = time.time()
+        self.logFileFid = None
+
+        # Threshold data
+        self.threshold = DEFAULT_THRESHOLD
+        self.thresholdLineEdit.setText('%1.2f'%(self.threshold,))
+
+        # Set default log and background files
+        self.userHome = os.getenv('USERPROFILE')
+        if self.userHome is None:
+            self.userHome = os.getenv('HOME')
+        self.defaultLogPath = os.path.join(self.userHome, DEFAULT_LOG_FILE)
+        self.defaultBackgroundPath = os.path.join(self.userHome, DEFAULT_BACKGROUND_FILE)
+        self.logPath = self.defaultLogPath
+        self.backgroundPath = self.defaultBackgroundPath
+        self.logFileLabel.setText('Log File: %s'%(self.logPath))
+
+        # Set last directories
+        self.lastLogDir = self.userHome
+        self.lastBackgroundDir = self.userHome
+
+        # Set default com port
+        osType = platform.system()
+        if osType == 'Linux':
+            self.port = '/dev/ttyUSB0'
+        else:
+            self.port = 'com1'
+        self.serialPortLineEdit.setText(self.port)
+
+        #  Initialize plot
+        self.pixelPlot, = self.mpl.canvas.ax.plot([],[],linewidth=2)
+        self.levelPlot, = self.mpl.canvas.ax.plot([0],[0],'or')
+        self.pixelPlot.set_visible(False)
+        self.levelPlot.set_visible(False)
+        self.mpl.canvas.ax.set_autoscale_on(False)
+        self.mpl.canvas.ax.grid('on')
+        self.mpl.canvas.ax.set_xlim(0,NUM_PIXEL*PIXEL2MM)
+        self.mpl.canvas.ax.set_ylim(0,ADC_INT_MAX*ADC2VOLTS)
+        self.mpl.canvas.ax.set_xlabel('pixel (mm)')
+        self.mpl.canvas.ax.set_ylabel('intensity (V)')
+        self.mpl.canvas.ax.set_title('Stopped')
+
+        # Create lists for enabling disabling widgets and apply to current state 
+        self.createEnableDisableLists()
+        self.enableDisableWidgets()
+
+
+    def createEnableDisableLists(self):
+        self.runningEnableList = [
+                'takeBackgroundPushButton',
+                'loadBackgroundPushButton',
+                'startLogPushButton',
+                'stopLogPushButton',
+                'stopPushButton',
+                ]
+        self.runningDisableList = [
+                'startPushButton',
+                'serialPortLineEdit',
+                ]
+
+        self.backgroundEnableList = [
+                'saveBackgroundPushButton',
+                'deleteBackgroundPushButton'
+                ]
+        self.backgroundDisableList = []
+
+        self.loggingEnableList = [
+                'stopLogPushButton'
+                ]
+        self.loggingDisableList = [
+                'stopPushButton',
+                'setLogPushButton',
+                'startLogPushButton',
+                'takeBackgroundPushButton',
+                'saveBackgroundPushButton',
+                'loadBackgroundPushButton',
+                'deleteBackgroundPushButton',
+                'debugRadioButton',
+                'thresholdLineEdit',
+                ]
+
+    def enableDisableWidgets(self): 
+        """
+        Enable and disable widgtes based on system state. I'm not real happy with this 
+        at present - definitely not the cleanest or clearest way to do things.
+        """
+
+        # Set widgets base on whether the device is running or not
+        for name in self.runningEnableList:
+            widget = getattr(self,name)
+            widget.setEnabled(self.running)
+        for name in self.runningDisableList:
+            widget = getattr(self,name)
+            widget.setEnabled(not self.running)
+
+        # Set widgets based on logging state
+        if self.running:
+            for name in self.loggingEnableList:
+                widget = getattr(self,name)
+                widget.setEnabled(self.logging)
+            for name in self.loggingDisableList:
+                widget = getattr(self,name)
+                widget.setEnabled((not self.logging))
+
+        # Set widgets based on whether or not we have a background image loaded
+        if not self.logging:
+            for name in self.backgroundEnableList:
+                widget = getattr(self,name)
+                widget.setEnabled(self.haveBackground)
+            for name in self.backgroundDisableList:
+                widget = getattr(self,name)
+                widget.setEnabled((not self.haveBackground) and self.running)
+
+    def timer_Callback(self):
+        """
+        Grab data from sensor, display, find fluid level and write to log file.
+        """
+
+        # Get time and sensor data
+        currentTime  = time.time()
+        data = self.sensor.getData()
+        if data is None:
+            return 
+
+        # Update time label
+        dt = currentTime - self.startTime
+        self.timeLabel.setText('Time: %1.0f (s)'%(dt,))
+
+        # Check shape of data
+        if data.shape[0] != NUM_PIXEL:
+            return
+
+        # Convert data to voltages
+        data = ADC2VOLTS*data
+
+        # Get background data 
+        if self.takeBackground:
+            self.backgroundData += data
+            self.backgroundCount += 1
+            self.mpl.canvas.ax.set_title('taking background image: %d'%(self.backgroundCount,))
+            if self.backgroundCount == NUM_BACKGROUND_AVG:
+                self.takeBackground = False
+                self.haveBackground = True
+                self.backgroundCount = 0
+                self.backgroundData = self.backgroundData/NUM_BACKGROUND_AVG
+                self.enableDisableWidgets()
+        else:
+            if self.logging:
+                self.mpl.canvas.ax.set_title('Logging')
+            else:
+                self.mpl.canvas.ax.set_title('Running')
+
+        # Perform background subtraction
+        if self.haveBackground:
+            data = data + (BASELINE_PIXEL_LEVEL- self.backgroundData)
+
+        # Plot pixel data
+        self.pixelPlot.set_visible(True)
+        self.pixelPlot.set_data(self.pixelPosArray,data)
+
+        # Find fluid level
+        slope_threshold = self.threshold*ADC_VOLTS_MAX/NUM_PIXEL
+        rval = getFluidLevel(data,slope_threshold=slope_threshold,window_size=DETECTION_WINDOW)
+        if rval:
+            ind, value = rval
+            pixel_pos = ind*PIXEL2MM
+            fluid_level = SENSOR_RANGE_NL - pixel_pos*MM2NL
+            self.levelLabel.setText('Fluid Level: %1.0f(nl)'%(fluid_level,))
+            self.levelPlot.set_visible(True)
+            self.levelPlot.set_data([pixel_pos],[value])
+        else:
+            self.levelPlot.set_visible(False)
+            self.levelLabel.setText('Fluid Level: ________')
+
+        self.mpl.canvas.fig.canvas.draw()
+
+        # Update log file
+        if self.logging:
+            self.logFileFid.write('%f %f\n'%(dt,fluid_level))
+
+        #time_end = time.time()
+        #print 'dt = ', time_end - currentTime 
+
+    def startPressed_Callback(self):
+        self.serialPortLineEdit.setEnabled(False)
+
+    def startClicked_Callback(self):
+        """
+        Start serial communications with sensor
+        """
+        # Try to open serial port
+        try:
+            self.sensor = ArrayReader(port=self.port,baudrate=115200,timeout=0.05)
+        except serial.serialutil.SerialException, e:
+            QtGui.QMessageBox.critical(self,'Error', '%s'%(e,))
+            self.sensor = None
+            self.serialPortLineEdit.setEnabled(True)
+            return
+
+        # Start timer, set state and update widgets enable values
+        self.timer.start()
+        self.startTime = time.time()
+        self.running = True
+        self.enableDisableWidgets()
+
+    def stopClicked_Callback(self):
+        """
+        Stop serial communications with sensor
+        """
+        self.stopSensor()
+        self.timer.stop()
+        self.running = False 
+        self.enableDisableWidgets()
+        self.mpl.canvas.ax.set_title('Stopped')
+        self.mpl.canvas.draw()
+
+    def stopSensor(self):
+        """
+        Close sensor and delete.
+        """
+        try:
+            self.sensor.close()
+            del self.sensor
+            self.sensor = None 
+        except:
+            pass
+
+    def serialPortLineEdit_Callback(self):
+        """
+        Set serial port sting.
+        """
+        self.port = str(self.serialPortLineEdit.text())
+
+    def thresholdLineEdit_Callback(self):
+        """
+        Get Threshold setting
+        """
+        thresholdStr = str(self.thresholdLineEdit.text())
+        try:
+            threshold = float(thresholdStr)
+            self.threshold = threshold
+        except ValueError, e:
+            QtGui.QMessageBox.critical(self,'Error', 'Input must be a float, %s'%(e,))
+            return
+        self.thresholdLineEdit.setText('%1.2f'%(self.threshold,))
+
+    def debug_Callback(self):
+        """
+        For setting up debuging data
+        """
+        pass
+
+    def takeBackground_Callback(self):
+        self.haveBackground = False
+        self.takeBackground = True
+        self.backgroundCount = 0
+        self.backgroundData = 0.0*self.backgroundData
+
+    def saveBackground_Callback(self):
+        filename = QtGui.QFileDialog.getSaveFileName(None,'Select background file',self.lastBackgroundDir)
+        filename = str(filename)
+        if filename:
+            # save background data
+            pylab.savetxt(filename,self.backgroundData)
+            self.backgroundPath = filename
+            self.lastBackgroundDir =  os.path.split(filename)[0]
+
+    def loadBackground_Callback(self):
+        """
+        Load background data from file
+        """
+        filename = QtGui.QFileDialog.getOpenFileName(None,'Select background file',self.lastBackgroundDir)
+        filename = str(filename)
+        if filename:
+            # Try to load background data 
+            try:
+                data = pylab.loadtxt(filename)
+            except Exception, e:
+                QtGui.QMessageBox.critical(self,'Error', '%s'%(e,))
+                return
+
+            # Check size of array
+            if not len(data.shape) == 1 and data.shape[0] == NUM_PIXEL:
+                QtGui.QMessageBox.critical(self,'Error', 'background data shape must be (768,)')
+                return
+
+            self.backgroundPath = filename
+            self.lastBackgroundDir =  os.path.split(filename)[0]
+            self.backgroundData = data
+            self.haveBackground = True
+        self.enableDisableWidgets()
+
+    def deleteBackground_Callback(self):
+        """
+        Delete current background data
+        """
+        self.haveBackground = False
+        self.enableDisableWidgets()
+
+    def setLog_Callback(self):
+        """
+        Set the log file.
+        """
+        filename = QtGui.QFileDialog.getSaveFileName(None,'Select log file',self.lastLogDir)
+        filename = str(filename)
+        if filename:
+            # Set new log file
+            self.logPath = filename
+            self.lastLogDir =  os.path.split(filename)[0]
+            self.logFileLabel.setText('Log File: %s'%(self.logPath))
+
+    def startLog_Callback(self):
+        """
+        Start logging data to log file.
+        """
+        # Check to see if file already exists
+        if os.path.isfile(self.logPath):
+            msgString = 'Log file %s already exists - do you want to overwrite thie file?'%(self.logPath,)
+            reply = QtGui.QMessageBox.question(
+                    self,
+                    'File Exists', 
+                    msgString, 
+                    QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, 
+                    QtGui.QMessageBox.No,
+                    )
+            if reply == QtGui.QMessageBox.No:
+                return
+            
+        try:
+            self.logFileFid = open(self.logPath,'w')
+        except Exception, e:
+            QtGui.QMessageBox.critical(self,'Error', '%s'%(e,))
+            return
+        
+        self.logging = True
+        self.startTime = time.time()
+        self.timeLabel.setText('Time: %1.0f (s)'%(0.0,))
+        self.enableDisableWidgets()
+
+    def stopLog_Callback(self):
+        """
+        Stop logging data to log file. 
+        """
+        self.logFileFid.close()
+        self.logFileFid = None
+        self.logging = False
+        self.enableDisableWidgets()
+
+    def main(self):
+        self.show()
+
+
+def getFluidLevel(data,slope_threshold=1.5,window_size=15):
+    """
+    Automatically determine the fluid level from the slope change
+    """
+    found = False
+    n = data.shape[0]
+    for i in range(window_size,n-window_size):
+        window_ind  = range(i-window_size,i+window_size)
+        window_data = data[i-window_size:i+window_size]
+        fit = pylab.polyfit(window_ind, window_data,1)
+        if fit[0] > slope_threshold:
+            found = True
+            break
+    if found:
+        return i, data[i]
+    else:
+        return None
+
+# -----------------------------------------------------------------------
+if __name__ == '__main__':
+    app = QtGui.QApplication(sys.argv)
+    sensor = Sensor_MainWindow()
+    sensor.main()
+    app.exec_()

array_sensor_gui/array_sensor.ui

+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ArraySensorMainWindow</class>
+ <widget class="QMainWindow" name="ArraySensorMainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>751</width>
+    <height>684</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Capillary Array Sensor</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout_3">
+    <item>
+     <widget class="QFrame" name="frame">
+      <property name="frameShape">
+       <enum>QFrame::StyledPanel</enum>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Raised</enum>
+      </property>
+      <layout class="QVBoxLayout" name="verticalLayout_4">
+       <item>
+        <widget class="QWidget" name="widget_2" native="true">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout">
+          <item>
+           <widget class="QLabel" name="timeLabel">
+            <property name="text">
+             <string>Time:         </string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <spacer name="horizontalSpacer_5">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="sizeType">
+             <enum>QSizePolicy::Fixed</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>40</width>
+              <height>20</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+          <item>
+           <widget class="QLabel" name="levelLabel">
+            <property name="text">
+             <string>Fluid Level:          </string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <spacer name="horizontalSpacer_2">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>40</width>
+              <height>20</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+          <item>
+           <widget class="QRadioButton" name="debugRadioButton">
+            <property name="text">
+             <string>debug</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </widget>
+       </item>
+       <item>
+        <widget class="MplWidget" name="mpl" native="true">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </item>
+    <item>
+     <widget class="QFrame" name="frame_3">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="frameShape">
+       <enum>QFrame::StyledPanel</enum>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Raised</enum>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout_3">
+       <item>
+        <widget class="QLabel" name="label">
+         <property name="text">
+          <string>Serial Port</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLineEdit" name="serialPortLineEdit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="toolTip">
+          <string>set serial port address</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="horizontalSpacer_6">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_2">
+         <property name="text">
+          <string>Threshold</string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLineEdit" name="thresholdLineEdit">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>80</width>
+           <height>16777215</height>
+          </size>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="horizontalSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QPushButton" name="startPushButton">
+         <property name="toolTip">
+          <string>start acquiring data from sensor</string>
+         </property>
+         <property name="text">
+          <string>        Start      </string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QPushButton" name="stopPushButton">
+         <property name="toolTip">
+          <string>stop acquisition form sensor</string>
+         </property>
+         <property name="text">
+          <string>       Stop        </string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </item>
+    <item>
+     <widget class="QFrame" name="frame_2">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="frameShape">
+       <enum>QFrame::StyledPanel</enum>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Raised</enum>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout_5">
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_2">
+         <item>
+          <widget class="QPushButton" name="takeBackgroundPushButton">
+           <property name="toolTip">
+            <string>take background reading of capillary for equalization</string>
+           </property>
+           <property name="text">
+            <string>Take Background</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="loadBackgroundPushButton">
+           <property name="toolTip">
+            <string>load background reading from file</string>
+           </property>
+           <property name="text">
+            <string>Load Background</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_5">
+         <item>
+          <widget class="QPushButton" name="deleteBackgroundPushButton">
+           <property name="toolTip">
+            <string>delete current background reading</string>
+           </property>
+           <property name="text">
+            <string>Delete Background</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="saveBackgroundPushButton">
+           <property name="toolTip">
+            <string>save current background reading to file</string>
+           </property>
+           <property name="text">
+            <string>Save Background</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <spacer name="horizontalSpacer_4">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_6">
+         <item>
+          <widget class="QPushButton" name="setLogPushButton">
+           <property name="toolTip">
+            <string>set current data log file</string>
+           </property>
+           <property name="text">
+            <string> Set Log File  </string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QWidget" name="widget" native="true"/>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout">
+         <item>
+          <widget class="QPushButton" name="startLogPushButton">
+           <property name="toolTip">
+            <string>start logging data to file</string>
+           </property>
+           <property name="text">
+            <string>Start Logging</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="stopLogPushButton">
+           <property name="toolTip">
+            <string>stop logging data to file</string>
+           </property>
+           <property name="text">
+            <string>Stop Logging</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+    </item>
+    <item>
+     <widget class="QFrame" name="frame_4">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="frameShape">
+       <enum>QFrame::StyledPanel</enum>
+      </property>
+      <property name="frameShadow">
+       <enum>QFrame::Raised</enum>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout_2">
+       <item>
+        <widget class="QLabel" name="logFileLabel">
+         <property name="text">
+          <string>Log File: </string>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <spacer name="horizontalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>751</width>
+     <height>23</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>MplWidget</class>
+   <extends>QWidget</extends>
+   <header>mplwidget</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>

array_sensor_gui/array_sensor_ui.py

+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'array_sensor.ui'
+#
+# Created: Mon Aug 15 12:39:32 2011
+#      by: PyQt4 UI code generator 4.7.2
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt4 import QtCore, QtGui
+
+class Ui_ArraySensorMainWindow(object):
+    def setupUi(self, ArraySensorMainWindow):
+        ArraySensorMainWindow.setObjectName("ArraySensorMainWindow")
+        ArraySensorMainWindow.resize(751, 684)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(ArraySensorMainWindow.sizePolicy().hasHeightForWidth())
+        ArraySensorMainWindow.setSizePolicy(sizePolicy)
+        self.centralwidget = QtGui.QWidget(ArraySensorMainWindow)
+        self.centralwidget.setObjectName("centralwidget")
+        self.verticalLayout_3 = QtGui.QVBoxLayout(self.centralwidget)
+        self.verticalLayout_3.setObjectName("verticalLayout_3")
+        self.frame = QtGui.QFrame(self.centralwidget)
+        self.frame.setFrameShape(QtGui.QFrame.StyledPanel)
+        self.frame.setFrameShadow(QtGui.QFrame.Raised)
+        self.frame.setObjectName("frame")
+        self.verticalLayout_4 = QtGui.QVBoxLayout(self.frame)
+        self.verticalLayout_4.setObjectName("verticalLayout_4")
+        self.widget_2 = QtGui.QWidget(self.frame)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth())
+        self.widget_2.setSizePolicy(sizePolicy)
+        self.widget_2.setObjectName("widget_2")
+        self.horizontalLayout = QtGui.QHBoxLayout(self.widget_2)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.timeLabel = QtGui.QLabel(self.widget_2)
+        self.timeLabel.setObjectName("timeLabel")
+        self.horizontalLayout.addWidget(self.timeLabel)
+        spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem)
+        self.levelLabel = QtGui.QLabel(self.widget_2)
+        self.levelLabel.setObjectName("levelLabel")
+        self.horizontalLayout.addWidget(self.levelLabel)
+        spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem1)
+        self.debugRadioButton = QtGui.QRadioButton(self.widget_2)
+        self.debugRadioButton.setObjectName("debugRadioButton")
+        self.horizontalLayout.addWidget(self.debugRadioButton)
+        self.verticalLayout_4.addWidget(self.widget_2)
+        self.mpl = MplWidget(self.frame)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.mpl.sizePolicy().hasHeightForWidth())
+        self.mpl.setSizePolicy(sizePolicy)
+        self.mpl.setObjectName("mpl")
+        self.verticalLayout_4.addWidget(self.mpl)
+        self.verticalLayout_3.addWidget(self.frame)
+        self.frame_3 = QtGui.QFrame(self.centralwidget)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.frame_3.sizePolicy().hasHeightForWidth())
+        self.frame_3.setSizePolicy(sizePolicy)
+        self.frame_3.setFrameShape(QtGui.QFrame.StyledPanel)
+        self.frame_3.setFrameShadow(QtGui.QFrame.Raised)
+        self.frame_3.setObjectName("frame_3")
+        self.horizontalLayout_3 = QtGui.QHBoxLayout(self.frame_3)
+        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+        self.label = QtGui.QLabel(self.frame_3)
+        self.label.setObjectName("label")
+        self.horizontalLayout_3.addWidget(self.label)
+        self.serialPortLineEdit = QtGui.QLineEdit(self.frame_3)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.serialPortLineEdit.sizePolicy().hasHeightForWidth())
+        self.serialPortLineEdit.setSizePolicy(sizePolicy)
+        self.serialPortLineEdit.setObjectName("serialPortLineEdit")
+        self.horizontalLayout_3.addWidget(self.serialPortLineEdit)
+        spacerItem2 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout_3.addItem(spacerItem2)
+        self.label_2 = QtGui.QLabel(self.frame_3)
+        self.label_2.setObjectName("label_2")
+        self.horizontalLayout_3.addWidget(self.label_2)
+        self.thresholdLineEdit = QtGui.QLineEdit(self.frame_3)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.thresholdLineEdit.sizePolicy().hasHeightForWidth())
+        self.thresholdLineEdit.setSizePolicy(sizePolicy)
+        self.thresholdLineEdit.setMaximumSize(QtCore.QSize(80, 16777215))
+        self.thresholdLineEdit.setObjectName("thresholdLineEdit")
+        self.horizontalLayout_3.addWidget(self.thresholdLineEdit)
+        spacerItem3 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout_3.addItem(spacerItem3)
+        self.startPushButton = QtGui.QPushButton(self.frame_3)
+        self.startPushButton.setObjectName("startPushButton")
+        self.horizontalLayout_3.addWidget(self.startPushButton)
+        self.stopPushButton = QtGui.QPushButton(self.frame_3)
+        self.stopPushButton.setObjectName("stopPushButton")
+        self.horizontalLayout_3.addWidget(self.stopPushButton)
+        self.verticalLayout_3.addWidget(self.frame_3)
+        self.frame_2 = QtGui.QFrame(self.centralwidget)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth())
+        self.frame_2.setSizePolicy(sizePolicy)
+        self.frame_2.setFrameShape(QtGui.QFrame.StyledPanel)
+        self.frame_2.setFrameShadow(QtGui.QFrame.Raised)
+        self.frame_2.setObjectName("frame_2")
+        self.horizontalLayout_5 = QtGui.QHBoxLayout(self.frame_2)
+        self.horizontalLayout_5.setObjectName("horizontalLayout_5")
+        self.verticalLayout_2 = QtGui.QVBoxLayout()
+        self.verticalLayout_2.setObjectName("verticalLayout_2")
+        self.takeBackgroundPushButton = QtGui.QPushButton(self.frame_2)
+        self.takeBackgroundPushButton.setObjectName("takeBackgroundPushButton")
+        self.verticalLayout_2.addWidget(self.takeBackgroundPushButton)
+        self.loadBackgroundPushButton = QtGui.QPushButton(self.frame_2)
+        self.loadBackgroundPushButton.setObjectName("loadBackgroundPushButton")
+        self.verticalLayout_2.addWidget(self.loadBackgroundPushButton)
+        self.horizontalLayout_5.addLayout(self.verticalLayout_2)
+        self.verticalLayout_5 = QtGui.QVBoxLayout()
+        self.verticalLayout_5.setObjectName("verticalLayout_5")
+        self.deleteBackgroundPushButton = QtGui.QPushButton(self.frame_2)
+        self.deleteBackgroundPushButton.setObjectName("deleteBackgroundPushButton")
+        self.verticalLayout_5.addWidget(self.deleteBackgroundPushButton)
+        self.saveBackgroundPushButton = QtGui.QPushButton(self.frame_2)
+        self.saveBackgroundPushButton.setObjectName("saveBackgroundPushButton")
+        self.verticalLayout_5.addWidget(self.saveBackgroundPushButton)
+        self.horizontalLayout_5.addLayout(self.verticalLayout_5)
+        spacerItem4 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout_5.addItem(spacerItem4)
+        self.verticalLayout_6 = QtGui.QVBoxLayout()
+        self.verticalLayout_6.setObjectName("verticalLayout_6")
+        self.setLogPushButton = QtGui.QPushButton(self.frame_2)
+        self.setLogPushButton.setObjectName("setLogPushButton")
+        self.verticalLayout_6.addWidget(self.setLogPushButton)
+        self.widget = QtGui.QWidget(self.frame_2)
+        self.widget.setObjectName("widget")
+        self.verticalLayout_6.addWidget(self.widget)
+        self.horizontalLayout_5.addLayout(self.verticalLayout_6)
+        self.verticalLayout = QtGui.QVBoxLayout()
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.startLogPushButton = QtGui.QPushButton(self.frame_2)
+        self.startLogPushButton.setObjectName("startLogPushButton")
+        self.verticalLayout.addWidget(self.startLogPushButton)
+        self.stopLogPushButton = QtGui.QPushButton(self.frame_2)
+        self.stopLogPushButton.setObjectName("stopLogPushButton")
+        self.verticalLayout.addWidget(self.stopLogPushButton)
+        self.horizontalLayout_5.addLayout(self.verticalLayout)
+        self.verticalLayout_3.addWidget(self.frame_2)
+        self.frame_4 = QtGui.QFrame(self.centralwidget)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(self.frame_4.sizePolicy().hasHeightForWidth())
+        self.frame_4.setSizePolicy(sizePolicy)
+        self.frame_4.setFrameShape(QtGui.QFrame.StyledPanel)
+        self.frame_4.setFrameShadow(QtGui.QFrame.Raised)
+        self.frame_4.setObjectName("frame_4")
+        self.horizontalLayout_2 = QtGui.QHBoxLayout(self.frame_4)
+        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+        self.logFileLabel = QtGui.QLabel(self.frame_4)
+        self.logFileLabel.setObjectName("logFileLabel")
+        self.horizontalLayout_2.addWidget(self.logFileLabel)
+        spacerItem5 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout_2.addItem(spacerItem5)
+        self.verticalLayout_3.addWidget(self.frame_4)
+        ArraySensorMainWindow.setCentralWidget(self.centralwidget)
+        self.menubar = QtGui.QMenuBar(ArraySensorMainWindow)
+        self.menubar.setGeometry(QtCore.QRect(0, 0, 751, 23))
+        self.menubar.setObjectName("menubar")
+        ArraySensorMainWindow.setMenuBar(self.menubar)
+        self.statusbar = QtGui.QStatusBar(ArraySensorMainWindow)
+        self.statusbar.setObjectName("statusbar")
+        ArraySensorMainWindow.setStatusBar(self.statusbar)
+
+        self.retranslateUi(ArraySensorMainWindow)
+        QtCore.QMetaObject.connectSlotsByName(ArraySensorMainWindow)
+
+    def retranslateUi(self, ArraySensorMainWindow):
+        ArraySensorMainWindow.setWindowTitle(QtGui.QApplication.translate("ArraySensorMainWindow", "Capillary Array Sensor", None, QtGui.QApplication.UnicodeUTF8))
+        self.timeLabel.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Time:         ", None, QtGui.QApplication.UnicodeUTF8))
+        self.levelLabel.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Fluid Level:          ", None, QtGui.QApplication.UnicodeUTF8))
+        self.debugRadioButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "debug", None, QtGui.QApplication.UnicodeUTF8))
+        self.label.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Serial Port", None, QtGui.QApplication.UnicodeUTF8))
+        self.serialPortLineEdit.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "set serial port address", None, QtGui.QApplication.UnicodeUTF8))
+        self.label_2.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Threshold", None, QtGui.QApplication.UnicodeUTF8))
+        self.startPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "start acquiring data from sensor", None, QtGui.QApplication.UnicodeUTF8))
+        self.startPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "        Start      ", None, QtGui.QApplication.UnicodeUTF8))
+        self.stopPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "stop acquisition form sensor", None, QtGui.QApplication.UnicodeUTF8))
+        self.stopPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "       Stop        ", None, QtGui.QApplication.UnicodeUTF8))
+        self.takeBackgroundPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "take background reading of capillary for equalization", None, QtGui.QApplication.UnicodeUTF8))
+        self.takeBackgroundPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Take Background", None, QtGui.QApplication.UnicodeUTF8))
+        self.loadBackgroundPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "load background reading from file", None, QtGui.QApplication.UnicodeUTF8))
+        self.loadBackgroundPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Load Background", None, QtGui.QApplication.UnicodeUTF8))
+        self.deleteBackgroundPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "delete current background reading", None, QtGui.QApplication.UnicodeUTF8))
+        self.deleteBackgroundPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Delete Background", None, QtGui.QApplication.UnicodeUTF8))
+        self.saveBackgroundPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "save current background reading to file", None, QtGui.QApplication.UnicodeUTF8))
+        self.saveBackgroundPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Save Background", None, QtGui.QApplication.UnicodeUTF8))
+        self.setLogPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "set current data log file", None, QtGui.QApplication.UnicodeUTF8))
+        self.setLogPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", " Set Log File  ", None, QtGui.QApplication.UnicodeUTF8))
+        self.startLogPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "start logging data to file", None, QtGui.QApplication.UnicodeUTF8))
+        self.startLogPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Start Logging", None, QtGui.QApplication.UnicodeUTF8))
+        self.stopLogPushButton.setToolTip(QtGui.QApplication.translate("ArraySensorMainWindow", "stop logging data to file", None, QtGui.QApplication.UnicodeUTF8))
+        self.stopLogPushButton.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Stop Logging", None, QtGui.QApplication.UnicodeUTF8))
+        self.logFileLabel.setText(QtGui.QApplication.translate("ArraySensorMainWindow", "Log File: ", None, QtGui.QApplication.UnicodeUTF8))
+
+from mplwidget import MplWidget

array_sensor_gui/mplwidget.py

+from PyQt4 import QtGui
+from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.figure import Figure
+
+class MplCanvas(FigureCanvas):
+
+    def __init__(self):
+        self.fig = Figure()
+        self.ax = self.fig.add_subplot(111)
+        FigureCanvas.__init__(self,self.fig)
+        FigureCanvas.setSizePolicy(
+                self, 
+                QtGui.QSizePolicy.Expanding, 
+                QtGui.QSizePolicy.Expanding
+                )
+        FigureCanvas.updateGeometry(self)
+
+
+class MplWidget(QtGui.QWidget):
+
+    def __init__(self,parent=None):
+        QtGui.QWidget.__init__(self,parent)
+        self.canvas = MplCanvas()
+        self.vbl = QtGui.QVBoxLayout()
+        self.vbl.addWidget(self.canvas)
+        self.setLayout(self.vbl)
+