Пример #1
0
class PatchWindow(QtGui.QMainWindow):
    
    sigWindowClosed = QtCore.Signal(object)
    
    def __init__(self, dm, clampName):
        QtGui.QMainWindow.__init__(self)
        self.setWindowTitle(clampName)
        self.startTime = None
        self.redrawCommand = 1
        
        self.analysisItems = {
            'inputResistance': u'Ω', 
            'accessResistance': u'Ω',
            'capacitance': 'F',
            'restingPotential': 'V', 
            'restingCurrent': 'A', 
            'fitError': ''
        }
        
        self.params = {
            'mode': 'vc',
            'rate': 400000,
            'downsample': 10,
            'cycleTime': .2,
            'recordTime': 0.1,
            'delayTime': 0.03,
            'pulseTime': 0.05,
            'icPulse': -30e-12,
            'vcPulse': -10e-3,
            'icHolding': 0,
            'vcHolding': -65e-3,
            'icHoldingEnabled': False,
            'icPulseEnabled': True,
            'vcHoldingEnabled': False,
            'vcPulseEnabled': True,
            'drawFit': True,
            'average': 1,
        }
        
        
        self.paramLock = Mutex(QtCore.QMutex.Recursive)

        self.manager = dm
        self.clampName = clampName
        self.thread = PatchThread(self)
        self.cw = QtGui.QWidget()
        self.setCentralWidget(self.cw)
        self.ui = Ui_Form()
        self.ui.setupUi(self.cw)
        #self.logBtn = LogButton("Log")
        #self.statusBar().addPermanentWidget(self.logBtn)
        self.setStatusBar(StatusBar())

        self.stateFile = os.path.join('modules', self.clampName + '_ui.cfg')
        uiState = Manager.getManager().readConfigFile(self.stateFile)
        if 'geometry' in uiState:
            geom = QtCore.QRect(*uiState['geometry'])
            self.setGeometry(geom)
        if 'window' in uiState:
            ws = QtCore.QByteArray.fromPercentEncoding(uiState['window'])
            self.restoreState(ws)
            
        self.ui.splitter_2.setSizes([self.width()/4, self.width()*3./4.])


        self.plots = {}
        for k in self.analysisItems:
            p = PlotWidget()
            p.setLabel('left', text=k, units=self.analysisItems[k])
            self.ui.plotLayout.addWidget(p)
            self.plots[k] = p
        #irp = self.plots['inputResistance']
        #irp.setManualYScale()
        #irp.setYLog(True)
        #irp.setYRange(1e6, 1e11)
            
        
        self.ui.icPulseSpin.setOpts(dec=True, step=1, minStep=1e-12, bounds=[None,None], siPrefix=True, suffix='A')
        self.ui.vcPulseSpin.setOpts(dec=True, step=1, minStep=1e-3, bounds=[None,None], siPrefix=True, suffix='V')
        self.ui.icHoldSpin.setOpts(dec=True, step=1, minStep=1e-12, bounds=[None,None], siPrefix=True, suffix='A')
        self.ui.vcHoldSpin.setOpts(dec=True, step=1, minStep=1e-3, bounds=[None,None], siPrefix=True, suffix='V')
        self.ui.cycleTimeSpin.setOpts(dec=True, step=1, minStep=1e-6, bounds=[0,None], siPrefix=True, suffix='s')
        self.ui.pulseTimeSpin.setOpts(dec=True, step=1, minStep=1e-6, bounds=[0,1.], siPrefix=True, suffix='s')
        self.ui.delayTimeSpin.setOpts(dec=True, step=1, minStep=1e-6, bounds=[0,1.], siPrefix=True, suffix='s')
        
        
        self.stateGroup = WidgetGroup([
            (self.ui.icPulseSpin, 'icPulse'),
            (self.ui.vcPulseSpin, 'vcPulse'),
            (self.ui.icHoldSpin, 'icHolding'),
            (self.ui.vcHoldSpin, 'vcHolding'),
            (self.ui.icPulseCheck, 'icPulseEnabled'),
            (self.ui.vcPulseCheck, 'vcPulseEnabled'),
            (self.ui.icHoldCheck, 'icHoldingEnabled'),
            (self.ui.vcHoldCheck, 'vcHoldingEnabled'),
            (self.ui.cycleTimeSpin, 'cycleTime'),
            (self.ui.pulseTimeSpin, 'pulseTime'),
            (self.ui.delayTimeSpin, 'delayTime'),
            (self.ui.drawFitCheck, 'drawFit'),
            (self.ui.averageSpin, 'average'),
        ])
        self.stateGroup.setState(self.params)
        
        self.ui.patchPlot.setLabel('left', text='Primary', units='A')
        self.patchCurve = self.ui.patchPlot.plot(pen=QtGui.QPen(QtGui.QColor(200, 200, 200)))
        self.patchFitCurve = self.ui.patchPlot.plot(pen=QtGui.QPen(QtGui.QColor(0, 100, 200)))
        self.ui.commandPlot.setLabel('left', text='Command', units='V')
        self.commandCurve = self.ui.commandPlot.plot(pen=QtGui.QPen(QtGui.QColor(200, 200, 200)))
        
        self.ui.startBtn.clicked.connect(self.startClicked)
        self.ui.recordBtn.clicked.connect(self.recordClicked)
        self.ui.bathModeBtn.clicked.connect(self.bathMode)
        self.ui.patchModeBtn.clicked.connect(self.patchMode)
        self.ui.cellModeBtn.clicked.connect(self.cellMode)
        self.ui.monitorModeBtn.clicked.connect(self.monitorMode)
        self.ui.resetBtn.clicked.connect(self.resetClicked)
        self.thread.finished.connect(self.threadStopped)
        self.thread.sigNewFrame.connect(self.handleNewFrame)
        self.ui.vcModeRadio.toggled.connect(self.updateParams)
        self.stateGroup.sigChanged.connect(self.updateParams)
                
        ## Configure analysis plots, curves, and data arrays
        self.analysisCurves = {}
        self.analysisData = {'time': []}
        for n in self.analysisItems:
            w = getattr(self.ui, n+'Check')
            w.clicked.connect(self.showPlots)
            p = self.plots[n]
            self.analysisCurves[n] = p.plot(pen=QtGui.QPen(QtGui.QColor(200, 200, 200)))
            for suf in ['', 'Std']:
                self.analysisData[n+suf] = []
        self.showPlots()
        self.updateParams()
        self.show()
        self.bathMode()
    
    def quit(self):
        #print "Stopping patch thread.."
        geom = self.geometry()
        uiState = {'window': str(self.saveState().toPercentEncoding()), 'geometry': [geom.x(), geom.y(), geom.width(), geom.height()]}
        Manager.getManager().writeConfigFile(uiState, self.stateFile)
        
        self.thread.stop(block=True)
        #print "Patch thread exited; module quitting."
        
    def closeEvent(self, ev):
        self.quit()
        self.sigWindowClosed.emit(self)
    
    def bathMode(self):
        self.ui.vcPulseCheck.setChecked(True)
        self.ui.vcHoldCheck.setChecked(False)
        self.ui.vcModeRadio.setChecked(True)
        self.ui.cycleTimeSpin.setValue(0.2)
        self.ui.pulseTimeSpin.setValue(10e-3)
        self.ui.delayTimeSpin.setValue(10e-3)
        self.ui.averageSpin.setValue(1)
    
    def patchMode(self):
        self.ui.vcPulseCheck.setChecked(True)
        self.ui.vcHoldCheck.setChecked(True)
        self.ui.vcModeRadio.setChecked(True)
        self.ui.cycleTimeSpin.setValue(0.2)
        self.ui.pulseTimeSpin.setValue(10e-3)
        self.ui.delayTimeSpin.setValue(10e-3)
        self.ui.averageSpin.setValue(1)
    
    def cellMode(self):
        self.ui.icPulseCheck.setChecked(True)
        self.ui.icModeRadio.setChecked(True)
        self.ui.cycleTimeSpin.setValue(250e-3)
        self.ui.pulseTimeSpin.setValue(150e-3)
        self.ui.delayTimeSpin.setValue(30e-3)
        self.ui.averageSpin.setValue(1)

    def monitorMode(self):
        self.ui.cycleTimeSpin.setValue(40)
        self.ui.averageSpin.setValue(5)
        
    def showPlots(self):
        """Show/hide analysis plot widgets"""
        for n in self.analysisItems:
            w = getattr(self.ui, n+'Check')
            p = self.plots[n]
            if w.isChecked():
                p.show()
            else:
                p.hide()
        self.updateAnalysisPlots()
    
    def updateParams(self, *args):
        with self.paramLock:
            if self.ui.icModeRadio.isChecked():
                mode = 'ic'
            else:
                mode = 'vc'
            self.params['mode'] = mode
            state = self.stateGroup.state()
            for p in self.params:
                if p in state:
                    self.params[p] = state[p]
            self.params['recordTime'] = self.params['delayTime'] *2.0 + self.params['pulseTime']
        self.thread.updateParams()
        self.redrawCommand = 2   ## may need to redraw twice to make sure the update has gone through
        
    def recordClicked(self):
        if self.ui.recordBtn.isChecked():
            data = self.makeAnalysisArray()
            if data.shape[0] == 0:  ## no data yet; don't start the file
                self.storageFile = None
                return
            self.newFile(data)
        else:
            self.storageFile = None
            
    def newFile(self, data):
        sd = self.storageDir()
        self.storageFile = sd.writeFile(data, self.clampName, autoIncrement=True, appendAxis='Time', newFile=True)
        if self.startTime is not None:
            self.storageFile.setInfo({'startTime': self.startTime})
                
    def storageDir(self):
        return self.manager.getCurrentDir().getDir('Patch', create=True)
                
    #def storageFile(self):
        #sd = self.storageDir()
        #return sd.getFile(self.clampName, create=True)
            
        
    def resetClicked(self):
        self.ui.recordBtn.setChecked(False)
        self.recordClicked()
        for n in self.analysisData:
            self.analysisData[n] = []
        self.startTime = None
        
    def handleNewFrame(self, frame):
        prof = Profiler('PatchWindow.handleNewFrame', disabled=True)
        with self.paramLock:
            mode = self.params['mode']
        
        data = frame['data'][self.clampName]
        
        if mode == 'vc':
            self.ui.patchPlot.setLabel('left', units='A')
        else:
            self.ui.patchPlot.setLabel('left', units='V')
        prof.mark('1')
            
        self.patchCurve.setData(data.xvals('Time'), data['primary'])
        prof.mark('2')
        if self.redrawCommand > 0:
            self.redrawCommand -= 1
            #print "set command curve"
            self.commandCurve.setData(data.xvals('Time'), data['command'])
            if mode == 'vc':
                self.ui.commandPlot.setLabel('left', units='V')
            else:
                self.ui.commandPlot.setLabel('left', units='A')
        prof.mark('3')
        #self.ui.patchPlot.replot()
        #self.ui.commandPlot.replot()
        if frame['analysis']['fitTrace'] is not None:
            self.patchFitCurve.show()
            self.patchFitCurve.setData(data.xvals('Time'), frame['analysis']['fitTrace'])
        else:
            self.patchFitCurve.hide()
        prof.mark('4')
        
        for k in self.analysisItems:
            if k in frame['analysis']:
                self.analysisData[k].append(frame['analysis'][k])
        prof.mark('5')
                
        for r in ['input', 'access']:
            res = r+'Resistance'
            label = getattr(self.ui, res+'Label')
            resistance = frame['analysis'][res]
            label.setText(siFormat(resistance) + u'Ω')
        prof.mark('6')
        self.ui.restingPotentialLabel.setText(siFormat(frame['analysis']['restingPotential'], error=frame['analysis']['restingPotentialStd'], suffix='V'))
        self.ui.restingCurrentLabel.setText(siFormat(frame['analysis']['restingCurrent'], error=frame['analysis']['restingCurrentStd'], suffix='A'))
        self.ui.capacitanceLabel.setText('%sF' % siFormat(frame['analysis']['capacitance']))
        self.ui.fitErrorLabel.setText('%7.2g' % frame['analysis']['fitError'])
        prof.mark('7')
        
        start = data._info[-1]['DAQ']['command']['startTime']
        if self.startTime is None:
            self.startTime = start
            if self.ui.recordBtn.isChecked() and self.storageFile is not None:
                self.storageFile.setInfo({'startTime': self.startTime})
        self.analysisData['time'].append(start - self.startTime)
        prof.mark('8')
        self.updateAnalysisPlots()
        prof.mark('9')
        
        ## Record to disk if requested.
        if self.ui.recordBtn.isChecked():
            
            arr = self.makeAnalysisArray(lastOnly=True)
            #print "appending array", arr.shape
            if self.storageFile is None:
                self.newFile(arr)
            else:
                arr.write(self.storageFile.name(), appendAxis='Time')
        prof.mark('10')
        prof.finish()
        
    def makeAnalysisArray(self, lastOnly=False):
        ## Determine how much of the data to include in this array
        if lastOnly:
            sl = slice(-1, None)
        else:
            sl = slice(None)
            
        ## Generate the meta-info structure
        info = [
            {'name': 'Time', 'values': self.analysisData['time'][sl], 'units': 's'},
            {'name': 'Value', 'cols': []}
        ]
        for k in self.analysisItems:
            for s in ['', 'Std']:
                if len(self.analysisData[k+s]) < 1:
                    continue
                info[1]['cols'].append({'name': k+s, 'units': self.analysisItems[k]})
                
        ## Create the blank MetaArray
        data = MetaArray(
            (len(info[0]['values']), len(info[1]['cols'])), 
            dtype=float,
            info=info
        )
        
        ## Fill with data
        for k in self.analysisItems:
            for s in ['', 'Std']:
                if len(self.analysisData[k+s]) < 1:
                    continue
                try:
                    data[:, k+s] = self.analysisData[k+s][sl]
                except:
                    print data.shape, data[:, k+s].shape, len(self.analysisData[k+s][sl])
                    raise
                
        return data
            
            
        
    def updateAnalysisPlots(self):
        for n in self.analysisItems:
            p = self.plots[n]
            if p.isVisible():
                self.analysisCurves[n].setData(self.analysisData['time'], self.analysisData[n])
                #if len(self.analysisData[n+'Std']) > 0:
                    #self.analysisCurves[p+'Std'].setData(self.analysisData['time'], self.analysisData[n+'Std'])
                #p.replot()
    
    def startClicked(self):
        if self.ui.startBtn.isChecked():
            if not self.thread.isRunning():
                self.thread.start()
                Manager.logMsg("Patch module started.")
            self.ui.startBtn.setText('Stop')
        else:
            self.ui.startBtn.setEnabled(False)
            self.thread.stop()
            Manager.logMsg("Patch module stopped.")
            
    def threadStopped(self):
        self.ui.startBtn.setText('Start')
        self.ui.startBtn.setEnabled(True)
        self.ui.startBtn.setChecked(False)
Пример #2
0
class DaqChannelGui(QtGui.QWidget):
    def __init__(self,
                 parent,
                 name,
                 config,
                 plot,
                 dev,
                 taskRunner,
                 daqName=None):
        QtGui.QWidget.__init__(self, parent)

        ## Name of this channel
        self.name = name

        ## Parent taskGui object
        self.taskGui = weakref.ref(parent)

        ## Configuration for this channel defined in the device configuration file
        self.config = config

        self.scale = 1.0
        self.units = ''

        ## The device handle for this channel's DAQGeneric device
        self.dev = dev

        ## The task GUI window which contains this object
        self.taskRunner = weakref.ref(taskRunner)

        ## Make sure task interface includes our DAQ device
        if daqName is None:
            self.daqDev = self.dev.getDAQName(self.name)
        else:
            self.daqDev = daqName
        self.daqUI = self.taskRunner().getDevice(self.daqDev)

        ## plot widget
        self.plot = plot
        self.plot.setDownsampling(ds=True, auto=True, mode='peak')
        self.plot.setClipToView(True)

    def postUiInit(self):
        ## Automatically locate all read/writable widgets and group them together for easy
        ## save/restore operations
        self.stateGroup = WidgetGroup(self)
        self.stateGroup.addWidget(self.plot, name='plot')

        self.displayCheckChanged()
        self.ui.displayCheck.stateChanged.connect(self.displayCheckChanged)

        if 'units' in self.config:
            self.setUnits(self.config['units'])
        else:
            self.setUnits('')

    def updateTitle(self):
        self.ui.groupBox.setTitle(self.name + " (%s)" % self.units)

    def setUnits(self, units):
        self.units = units
        for s in self.getSpins():
            if isinstance(s, SpinBox):
                s.setOpts(suffix=units)
        self.updateTitle()

    def getSpins(self):
        return []

    def setChildrenVisible(self, obj, vis):
        for c in obj.children():
            if isinstance(c, QtGui.QWidget):
                c.setVisible(vis)
            else:
                self.setChildrenVisible(c, vis)

    def saveState(self):
        return self.stateGroup.state()

    def restoreState(self, state):
        self.stateGroup.setState(state)
        if hasattr(self.ui, 'waveGeneratorWidget'):
            self.ui.waveGeneratorWidget.update()

    def clearPlots(self):
        self.plot.clear()
        self.currentPlot = None

    def displayCheckChanged(self):
        if self.stateGroup.state()['displayCheck']:
            self.plot.show()
        else:
            self.plot.hide()

    def taskStarted(self, params):
        pass

    def taskSequenceStarted(self):
        pass

    def quit(self):
        #print "quit DAQGeneric channel", self.name
        self.plot.close()
Пример #3
0
class DaqChannelGui(QtGui.QWidget):
    def __init__(self, parent, name, config, plot, dev, taskRunner, daqName=None):
        QtGui.QWidget.__init__(self, parent)
        
        ## Name of this channel
        self.name = name
        
        ## Parent taskGui object
        self.taskGui = weakref.ref(parent)
        
        ## Configuration for this channel defined in the device configuration file
        self.config = config
        
        self.scale = 1.0
        self.units = ''
        
        ## The device handle for this channel's DAQGeneric device
        self.dev = dev
        
        ## The task GUI window which contains this object
        self.taskRunner = weakref.ref(taskRunner)
        
        ## Make sure task interface includes our DAQ device
        if daqName is None:
            self.daqDev = self.dev.getDAQName(self.name)
        else:
            self.daqDev = daqName
        self.daqUI = self.taskRunner().getDevice(self.daqDev)
        
        ## plot widget
        self.plot = plot
        self.plot.setDownsampling(ds=True, auto=True, mode='peak')
        self.plot.setClipToView(True)
        #plot.setCanvasBackground(QtGui.QColor(0,0,0))
        #plot.replot()
        
        ## Curves displayed in self.plot
        #self.plots = []
        
        
            
    def postUiInit(self):
        ## Automatically locate all read/writable widgets and group them together for easy 
        ## save/restore operations
        self.stateGroup = WidgetGroup(self)
        self.stateGroup.addWidget(self.plot, name='plot')
        
        self.displayCheckChanged()
        #QtCore.QObject.connect(self.ui.displayCheck, QtCore.SIGNAL('stateChanged(int)'), self.displayCheckChanged)
        self.ui.displayCheck.stateChanged.connect(self.displayCheckChanged)
        #QtCore.QObject.connect(self.ui.groupBox, QtCore.SIGNAL('toggled(bool)'), self.groupBoxClicked)
        self.ui.groupBox.toggled.connect(self.groupBoxClicked)
        
        #if 'userScale' in self.config:
            #self.setScale(self.config['userScale'])
        #else:
            #self.setScale(1.0)
        
        if 'units' in self.config:
            self.setUnits(self.config['units'])
        else:
            self.setUnits('')
            
    def updateTitle(self):
        if self.ui.groupBox.isChecked():
            plus = ""
        else:
            plus = "[+] "
        
        #units = " (x%s)" % siFormat(self.scale, suffix=self.units)
        
        
        self.ui.groupBox.setTitle(plus + self.name + " (%s)" %self.units)
    
    def setUnits(self, units):
        self.units = units
        for s in self.getSpins():
            if isinstance(s, SpinBox):
                s.setOpts(suffix=units)
        self.updateTitle()

    def getSpins(self):
        return []

    #def setScale(self, scale):
        #self.scale = scale
        #self.updateTitle()
	        
    def groupBoxClicked(self, b):
        self.setChildrenVisible(self.ui.groupBox, b)
        self.updateTitle()
        #if b:
        #    self.ui.groupBox.setTitle(unicode(self.ui.groupBox.title())[4:])
        #else:
        #    self.ui.groupBox.setTitle("[+] " + unicode(self.ui.groupBox.title()))
            
    def setChildrenVisible(self, obj, vis):
        for c in obj.children():
            if isinstance(c, QtGui.QWidget):
                c.setVisible(vis)
            else:
                self.setChildrenVisible(c, vis)
            
    def saveState(self):
        return self.stateGroup.state()
    
    def restoreState(self, state):
        self.stateGroup.setState(state)
        if hasattr(self.ui, 'waveGeneratorWidget'):
            self.ui.waveGeneratorWidget.update()

    def clearPlots(self):
        #for i in self.plots:
            #i.detach()
        #self.plots = []
        self.plot.clear()
        self.currentPlot = None

    def displayCheckChanged(self):
        if self.stateGroup.state()['displayCheck']:
            self.plot.show()
        else:
            self.plot.hide()
            
    def taskStarted(self, params):
        pass
    
    def taskSequenceStarted(self):
        pass
    
    def quit(self):
        #print "quit DAQGeneric channel", self.name
        self.plot.close()
Пример #4
0
class PatchWindow(QtGui.QMainWindow):

    sigWindowClosed = QtCore.Signal(object)

    def __init__(self, dm, config):
        clampName = config['clampDev']
        QtGui.QMainWindow.__init__(self)
        self.setWindowTitle(clampName)
        self.startTime = None
        self.redrawCommand = 1

        self.analysisItems = {
            'inputResistance': u'Ω',
            'accessResistance': u'Ω',
            'capacitance': 'F',
            'restingPotential': 'V',
            'restingCurrent': 'A',
            'fitError': ''
        }

        self.params = {
            'mode': 'vc',
            'rate': config.get('sampleRate', 100000),
            'downsample': config.get('downsample', 3),
            'cycleTime': .2,
            'recordTime': 0.1,
            'delayTime': 0.03,
            'pulseTime': 0.05,
            'icPulse': -30e-12,
            'vcPulse': -10e-3,
            'icHolding': 0,
            'vcHolding': -65e-3,
            'icHoldingEnabled': False,
            'icPulseEnabled': True,
            'vcHoldingEnabled': False,
            'vcPulseEnabled': True,
            'drawFit': True,
            'average': 1,
        }

        self.paramLock = Mutex(QtCore.QMutex.Recursive)

        self.manager = dm
        self.clampName = clampName
        self.thread = PatchThread(self)
        self.cw = QtGui.QWidget()
        self.setCentralWidget(self.cw)
        self.ui = Ui_Form()
        self.ui.setupUi(self.cw)
        #self.logBtn = LogButton("Log")
        #self.statusBar().addPermanentWidget(self.logBtn)
        self.setStatusBar(StatusBar())

        self.stateFile = os.path.join('modules', self.clampName + '_ui.cfg')
        uiState = Manager.getManager().readConfigFile(self.stateFile)
        if 'geometry' in uiState:
            geom = QtCore.QRect(*uiState['geometry'])
            self.setGeometry(geom)
        if 'window' in uiState:
            ws = QtCore.QByteArray.fromPercentEncoding(uiState['window'])
            self.restoreState(ws)

        self.ui.splitter_2.setSizes([self.width() / 4, self.width() * 3. / 4.])
        self.ui.splitter.setStretchFactor(0, 30)
        self.ui.splitter.setStretchFactor(1, 10)

        self.plots = {}
        for k in self.analysisItems:
            p = PlotWidget()
            p.setLabel('left', text=k, units=self.analysisItems[k])
            self.ui.plotLayout.addWidget(p)
            self.plots[k] = p
        irp = self.plots['inputResistance']
        irp.setLogMode(y=True, x=False)
        irp.setYRange(6, 11)

        self.ui.icPulseSpin.setOpts(dec=True,
                                    step=1,
                                    minStep=1e-12,
                                    bounds=[None, None],
                                    siPrefix=True,
                                    suffix='A')
        self.ui.vcPulseSpin.setOpts(dec=True,
                                    step=1,
                                    minStep=1e-3,
                                    bounds=[None, None],
                                    siPrefix=True,
                                    suffix='V')
        self.ui.icHoldSpin.setOpts(dec=True,
                                   step=1,
                                   minStep=1e-12,
                                   bounds=[None, None],
                                   siPrefix=True,
                                   suffix='A')
        self.ui.vcHoldSpin.setOpts(dec=True,
                                   step=1,
                                   minStep=1e-3,
                                   bounds=[None, None],
                                   siPrefix=True,
                                   suffix='V')
        self.ui.cycleTimeSpin.setOpts(dec=True,
                                      step=1,
                                      minStep=1e-6,
                                      bounds=[0, None],
                                      siPrefix=True,
                                      suffix='s')
        self.ui.pulseTimeSpin.setOpts(dec=True,
                                      step=1,
                                      minStep=1e-6,
                                      bounds=[0, 1.],
                                      siPrefix=True,
                                      suffix='s')
        self.ui.delayTimeSpin.setOpts(dec=True,
                                      step=1,
                                      minStep=1e-6,
                                      bounds=[0, 1.],
                                      siPrefix=True,
                                      suffix='s')

        self.stateGroup = WidgetGroup([
            (self.ui.icPulseSpin, 'icPulse'),
            (self.ui.vcPulseSpin, 'vcPulse'),
            (self.ui.icHoldSpin, 'icHolding'),
            (self.ui.vcHoldSpin, 'vcHolding'),
            (self.ui.icPulseCheck, 'icPulseEnabled'),
            (self.ui.vcPulseCheck, 'vcPulseEnabled'),
            (self.ui.icHoldCheck, 'icHoldingEnabled'),
            (self.ui.vcHoldCheck, 'vcHoldingEnabled'),
            (self.ui.cycleTimeSpin, 'cycleTime'),
            (self.ui.pulseTimeSpin, 'pulseTime'),
            (self.ui.delayTimeSpin, 'delayTime'),
            (self.ui.drawFitCheck, 'drawFit'),
            (self.ui.averageSpin, 'average'),
        ])
        self.stateGroup.setState(self.params)

        self.ui.patchPlot.setLabel('left', text='Primary', units='A')
        self.patchCurve = self.ui.patchPlot.plot(
            pen=QtGui.QPen(QtGui.QColor(200, 200, 200)))
        self.patchFitCurve = self.ui.patchPlot.plot(
            pen=QtGui.QPen(QtGui.QColor(0, 100, 200)))
        self.ui.commandPlot.setLabel('left', text='Command', units='V')
        self.commandCurve = self.ui.commandPlot.plot(
            pen=QtGui.QPen(QtGui.QColor(200, 200, 200)))

        self.ui.startBtn.clicked.connect(self.startClicked)
        self.ui.recordBtn.clicked.connect(self.recordClicked)
        self.ui.bathModeBtn.clicked.connect(self.bathMode)
        self.ui.patchModeBtn.clicked.connect(self.patchMode)
        self.ui.cellModeBtn.clicked.connect(self.cellMode)
        self.ui.monitorModeBtn.clicked.connect(self.monitorMode)
        self.ui.resetBtn.clicked.connect(self.resetClicked)
        self.thread.finished.connect(self.threadStopped)
        self.thread.sigNewFrame.connect(self.handleNewFrame)
        self.ui.vcModeRadio.toggled.connect(self.updateParams)
        self.stateGroup.sigChanged.connect(self.updateParams)

        ## Configure analysis plots, curves, and data arrays
        self.analysisCurves = {}
        self.analysisData = {'time': []}
        for n in self.analysisItems:
            w = getattr(self.ui, n + 'Check')
            w.clicked.connect(self.showPlots)
            p = self.plots[n]
            self.analysisCurves[n] = p.plot(
                pen=QtGui.QPen(QtGui.QColor(200, 200, 200)))
            for suf in ['', 'Std']:
                self.analysisData[n + suf] = []
        self.showPlots()
        self.updateParams()
        self.show()
        self.bathMode()

    def quit(self):
        #print "Stopping patch thread.."
        geom = self.geometry()
        uiState = {
            'window': str(self.saveState().toPercentEncoding()),
            'geometry': [geom.x(),
                         geom.y(),
                         geom.width(),
                         geom.height()]
        }
        Manager.getManager().writeConfigFile(uiState, self.stateFile)

        self.thread.stop(block=True)
        #print "Patch thread exited; module quitting."

    def closeEvent(self, ev):
        self.quit()
        self.sigWindowClosed.emit(self)

    def bathMode(self):
        self.ui.vcPulseCheck.setChecked(True)
        self.ui.vcHoldCheck.setChecked(False)
        self.ui.vcModeRadio.setChecked(True)
        self.ui.cycleTimeSpin.setValue(0.2)
        self.ui.pulseTimeSpin.setValue(10e-3)
        self.ui.delayTimeSpin.setValue(10e-3)
        self.ui.averageSpin.setValue(1)

    def patchMode(self):
        self.ui.vcPulseCheck.setChecked(True)
        self.ui.vcHoldCheck.setChecked(True)
        self.ui.vcModeRadio.setChecked(True)
        self.ui.cycleTimeSpin.setValue(0.2)
        self.ui.pulseTimeSpin.setValue(10e-3)
        self.ui.delayTimeSpin.setValue(10e-3)
        self.ui.averageSpin.setValue(1)

    def cellMode(self):
        self.ui.icPulseCheck.setChecked(True)
        self.ui.icModeRadio.setChecked(True)
        self.ui.cycleTimeSpin.setValue(250e-3)
        self.ui.pulseTimeSpin.setValue(150e-3)
        self.ui.delayTimeSpin.setValue(30e-3)
        self.ui.averageSpin.setValue(1)

    def monitorMode(self):
        self.ui.cycleTimeSpin.setValue(40)
        self.ui.averageSpin.setValue(5)

    def showPlots(self):
        """Show/hide analysis plot widgets"""
        for n in self.analysisItems:
            w = getattr(self.ui, n + 'Check')
            p = self.plots[n]
            if w.isChecked():
                p.show()
            else:
                p.hide()
        self.updateAnalysisPlots()

    def updateParams(self, *args):
        with self.paramLock:
            if self.ui.icModeRadio.isChecked():
                mode = 'ic'
            else:
                mode = 'vc'
            self.params['mode'] = mode
            state = self.stateGroup.state()
            for p in self.params:
                if p in state:
                    self.params[p] = state[p]
            self.params['recordTime'] = self.params[
                'delayTime'] * 2.0 + self.params['pulseTime']
        self.thread.updateParams()
        self.redrawCommand = 2  ## may need to redraw twice to make sure the update has gone through

    def recordClicked(self):
        if self.ui.recordBtn.isChecked():
            data = self.makeAnalysisArray()
            if data.shape[0] == 0:  ## no data yet; don't start the file
                self.storageFile = None
                return
            self.newFile(data)
        else:
            self.storageFile = None

    def newFile(self, data):
        sd = self.storageDir()
        self.storageFile = sd.writeFile(data,
                                        self.clampName,
                                        autoIncrement=True,
                                        appendAxis='Time',
                                        newFile=True)
        if self.startTime is not None:
            self.storageFile.setInfo({'startTime': self.startTime})

    def storageDir(self):
        return self.manager.getCurrentDir().getDir('Patch', create=True)

    #def storageFile(self):
    #sd = self.storageDir()
    #return sd.getFile(self.clampName, create=True)

    def resetClicked(self):
        self.ui.recordBtn.setChecked(False)
        self.recordClicked()
        for n in self.analysisData:
            self.analysisData[n] = []
        self.startTime = None

    def handleNewFrame(self, frame):
        prof = Profiler('PatchWindow.handleNewFrame', disabled=True)
        with self.paramLock:
            mode = self.params['mode']

        data = frame['data'][self.clampName]

        if mode == 'vc':
            self.ui.patchPlot.setLabel('left', units='A')
        else:
            self.ui.patchPlot.setLabel('left', units='V')
        prof.mark('1')

        self.patchCurve.setData(data.xvals('Time'), data['primary'])
        prof.mark('2')
        if self.redrawCommand > 0:
            self.redrawCommand -= 1
            #print "set command curve"
            self.commandCurve.setData(data.xvals('Time'), data['command'])
            if mode == 'vc':
                self.ui.commandPlot.setLabel('left', units='V')
            else:
                self.ui.commandPlot.setLabel('left', units='A')
        prof.mark('3')
        #self.ui.patchPlot.replot()
        #self.ui.commandPlot.replot()
        if frame['analysis']['fitTrace'] is not None:
            self.patchFitCurve.show()
            self.patchFitCurve.setData(data.xvals('Time'),
                                       frame['analysis']['fitTrace'])
        else:
            self.patchFitCurve.hide()
        prof.mark('4')

        for k in self.analysisItems:
            if k in frame['analysis']:
                self.analysisData[k].append(frame['analysis'][k])
        prof.mark('5')

        for r in ['input', 'access']:
            res = r + 'Resistance'
            label = getattr(self.ui, res + 'Label')
            resistance = frame['analysis'][res]
            label.setText(siFormat(resistance) + u'Ω')
        prof.mark('6')
        self.ui.restingPotentialLabel.setText(
            siFormat(frame['analysis']['restingPotential'],
                     error=frame['analysis']['restingPotentialStd'],
                     suffix='V'))
        self.ui.restingCurrentLabel.setText(
            siFormat(frame['analysis']['restingCurrent'],
                     error=frame['analysis']['restingCurrentStd'],
                     suffix='A'))
        self.ui.capacitanceLabel.setText(
            '%sF' % siFormat(frame['analysis']['capacitance']))
        self.ui.fitErrorLabel.setText('%7.2g' % frame['analysis']['fitError'])
        prof.mark('7')

        start = data._info[-1]['DAQ']['command']['startTime']
        if self.startTime is None:
            self.startTime = start
            if self.ui.recordBtn.isChecked() and self.storageFile is not None:
                self.storageFile.setInfo({'startTime': self.startTime})
        self.analysisData['time'].append(start - self.startTime)
        prof.mark('8')
        self.updateAnalysisPlots()
        prof.mark('9')

        ## Record to disk if requested.
        if self.ui.recordBtn.isChecked():

            arr = self.makeAnalysisArray(lastOnly=True)
            #print "appending array", arr.shape
            if self.storageFile is None:
                self.newFile(arr)
            else:
                arr.write(self.storageFile.name(), appendAxis='Time')
        prof.mark('10')
        prof.finish()

    def makeAnalysisArray(self, lastOnly=False):
        ## Determine how much of the data to include in this array
        if lastOnly:
            sl = slice(-1, None)
        else:
            sl = slice(None)

        ## Generate the meta-info structure
        info = [{
            'name': 'Time',
            'values': self.analysisData['time'][sl],
            'units': 's'
        }, {
            'name': 'Value',
            'cols': []
        }]
        for k in self.analysisItems:
            for s in ['', 'Std']:
                if len(self.analysisData[k + s]) < 1:
                    continue
                info[1]['cols'].append({
                    'name': k + s,
                    'units': self.analysisItems[k]
                })

        ## Create the blank MetaArray
        data = MetaArray((len(info[0]['values']), len(info[1]['cols'])),
                         dtype=float,
                         info=info)

        ## Fill with data
        for k in self.analysisItems:
            for s in ['', 'Std']:
                if len(self.analysisData[k + s]) < 1:
                    continue
                try:
                    data[:, k + s] = self.analysisData[k + s][sl]
                except:
                    print data.shape, data[:, k + s].shape, len(
                        self.analysisData[k + s][sl])
                    raise

        return data

    def updateAnalysisPlots(self):
        for n in self.analysisItems:
            p = self.plots[n]
            if p.isVisible():
                self.analysisCurves[n].setData(self.analysisData['time'],
                                               self.analysisData[n])
                #if len(self.analysisData[n+'Std']) > 0:
                #self.analysisCurves[p+'Std'].setData(self.analysisData['time'], self.analysisData[n+'Std'])
                #p.replot()

    def startClicked(self):
        if self.ui.startBtn.isChecked():
            if not self.thread.isRunning():
                self.thread.start()
                Manager.logMsg("Patch module started.")
            self.ui.startBtn.setText('Stop')
        else:
            self.ui.startBtn.setEnabled(False)
            self.thread.stop()
            Manager.logMsg("Patch module stopped.")

    def threadStopped(self):
        self.ui.startBtn.setText('Start')
        self.ui.startBtn.setEnabled(True)
        self.ui.startBtn.setChecked(False)