예제 #1
0
class AnalysisModule(QtGui.QWidget):
    def __init__(self, taskRunner):
        QtGui.QWidget.__init__(self)
        self.pr = taskRunner
        self.pr.sigNewFrame.connect(self.newFrame)
        self.pr.sigTaskSequenceStarted.connect(self.taskSequenceStarted)
        self.pr.sigTaskStarted.connect(self.taskStarted)
        self.pr.sigTaskFinished.connect(self.taskFinished)

    def postGuiInit(self):
        self.stateGroup = WidgetGroup(self)

    def newFrame(self, *args):
        pass
        #print "NEW FRAME!"
        #print args

    def taskStarted(self, *args):
        pass
        #print "taskStarted!"
        
    def taskFinished(self):
        pass
    
    def taskSequenceStarted(self, *args):
        pass
        #print "taskStarted!"
    
    def saveState(self):
        return self.stateGroup.state()
        
    def restoreState(self, state):
        self.stateGroup.setState(state)
        
    def quit(self):
        try:
            self.pr.sigNewFrame.disconnect(self.newFrame)
        except TypeError:
            pass
        try:
            self.pr.sigTaskSequenceStarted.disconnect(self.taskSequenceStarted)
        except TypeError:
            pass
        try:
            self.pr.sigTaskStarted.disconnect(self.taskStarted)
        except TypeError:
            pass
        try:
            self.pr.sigTaskFinished.disconnect(self.taskFinished)
        except TypeError:
            pass
예제 #2
0
class AnalysisModule(QtGui.QWidget):
    def __init__(self, taskRunner):
        QtGui.QWidget.__init__(self)
        self.pr = taskRunner
        self.pr.sigNewFrame.connect(self.newFrame)
        self.pr.sigTaskSequenceStarted.connect(self.taskSequenceStarted)
        self.pr.sigTaskStarted.connect(self.taskStarted)
        self.pr.sigTaskFinished.connect(self.taskFinished)

    def postGuiInit(self):
        self.stateGroup = WidgetGroup(self)

    def newFrame(self, *args):
        pass
        #print "NEW FRAME!"
        #print args

    def taskStarted(self, *args):
        pass
        #print "taskStarted!"

    def taskFinished(self):
        pass

    def taskSequenceStarted(self, *args):
        pass
        #print "taskStarted!"

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

    def restoreState(self, state):
        self.stateGroup.setState(state)

    def quit(self):
        try:
            self.pr.sigNewFrame.disconnect(self.newFrame)
        except TypeError:
            pass
        try:
            self.pr.sigTaskSequenceStarted.disconnect(self.taskSequenceStarted)
        except TypeError:
            pass
        try:
            self.pr.sigTaskStarted.disconnect(self.taskStarted)
        except TypeError:
            pass
        try:
            self.pr.sigTaskFinished.disconnect(self.taskFinished)
        except TypeError:
            pass
예제 #3
0
파일: taskGUI.py 프로젝트: ablot/acq4
class DAQGenericTaskGui(TaskGui):
    
    #sigSequenceChanged = QtCore.Signal(object)  ## defined upstream
    
    def __init__(self, dev, task, ownUi=True):
        TaskGui.__init__(self, dev, task)
        self.plots = weakref.WeakValueDictionary()
        self.channels = {}
        
        if ownUi:
            self.ui = Ui_Form()
            self.ui.setupUi(self)
            self.stateGroup = WidgetGroup([
                (self.ui.topSplitter, 'splitter1'),
                (self.ui.controlSplitter, 'splitter2'),
                (self.ui.plotSplitter, 'splitter3'),
            ])
            self.createChannelWidgets(self.ui.controlSplitter, self.ui.plotSplitter)
            self.ui.topSplitter.setStretchFactor(0, 0)
            self.ui.topSplitter.setStretchFactor(1, 1)
            
        else:
            ## If ownUi is False, then the UI is created elsewhere and createChannelWidgets must be called from there too.
            self.stateGroup = None
        
    def createChannelWidgets(self, ctrlParent, plotParent):
        ## Create plots and control widgets
        for ch in self.dev._DGConfig:
            (w, p) = self.createChannelWidget(ch)
            plotParent.addWidget(p)
            ctrlParent.addWidget(w)
        

    def createChannelWidget(self, ch, daqName=None):
        conf = self.dev._DGConfig[ch]
        p = PlotWidget(self)
        
        units = ''
        if 'units' in conf:
            units = conf['units']
            
        #p.setAxisTitle(PlotWidget.yLeft, ch+units)
        p.setLabel('left', text=ch, units=units)
        #print "Plot label:", ch, units
        self.plots[ch] = p
        
        p.registerPlot(self.dev.name() + '.' + ch)
        
        if conf['type'] in ['ao', 'do']:
            w = OutputChannelGui(self, ch, conf, p, self.dev, self.taskRunner, daqName)
            #QtCore.QObject.connect(w, QtCore.SIGNAL('sequenceChanged'), self.sequenceChanged)
            w.sigSequenceChanged.connect(self.sequenceChanged)
        elif conf['type'] in ['ai', 'di']:
            w = InputChannelGui(self, ch, conf, p, self.dev, self.taskRunner, daqName)
        else:
            raise Exception("Unrecognized device type '%s'" % conf['type'])
        # w.setUnits(units)
        self.channels[ch] = w
        
        return (w, p)

    def saveState(self):
        if self.stateGroup is not None:
            state = self.stateGroup.state().copy()
        else:
            state = {}
        state['channels'] = {}
        for ch in self.channels:
            state['channels'][ch] = self.channels[ch].saveState()
        return state

    def restoreState(self, state):
        try:
            if self.stateGroup is not None:
                self.stateGroup.setState(state)
            for ch in state['channels']:
                try:
                    self.channels[ch].restoreState(state['channels'][ch])
                except KeyError:
                    printExc("Warning: Cannot restore state for channel %s.%s (channel does not exist on this device)" % (self.dev.name(), ch))
                    continue    
                self.channels[ch].restoreState(state['channels'][ch])
        except:
            printExc('Error while restoring GUI state:')
            #sys.excepthook(*sys.exc_info())
        #self.ui.waveGeneratorWidget.update()
            
        
    def listSequence(self):
        ## returns sequence parameter names and lengths
        l = {}
        for ch in self.channels:
            chl = self.channels[ch].listSequence()
            for k in chl:
                l[ch+'.'+k] = chl[k]
        return l
        
    def sequenceChanged(self):
        #self.emit(QtCore.SIGNAL('sequenceChanged'), self.dev.name())
        self.sigSequenceChanged.emit(self.dev.name())
        
    def taskStarted(self, params):  ## automatically invoked from TaskGui
        ## Pull out parameters for this device
        params = dict([(p[1], params[p]) for p in params if p[0] == self.dev.name()])
        
        for ch in self.channels:
            ## Extract just the parameters the channel will need
            chParams = {}
            search = ch + '.'
            for k in params:
                if k[:len(search)] == search:
                    chParams[k[len(search):]] = params[k]
            self.channels[ch].taskStarted(chParams)
            
    def taskSequenceStarted(self):  ## automatically invoked from TaskGui
        for ch in self.channels:
            self.channels[ch].taskSequenceStarted()
        
        
    def generateTask(self, params=None):
        #print "generating task with:", params
        if params is None:
            params = {}
        p = {}
        for ch in self.channels:
            ## Extract just the parameters the channel will need
            chParams = {}
            search = ch + '.'
            for k in params:
                if k[:len(search)] == search:
                    chParams[k[len(search):]] = params[k]
            ## request the task from the channel
            #print "  requesting %s task with params:"%ch, chParams
            p[ch] = self.channels[ch].generateTask(chParams)
        #print p
        return p
        
    def handleResult(self, result, params):
        #print "handling result:", result
        if result is None:
            return
        for ch in self.channels:
            #print "\nhandle result for", ch
            #print 'result:', type(result)
            #print result
            #print "--------\n"
            #if ch not in result:
                #print "  no result"
                #continue
            #print result.infoCopy()
            if result.hasColumn(0, ch):
                self.channels[ch].handleResult(result[ch], params)
            
    def getChanHolding(self, chan):
        """Return the holding value that this channel will use when the task is run."""
        return self.dev.getChanHolding(chan)
            
    def quit(self):
        TaskGui.quit(self)
        for ch in self.channels:
            self.channels[ch].quit()
예제 #4
0
파일: taskGUI.py 프로젝트: neurodebian/acq4
class NiDAQTask(TaskGui):
    
    sigChanged = QtCore.Signal(object)
    
    def __init__(self, dev, task):
        TaskGui.__init__(self, dev, task)
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        self.nPts = 0
        self.ignorePeriod = False
        self.ignoreRate = False
        self.rate = 40e3
        self.updateNPts()
        self.updateDevList()
        #self.devs = []
        self.ui.rateSpin.setOpts(dec=True, step=0.5, minStep=10, bounds=[1,None], siPrefix=True, suffix='Hz')
        self.ui.periodSpin.setOpts(dec=True, step=0.5, minStep=1e-6, bounds=[1e-6,None], siPrefix=True, suffix='s')
        self.ui.besselCutoffSpin.setOpts(value=20e3, dec=True, step=0.5, minStep=10, bounds=[1,None], siPrefix=True, suffix='Hz')
        self.ui.butterworthPassbandSpin.setOpts(value=20e3, dec=True, step=0.5, minStep=10, bounds=[1,None], siPrefix=True, suffix='Hz')
        self.ui.butterworthStopbandSpin.setOpts(value=40e3, dec=True, step=0.5, minStep=10, bounds=[1,None], siPrefix=True, suffix='Hz')
        
        ## important to create widget group before connecting anything else.
        self.stateGroup = WidgetGroup([
            (self.ui.rateSpin, 'rate'),
            (self.ui.downsampleSpin, 'downsample'),
            (self.ui.triggerDevList, 'triggerDevice'),
            (self.ui.denoiseCombo, 'denoiseMethod'),
            (self.ui.denoiseThresholdSpin, 'denoiseThreshold'),
            (self.ui.denoiseWidthSpin, 'denoiseWidth'),
            (self.ui.filterCombo, 'filterMethod'),
            (self.ui.besselCutoffSpin, 'besselCutoff'),
            (self.ui.besselOrderSpin, 'besselOrder'),
            (self.ui.butterworthPassbandSpin, 'butterworthPassband'),
            (self.ui.butterworthStopbandSpin, 'butterworthStopband'),
            (self.ui.butterworthPassDBSpin, 'butterworthPassDB'),
            (self.ui.butterworthStopDBSpin, 'butterworthStopDB'),
        ])
        
        
        
        self.ui.rateSpin.valueChanged.connect(self.rateChanged)
        self.ui.periodSpin.sigValueChanging.connect(self.updateRateSpin)
        self.ui.rateSpin.sigValueChanging.connect(self.updatePeriodSpin)
        self.task.sigTaskChanged.connect(self.taskChanged)
        self.ui.denoiseCombo.currentIndexChanged.connect(self.ui.denoiseStack.setCurrentIndex)
        self.ui.filterCombo.currentIndexChanged.connect(self.ui.filterStack.setCurrentIndex)
        self.ui.rateSpin.setValue(self.rate)
        
        
    def quit(self):
        TaskGui.quit(self)
        QtCore.QObject.disconnect(self.task, QtCore.SIGNAL('taskChanged'), self.taskChanged)
        
    def saveState(self):
        return self.stateGroup.state()
        
    def restoreState(self, state):
        
        try:
            #if 'rate' in state:
                #if 'downsample' in state:
                    #self.ui.downsampleSpin.setValue(state['downsample'])
                #self.ui.rateSpin.setValue(state['rate'] / 1000.)
                ##print "trigger dev:", state['triggerDevice']
                ##print self.devs
                #if 'triggerDevice' in state and state['triggerDevice'] in self.devs:
                    #self.ui.triggerDevList.setCurrentIndex(self.devs.index(state['triggerDevice'])+1)
                    ##print "Set index to", self.devs.index(state['triggerDevice'])+1
                #else:
                    #self.ui.triggerDevList.setCurrentIndex(0)
                    ##print "No index"
            #else:
            self.stateGroup.setState(state)
        except:
            #sys.excepthook(*sys.exc_info())
            printExc("Error while loading DAQ task GUI configuration (proceeding with default configuration) :")
        
    def generateTask(self, params=None):
        task = self.currentState()
        task2 = {}
        
        # just for cleanliness, remove any filtering parameters that are not in use:
        remNames = ['butterworth', 'bessel']
        if task['filterMethod'].lower() in remNames:
            remNames.remove(task['filterMethod'].lower())
        if task['denoiseMethod'] == 'None':
            remNames.append('denoiseWidth')
            remNames.append('denoiseThreshold')
        for k in task:
            if not any(map(k.startswith, remNames)):
                task2[k] = task[k]
        
        return task2
        
    def currentState(self):
        self.updateNPts()
        state = self.stateGroup.state()
        ## make sure all of these are up to date:
        state['numPts'] = self.nPts
        state['rate'] = self.rate
        state['downsample'] = self.ui.downsampleSpin.value()
        if self.ui.triggerDevList.currentIndex() > 0:
            state['triggerDevice'] = str(self.ui.triggerDevList.currentText())
        else:
            del state['triggerDevice']
        
        return state
        
    def updatePeriodSpin(self):
        if self.ignoreRate:
            return
        period = 1. / self.ui.rateSpin.value()
        
        self.ignorePeriod = True
        self.ui.periodSpin.setValue(period)
        self.ignorePeriod = False
        
    def updateRateSpin(self):
        if self.ignorePeriod:
            return
        period = self.ui.periodSpin.value()
        rate = 1.0 / period
        self.ignoreRate = True
        self.ui.rateSpin.setValue(rate)
        self.ignoreRate = False
        
    def rateChanged(self):
        self.rate = self.ui.rateSpin.value()
        self.updateNPts()
        self.sigChanged.emit(self.currentState())
        
        
    def taskChanged(self, n, v):
        #print "caught task change", n, v
        if n == 'duration':
            self.updateNPts()
            self.sigChanged.emit(self.currentState())
        
    def updateNPts(self):
        dur = self.task.getParam('duration')
        nPts = int(dur * self.rate)
        if nPts != self.nPts:
            self.nPts = nPts
            self.ui.numPtsLabel.setText(str(self.nPts))
        
    def updateDevList(self):
        ## list all devices
        allDevNames = self.dev.dm.listDevices()
        ## convert device names into device handles
        allDevs = [self.dev.dm.getDevice(d) for d in allDevNames]
        ## select out devices which have trigger channel to this DAQ
        self.devs = [d.name() for d in allDevs if d.getTriggerChannel(self.dev.name()) is not None]
            
            
        self.ui.triggerDevList.clear()
        self.ui.triggerDevList.addItem('No Trigger')
        
        for d in self.devs:
            #print d, self.dev.name
            #dev = self.dev.dm.getDevice(d)
            #if dev.getTriggerChannel(self.dev.name) is not None:
                #print "------"
            self.ui.triggerDevList.addItem(d)
예제 #5
0
class NiDAQTask(TaskGui):

    sigChanged = QtCore.Signal(object)

    def __init__(self, dev, taskRunner):
        TaskGui.__init__(self, dev, taskRunner)
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        self.nPts = 0
        self.ignorePeriod = False
        self.ignoreRate = False
        self.rate = 40e3

        self._block_update = False  # use to block signal emission during bulk updates

        self.updateNPts()
        self.updateDevList()
        self.ui.rateSpin.setOpts(dec=True,
                                 step=0.5,
                                 minStep=10,
                                 bounds=[1, None],
                                 siPrefix=True,
                                 suffix='Hz')
        self.ui.periodSpin.setOpts(dec=True,
                                   step=0.5,
                                   minStep=1e-6,
                                   bounds=[1e-6, None],
                                   siPrefix=True,
                                   suffix='s')
        self.ui.besselCutoffSpin.setOpts(value=20e3,
                                         dec=True,
                                         step=0.5,
                                         minStep=10,
                                         bounds=[1, None],
                                         siPrefix=True,
                                         suffix='Hz')
        self.ui.butterworthPassbandSpin.setOpts(value=20e3,
                                                dec=True,
                                                step=0.5,
                                                minStep=10,
                                                bounds=[1, None],
                                                siPrefix=True,
                                                suffix='Hz')
        self.ui.butterworthStopbandSpin.setOpts(value=40e3,
                                                dec=True,
                                                step=0.5,
                                                minStep=10,
                                                bounds=[1, None],
                                                siPrefix=True,
                                                suffix='Hz')

        ## important to create widget group before connecting anything else.
        self.stateGroup = WidgetGroup([
            (self.ui.rateSpin, 'rate'),
            (self.ui.downsampleSpin, 'downsample'),
            (self.ui.triggerDevList, 'triggerDevice'),
            (self.ui.denoiseCombo, 'denoiseMethod'),
            (self.ui.denoiseThresholdSpin, 'denoiseThreshold'),
            (self.ui.denoiseWidthSpin, 'denoiseWidth'),
            (self.ui.filterCombo, 'filterMethod'),
            (self.ui.besselCutoffSpin, 'besselCutoff'),
            (self.ui.besselOrderSpin, 'besselOrder'),
            (self.ui.besselBidirCheck, 'besselBidirectional'),
            (self.ui.butterworthPassbandSpin, 'butterworthPassband'),
            (self.ui.butterworthStopbandSpin, 'butterworthStopband'),
            (self.ui.butterworthPassDBSpin, 'butterworthPassDB'),
            (self.ui.butterworthStopDBSpin, 'butterworthStopDB'),
            (self.ui.butterworthBidirCheck, 'butterworthBidirectional'),
        ])

        self.ui.periodSpin.sigValueChanging.connect(self.updateRateSpin)
        self.ui.rateSpin.sigValueChanging.connect(self.updatePeriodSpin)
        self.taskRunner.sigTaskChanged.connect(self.taskChanged)
        self.ui.denoiseCombo.currentIndexChanged.connect(
            self.updateDenoiseCtrl)
        self.ui.filterCombo.currentIndexChanged.connect(self.updateFilterCtrl)
        self.ui.rateSpin.setValue(self.rate)
        self.stateGroup.sigChanged.connect(self.stateChanged)

        self.updateDenoiseCtrl()
        self.updateFilterCtrl()

    def quit(self):
        self.taskRunner.sigTaskChanged.disconnect(self.taskChanged)
        TaskGui.quit(self)

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

    def restoreState(self, state):
        block = self._block_update
        try:
            self._block_update = True
            self.stateGroup.setState(state)
        except Exception:
            printExc(
                "Error while loading DAQ task GUI configuration (proceeding with default configuration) :"
            )
        finally:
            self._block_update = block
            self.stateChanged()

    def generateTask(self, params=None):
        task = self.currentState()
        task2 = {}

        # just for cleanliness, remove any filtering parameters that are not in use:
        remNames = ['butterworth', 'bessel']
        if task['filterMethod'].lower() in remNames:
            remNames.remove(task['filterMethod'].lower())
        if task['denoiseMethod'] == 'None':
            remNames.append('denoiseWidth')
            remNames.append('denoiseThreshold')
        for k in task:
            if not any(map(k.startswith, remNames)):
                task2[k] = task[k]

        return task2

    def currentState(self):
        self.updateNPts()
        state = self.stateGroup.state()
        ## make sure all of these are up to date:
        state['numPts'] = self.nPts
        state['rate'] = self.rate
        state['downsample'] = self.ui.downsampleSpin.value()
        if self.ui.triggerDevList.currentIndex() > 0:
            state['triggerDevice'] = str(self.ui.triggerDevList.currentText())
        else:
            del state['triggerDevice']

        return state

    def updatePeriodSpin(self):
        if self.ignoreRate:
            return
        period = 1. / self.ui.rateSpin.value()

        self.ignorePeriod = True
        self.ui.periodSpin.setValue(period)
        self.ignorePeriod = False

    def updateRateSpin(self):
        if self.ignorePeriod:
            return
        period = self.ui.periodSpin.value()
        rate = 1.0 / period
        self.ignoreRate = True
        self.ui.rateSpin.setValue(rate)
        self.ignoreRate = False

    def stateChanged(self, name=None, val=None):
        if name == 'rate':
            self.rate = self.ui.rateSpin.value()
            self.updateNPts()

        if self._block_update:
            return
        state = self.currentState()
        self.sigChanged.emit(state)

    def taskChanged(self, n, v):
        if n == 'duration':
            self.updateNPts()
            self.sigChanged.emit(self.currentState())

    def updateNPts(self):
        dur = self.taskRunner.getParam('duration')
        nPts = int(dur * self.rate)
        if nPts != self.nPts:
            self.nPts = nPts
            self.ui.numPtsLabel.setText(str(self.nPts))

    def updateDevList(self):
        ## list all devices
        allDevNames = self.dev.dm.listDevices()
        ## convert device names into device handles
        allDevs = [self.dev.dm.getDevice(d) for d in allDevNames]
        ## select out devices which have trigger channel to this DAQ
        self.devs = [
            d.name() for d in allDevs
            if d.getTriggerChannel(self.dev.name()) is not None
        ]

        self.ui.triggerDevList.clear()
        self.ui.triggerDevList.addItem('No Trigger')

        for d in self.devs:
            self.ui.triggerDevList.addItem(d)

    def updateDenoiseCtrl(self):
        denoise = self.ui.denoiseCombo.currentText()
        if denoise == 'None':
            self.ui.denoiseCtrl.hide()
        else:
            self.ui.denoiseCtrl.show()

    def updateFilterCtrl(self):
        filter = self.ui.filterCombo.currentText()
        if filter == 'None':
            self.ui.besselCtrl.hide()
            self.ui.butterworthCtrl.hide()
        elif filter == 'Bessel':
            self.ui.besselCtrl.show()
            self.ui.butterworthCtrl.hide()
        elif filter == 'Butterworth':
            self.ui.besselCtrl.hide()
            self.ui.butterworthCtrl.show()
예제 #6
0
class MultiClampTaskGui(TaskGui):

    #sigSequenceChanged = Qt.Signal(object)  ## defined upstream

    def __init__(self, dev, taskRunner):
        TaskGui.__init__(self, dev, taskRunner)
        daqDev = self.dev.getDAQName()
        self.daqUI = self.taskRunner.getDevice(daqDev)

        self.traces = {
        }  ## Stores traces from a sequence to allow average plotting
        self.resetInpPlots = False  ## Signals result handler to clear plots before adding a new one
        self.currentCmdPlot = None
        self._block_update = False  # blocks plotting during state changes

        self.ui = Ui_Form()
        self.ui.setupUi(self)

        self.ui.splitter_2.setStretchFactor(0, 0)
        self.ui.splitter_2.setStretchFactor(1, 1)
        self.ui.splitter.setStretchFactor(0, 3)
        self.ui.splitter.setStretchFactor(1, 1)

        self.stateGroup = WidgetGroup(self)
        #self.ui.waveGeneratorWidget.setTimeScale(1e-3)
        self.ui.waveGeneratorWidget.setMeta('x',
                                            units='s',
                                            siPrefix=True,
                                            dec=True,
                                            step=0.5,
                                            minStep=1e-6)
        self.unitLabels = [self.ui.waveGeneratorLabel, self.ui.holdingCheck]
        #self.modeSignalList = self.dev.listModeSignals()
        self.mode = None
        self.setMode('I=0')

        self.ui.topPlotWidget.registerPlot(self.dev.name() + '.Input')
        self.ui.topPlotWidget.setDownsampling(ds=True, auto=True, mode='peak')
        self.ui.topPlotWidget.setClipToView(True)
        self.ui.bottomPlotWidget.registerPlot(self.dev.name() + '.Command')
        self.ui.bottomPlotWidget.setDownsampling(ds=True,
                                                 auto=True,
                                                 mode='peak')
        self.ui.bottomPlotWidget.setClipToView(True)

        self.daqChanged(self.daqUI.currentState())
        self.daqUI.sigChanged.connect(self.daqChanged)
        self.ui.waveGeneratorWidget.sigDataChanged.connect(self.updateWaves)
        self.ui.waveGeneratorWidget.sigParametersChanged.connect(
            self.sequenceChanged)
        self.stateGroup.sigChanged.connect(self.uiStateChanged)
        self.dev.sigStateChanged.connect(self.devStateChanged)
        self.dev.sigHoldingChanged.connect(self.devHoldingChanged)
        self.uiStateChanged('', '')
        self.devStateChanged()

    def uiStateChanged(self, name, value):
        if 'ModeRadio' in name:
            self.setMode()

        if self.getMode() == 'I=0':
            self.ui.holdingCheck.setChecked(False)
            self.ui.holdingCheck.setEnabled(False)
        else:
            self.ui.holdingCheck.setEnabled(True)

        checkMap = {
            'holdingCheck': self.ui.holdingSpin,
            'primarySignalCheck': self.ui.primarySignalCombo,
            'secondarySignalCheck': self.ui.secondarySignalCombo,
            'primaryGainCheck': self.ui.primaryGainSpin,
            'secondaryGainCheck': self.ui.secondaryGainSpin,
        }

        ## For each check box, enable its corresponding control
        if name in checkMap:
            checkMap[name].setEnabled(value)
            self.devStateChanged()

    def devStateChanged(self, state=None):
        mode = self.getMode()
        state = self.dev.getLastState(mode)

        if not self.ui.holdingSpin.isEnabled():
            self.ui.holdingSpin.setValue(state['holding'])
        if not self.ui.primaryGainSpin.isEnabled():
            self.ui.primaryGainSpin.setValue(state['primaryGain'])
        if not self.ui.secondaryGainSpin.isEnabled():
            self.ui.secondaryGainSpin.setValue(state['secondaryGain'])

        psig = ssig = None
        if not self.ui.primarySignalCombo.isEnabled():
            psig = state['primarySignal']
        if not self.ui.secondarySignalCombo.isEnabled():
            ssig = state['secondarySignal']
        self.setSignals(psig, ssig)

    def devHoldingChanged(self, dev, mode):
        if mode != self.getMode():
            return
        if not self.ui.holdingSpin.isEnabled():
            state = self.dev.getLastState(mode)
            self.ui.holdingSpin.setValue(state['holding'])

    def saveState(self):
        state = self.stateGroup.state().copy()
        state['mode'] = self.getMode()
        state['primarySignal'] = str(self.ui.primarySignalCombo.currentText())
        state['secondarySignal'] = str(
            self.ui.secondarySignalCombo.currentText())
        return state

    def restoreState(self, state):
        block = self._block_update
        try:
            self._block_update = True
            self.setMode(state['mode'])
            if 'primarySignal' in state and 'secondarySignal' in state:
                self.setSignals(state['primarySignal'],
                                state['secondarySignal'])
            self.stateGroup.setState(state)
            self.devStateChanged()
        except:
            printExc('Error while restoring MultiClamp task GUI state:')
        finally:
            self._block_update = block
        self.updateWaves()

    def daqChanged(self, state):
        self.rate = state['rate']
        self.numPts = state['numPts']
        self.timeVals = numpy.linspace(0,
                                       float(self.numPts) / self.rate,
                                       self.numPts)
        self.updateWaves()

    def listSequence(self):
        return self.ui.waveGeneratorWidget.listSequences()

    def sequenceChanged(self):
        self.sigSequenceChanged.emit(self.dev.name())

    def updateWaves(self):
        if self._block_update:
            return

        self.clearCmdPlots()

        ## compute sequence waves
        params = {}
        ps = self.ui.waveGeneratorWidget.listSequences()
        for k in ps:
            params[k] = range(len(ps[k]))
        waves = []
        runSequence(lambda p: waves.append(self.getSingleWave(p)), params,
                    list(params.keys()))

        # Plot all waves but disable auto-range first to improve performance.
        autoRange = self.ui.bottomPlotWidget.getViewBox().autoRangeEnabled()
        self.ui.bottomPlotWidget.enableAutoRange(x=False, y=False)
        try:
            for w in waves:
                if w is not None:
                    self.plotCmdWave(w,
                                     color=Qt.QColor(100, 100, 100),
                                     replot=False)

            ## display single-mode wave in red
            single = self.getSingleWave()
            if single is not None:
                p = self.plotCmdWave(single, color=Qt.QColor(200, 100, 100))
                p.setZValue(1000)

        finally:
            # re-enable auto range if needed
            self.ui.bottomPlotWidget.enableAutoRange(x=autoRange[0],
                                                     y=autoRange[1])

    def clearCmdPlots(self):
        self.ui.bottomPlotWidget.clear()
        self.currentCmdPlot = None

    def taskSequenceStarted(self):
        self.resetInpPlots = True

    def clearInpPlots(self):
        self.traces = {}
        self.ui.topPlotWidget.clear()

    def taskStarted(self, params):
        ## Draw green trace for current command waveform
        if self.currentCmdPlot is not None:
            self.ui.bottomPlotWidget.removeItem(self.currentCmdPlot)
        params = dict([(p[1], params[p]) for p in params
                       if p[0] == self.dev.name()])
        cur = self.getSingleWave(params)
        if cur is not None:
            self.currentCmdPlot = self.plotCmdWave(cur,
                                                   color=Qt.QColor(
                                                       100, 200, 100))
            self.currentCmdPlot.setZValue(1001)

    def plotCmdWave(self, data, color=Qt.QColor(100, 100, 100), replot=True):
        if data is None:
            return
        plot = self.ui.bottomPlotWidget.plot(data, x=self.timeVals)
        plot.setPen(Qt.QPen(color))

        return plot

    def generateTask(self, params=None):
        state = self.stateGroup.state()
        if params is None:
            params = {}
        task = {}
        mode = self.getMode()
        task['mode'] = mode
        task['recordState'] = True
        #if self.ui.primarySignalCheck.isChecked():
        #task['primary'] = self.ui.primarySignalCombo.currentText()
        #if self.ui.secondarySignalCheck.isChecked():
        #task['secondary'] = self.ui.secondarySignalCombo.currentText()
        if state['primarySignalCheck']:
            task['primarySignal'] = state['primarySignalCombo']
        if state['secondarySignalCheck']:
            task['secondarySignal'] = state['secondarySignalCombo']
        if state['primaryGainCheck']:
            task['primaryGain'] = state['primaryGainSpin']
        if state['secondaryGainCheck']:
            task['secondaryGain'] = state['secondaryGainSpin']
        if mode != 'I=0':
            ## Must scale command to V or A before sending to task system.
            wave = self.getSingleWave(params)
            if wave is not None:
                task['command'] = wave
            if state['holdingCheck']:
                task['holding'] = state['holdingSpin']
        #print "Task:", task
        return task

    def getSingleWave(self, params=None):
        state = self.stateGroup.state()
        h = state['holdingSpin']
        self.ui.waveGeneratorWidget.setOffset(h)
        ## waveGenerator generates values in V or A
        wave = self.ui.waveGeneratorWidget.getSingle(self.rate, self.numPts,
                                                     params)

        if wave is None:
            return None
        return wave

    def getMode(self):
        if self.ui.icModeRadio.isChecked():
            self.mode = 'IC'
        elif self.ui.i0ModeRadio.isChecked():
            self.mode = 'I=0'
        else:
            self.mode = 'VC'
        return self.mode

    def setMode(self, mode=None):
        if mode != self.mode:
            oldMode = self.mode
            if mode is None:
                mode = self.getMode()
                #print "Set mode to", mode
            # set radio button
            if mode == 'IC':
                self.ui.icModeRadio.setChecked(True)
            elif mode == 'I=0':
                self.ui.i0ModeRadio.setChecked(True)
            else:
                self.ui.vcModeRadio.setChecked(True)

            # update signal lists
            self.stateGroup.blockSignals(True)
            sigs = self.dev.listSignals(mode)
            #print "Signals:", sigs
            #print "-------"
            for s, c in [(sigs[0], self.ui.primarySignalCombo),
                         (sigs[1], self.ui.secondarySignalCombo)]:
                c.clear()
                for ss in s:
                    c.addItem(ss)
            self.stateGroup.blockSignals(False)

            # Disable signal, holding, and gain checks (only when switching between v and i modes)
            if mode == 'VC' or oldMode == 'VC':
                self.ui.primarySignalCheck.setChecked(False)
                self.ui.secondarySignalCheck.setChecked(False)
                self.ui.holdingCheck.setChecked(False)
                self.ui.primaryGainCheck.setChecked(False)
                self.ui.secondaryGainCheck.setChecked(False)
                self.devStateChanged()

            # update unit labels and scaling
            if mode == 'VC':
                newUnit = 'V'
                oldUnit = 'A'
                spinOpts = dict(suffix='V',
                                siPrefix=True,
                                dec=True,
                                step=0.5,
                                minStep=1e-3)
                self.ui.waveGeneratorWidget.setMeta('y', **spinOpts)
                self.ui.waveGeneratorWidget.setMeta('xy',
                                                    units='V*s',
                                                    siPrefix=True,
                                                    dec=True,
                                                    step=0.5,
                                                    minStep=1e-6)
            else:
                newUnit = 'A'
                oldUnit = 'V'
                spinOpts = dict(suffix='A',
                                siPrefix=True,
                                dec=True,
                                step=0.5,
                                minStep=1e-12)
                self.ui.waveGeneratorWidget.setMeta('y', **spinOpts)
                self.ui.waveGeneratorWidget.setMeta('xy',
                                                    units='C',
                                                    siPrefix=True,
                                                    dec=True,
                                                    step=0.5,
                                                    minStep=1e-15)
            self.ui.holdingSpin.setOpts(**spinOpts)
            for l in self.unitLabels:
                text = str(l.text())
                l.setText(text.replace(oldUnit, newUnit))
            self.ui.topPlotWidget.setLabel('left', units=oldUnit)
            self.ui.bottomPlotWidget.setLabel('left', units=newUnit)

            ## Hide stim plot for I=0 mode
            if mode == 'I=0':
                self.ui.bottomPlotWidget.hide()
            else:
                self.ui.bottomPlotWidget.show()

            self.devStateChanged()

        self.mode = mode

    def setSignals(self, pri, sec):
        #print "setSignals", pri, sec
        for c, s in [(self.ui.primarySignalCombo, pri),
                     (self.ui.secondarySignalCombo, sec)]:
            if s is None:
                continue
            ind = c.findText(s)
            if ind == -1:
                for i in range(c.count()):
                    print(c.itemText(i))
                raise Exception('Signal "%s" does not exist' % s)
            c.setCurrentIndex(ind)

    def handleResult(self, result, params):
        if self.resetInpPlots:
            self.resetInpPlots = False
            self.clearInpPlots()

        ## Plot the results
        #plot = self.ui.topPlotWidget.plot(result['primary'].view(numpy.ndarray) / self.inpScale, x=result.xvals('Time'), params=params)
        plot = self.ui.topPlotWidget.plot(result['primary'].view(
            numpy.ndarray),
                                          x=result.xvals('Time'),
                                          params=params)

    def quit(self):
        TaskGui.quit(self)
        if not sip.isdeleted(self.daqUI):
            Qt.QObject.disconnect(self.daqUI, Qt.SIGNAL('changed'),
                                  self.daqChanged)
        self.ui.topPlotWidget.close()
        self.ui.bottomPlotWidget.close()
예제 #7
0
파일: taskGUI.py 프로젝트: neurodebian/acq4
class MultiClampTaskGui(TaskGui):
    
    #sigSequenceChanged = QtCore.Signal(object)  ## defined upstream
    
    def __init__(self, dev, task):
        TaskGui.__init__(self, dev, task)
        daqDev = self.dev.getDAQName()
        self.daqUI = self.task.getDevice(daqDev)
        
        self.traces = {}  ## Stores traces from a sequence to allow average plotting
        self.resetInpPlots = False  ## Signals result handler to clear plots before adding a new one
        self.currentCmdPlot = None
        
        
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        
        self.ui.splitter_2.setStretchFactor(0, 0)
        self.ui.splitter_2.setStretchFactor(1, 1)
        self.ui.splitter.setStretchFactor(0, 3)
        self.ui.splitter.setStretchFactor(1, 1)
        
        self.stateGroup = WidgetGroup(self)
        #self.ui.waveGeneratorWidget.setTimeScale(1e-3)
        self.ui.waveGeneratorWidget.setMeta('x', units='s', siPrefix=True, dec=True, step=0.5, minStep=1e-6)
        self.unitLabels = [self.ui.waveGeneratorLabel, self.ui.holdingCheck]
        #self.modeSignalList = self.dev.listModeSignals()
        self.mode = None
        self.setMode('I=0')

        self.ui.topPlotWidget.registerPlot(self.dev.name() + '.Input')
        self.ui.bottomPlotWidget.registerPlot(self.dev.name() + '.Command')

        self.daqChanged(self.daqUI.currentState())
        self.daqUI.sigChanged.connect(self.daqChanged)
        self.ui.waveGeneratorWidget.sigDataChanged.connect(self.updateWaves)
        self.ui.waveGeneratorWidget.sigParametersChanged.connect(self.sequenceChanged)
        self.stateGroup.sigChanged.connect(self.uiStateChanged)
        self.dev.sigStateChanged.connect(self.devStateChanged)
        self.devStateChanged()
        
        
    def uiStateChanged(self, name, value):
        if 'ModeRadio' in name:
            self.setMode()
        
        #i0Checks = [self.ui.holdingCheck, self.ui.primaryGainCheck, self.ui.secondaryGainCheck]
        if self.getMode() == 'I=0':
            self.ui.holdingCheck.setChecked(False)
            self.ui.holdingCheck.setEnabled(False)
            #for c in i0Checks:
                #c.setChecked(False)
                #c.setEnabled(False)
        else:
            self.ui.holdingCheck.setEnabled(True)
            #for c in i0Checks:
                #c.setEnabled(True)
            
        checkMap = {
            'holdingCheck': self.ui.holdingSpin,
            'primarySignalCheck': self.ui.primarySignalCombo,
            'secondarySignalCheck': self.ui.secondarySignalCombo,
            'primaryGainCheck': self.ui.primaryGainSpin,
            'secondaryGainCheck': self.ui.secondaryGainSpin,
        }
        
        ## For each check box, enable its corresponding control
        if name in checkMap:
            checkMap[name].setEnabled(value)
            self.devStateChanged()
            
        

    def devStateChanged(self, state=None):
        mode = self.getMode()
        state = self.dev.getLastState(mode)
        
        if not self.ui.holdingSpin.isEnabled():
            self.ui.holdingSpin.setValue(state['holding'])
        if not self.ui.primaryGainSpin.isEnabled():
            self.ui.primaryGainSpin.setValue(state['primaryGain'])
        if not self.ui.secondaryGainSpin.isEnabled():
            self.ui.secondaryGainSpin.setValue(state['secondaryGain'])
            
        psig = ssig = None
        if not self.ui.primarySignalCombo.isEnabled():
            psig = state['primarySignal']
        if not self.ui.secondarySignalCombo.isEnabled():
            ssig = state['secondarySignal']
        self.setSignals(psig, ssig)
        
            

    def saveState(self):
        state = self.stateGroup.state().copy()
        state['mode'] = self.getMode()
        state['primarySignal'] = str(self.ui.primarySignalCombo.currentText())
        state['secondarySignal'] = str(self.ui.secondarySignalCombo.currentText())
        return state
        
    def restoreState(self, state):
        try:
            self.setMode(state['mode'])
            if 'primarySignal' in state and 'secondarySignal' in state:
                self.setSignals(state['primarySignal'], state['secondarySignal'])
            self.stateGroup.setState(state)
        except:
            printExc('Error while restoring MultiClamp task GUI state:')
            
        #self.ui.waveGeneratorWidget.update() ## should be called as a result of stateGroup.setState; don't need to call again
        
        
        
    def daqChanged(self, state):
        self.rate = state['rate']
        self.numPts = state['numPts']
        self.timeVals = numpy.linspace(0, float(self.numPts)/self.rate, self.numPts)
        self.updateWaves()
        
    def listSequence(self):
        return self.ui.waveGeneratorWidget.listSequences()

    def sequenceChanged(self):
        #self.emit(QtCore.SIGNAL('sequenceChanged'), self.dev.name)
        self.sigSequenceChanged.emit(self.dev.name())

    def updateWaves(self):
        self.clearCmdPlots()
        
        ## display sequence waves
        params = {}
        ps = self.ui.waveGeneratorWidget.listSequences()
        for k in ps:
            params[k] = range(len(ps[k]))
        waves = []
        runSequence(lambda p: waves.append(self.getSingleWave(p)), params, params.keys())
        for w in waves:
            if w is not None:
                #self.plotCmdWave(w / self.cmdScale, color=QtGui.QColor(100, 100, 100), replot=False)
                self.plotCmdWave(w, color=QtGui.QColor(100, 100, 100), replot=False)
        
        ## display single-mode wave in red
        single = self.getSingleWave()
        if single is not None:
            #self.plotCmdWave(single / self.cmdScale, color=QtGui.QColor(200, 100, 100))
            p = self.plotCmdWave(single, color=QtGui.QColor(200, 100, 100))
            p.setZValue(1000)
        #self.paramListChanged
        
    def clearCmdPlots(self):
        self.ui.bottomPlotWidget.clear()
        self.currentCmdPlot = None

    def taskSequenceStarted(self):
        self.resetInpPlots = True

    def clearInpPlots(self):
        self.traces = {}
        self.ui.topPlotWidget.clear()
        
    def taskStarted(self, params):
        ## Draw green trace for current command waveform
        if self.currentCmdPlot is not None:
            self.ui.bottomPlotWidget.removeItem(self.currentCmdPlot)
        params = dict([(p[1], params[p]) for p in params if p[0] == self.dev.name()])
        cur = self.getSingleWave(params) 
        if cur is not None:
            self.currentCmdPlot = self.plotCmdWave(cur, color=QtGui.QColor(100, 200, 100))
            self.currentCmdPlot.setZValue(1001)
        
    def plotCmdWave(self, data, color=QtGui.QColor(100, 100, 100), replot=True):
        if data is None:
            return
        plot = self.ui.bottomPlotWidget.plot(data, x=self.timeVals)
        plot.setPen(QtGui.QPen(color))
        
        return plot
        
    def generateTask(self, params=None):
        state = self.stateGroup.state()
        if params is None:
            params = {}
        task = {}
        mode = self.getMode()
        task['mode'] = mode
        task['recordState'] = True
        #if self.ui.primarySignalCheck.isChecked():
            #task['primary'] = self.ui.primarySignalCombo.currentText()
        #if self.ui.secondarySignalCheck.isChecked():
            #task['secondary'] = self.ui.secondarySignalCombo.currentText()
        if state['primarySignalCheck']:
            task['primarySignal'] = state['primarySignalCombo']
        if state['secondarySignalCheck']:
            task['secondarySignal'] = state['secondarySignalCombo']
        if state['primaryGainCheck']:
            task['primaryGain'] = state['primaryGainSpin']
        if state['secondaryGainCheck']:
            task['secondaryGain'] = state['secondaryGainSpin']
        if mode != 'I=0':
            ## Must scale command to V or A before sending to task system.
            wave = self.getSingleWave(params)
            if wave is not None:
                task['command'] = wave
            if state['holdingCheck']:
                task['holding'] = state['holdingSpin']
        #print "Task:", task
        return task
    
    def getSingleWave(self, params=None):
        state = self.stateGroup.state()
        h = state['holdingSpin']
        #if state['holdingCheck']:
            #h = state['holdingSpin']
        #else:
            #h = 0.0
        self.ui.waveGeneratorWidget.setOffset(h)
        #self.ui.waveGeneratorWidget.setScale(self.cmdScale)
        ## waveGenerator generates values in V or A
        wave = self.ui.waveGeneratorWidget.getSingle(self.rate, self.numPts, params)
        
        if wave is None:
            return None
        #if state['holdingCheck']:
            #wave += (state['holdingSpin'] / self.cmdScale)
        return wave
        
        
    def getMode(self):
        if self.ui.icModeRadio.isChecked():
            self.mode =  'IC'
        elif self.ui.i0ModeRadio.isChecked():
            self.mode = 'I=0'
        else:
            self.mode = 'VC'
        return self.mode
        
    def setMode(self, mode=None):
        if mode != self.mode:
            oldMode = self.mode
            if mode is None:
                mode = self.getMode()
                #print "Set mode to", mode
            # set radio button
            if mode == 'IC':
                self.ui.icModeRadio.setChecked(True)
            elif mode == 'I=0':
                self.ui.i0ModeRadio.setChecked(True)
            else:
                self.ui.vcModeRadio.setChecked(True)
            
            # update signal lists
            self.stateGroup.blockSignals(True)
            sigs = self.dev.listSignals(mode)
            #print "Signals:", sigs
            #print "-------"
            for s, c in [(sigs[0], self.ui.primarySignalCombo),(sigs[1], self.ui.secondarySignalCombo)]:
                c.clear()
                for ss in s:
                    c.addItem(ss)
            self.stateGroup.blockSignals(False)
            
            #self.ui.primarySignalCombo.clear()
            #for s in self.modeSignalList['primary'][mode]:
                #self.ui.primarySignalCombo.addItem(s)
            #self.ui.secondarySignalCombo.clear()
            #for s in self.modeSignalList['secondary'][mode]:
                #self.ui.secondarySignalCombo.addItem(s)
            
            # Disable signal, holding, and gain checks (only when switching between v and i modes)
            if mode == 'VC' or oldMode == 'VC':
                self.ui.primarySignalCheck.setChecked(False)
                self.ui.secondarySignalCheck.setChecked(False)
                self.ui.holdingCheck.setChecked(False)
                self.ui.holdingSpin.setValue(0.0)
                self.ui.primaryGainCheck.setChecked(False)
                self.ui.secondaryGainCheck.setChecked(False)
            
            # update unit labels and scaling
            if mode == 'VC':
                newUnit = 'V'
                oldUnit = 'A'
                #self.cmdScale = 1e-3
                #self.inpScale = 1e-12
                spinOpts = dict(suffix='V', siPrefix=True, dec=True, step=0.5, minStep=1e-3)
                self.ui.waveGeneratorWidget.setMeta('y', **spinOpts)
                self.ui.waveGeneratorWidget.setMeta('xy', units='V*s', siPrefix=True, dec=True, step=0.5, minStep=1e-6)
            else:
                newUnit = 'A'
                oldUnit = 'V'
                #self.cmdScale = 1e-12
                #self.inpScale = 1e-3
                spinOpts = dict(suffix='A', siPrefix=True, dec=True, step=0.5, minStep=1e-12)
                self.ui.waveGeneratorWidget.setMeta('y', **spinOpts)
                self.ui.waveGeneratorWidget.setMeta('xy', units='C', siPrefix=True, dec=True, step=0.5, minStep=1e-15)
            #self.stateGroup.setScale(self.ui.holdingSpin, 1./self.cmdScale)
            self.ui.holdingSpin.setOpts(**spinOpts)
            #self.ui.waveGeneratorWidget.setScale(self.cmdScale)
            for l in self.unitLabels:
                text = str(l.text())
                l.setText(text.replace(oldUnit, newUnit))
            self.ui.topPlotWidget.setLabel('left', units=oldUnit)
            self.ui.bottomPlotWidget.setLabel('left', units=newUnit)
                
            ## Hide stim plot for I=0 mode
            if mode == 'I=0':
                self.ui.bottomPlotWidget.hide()
            else:
                self.ui.bottomPlotWidget.show()
        
            self.devStateChanged()
        
        self.mode = mode
        
    def setSignals(self, pri, sec):
        #print "setSignals", pri, sec
        for c, s in [(self.ui.primarySignalCombo, pri), (self.ui.secondarySignalCombo, sec)]:
            if s is None:
                continue
            ind = c.findText(s)
            if ind == -1:
                for i in range(c.count()):
                    print c.itemText(i)
                raise Exception('Signal "%s" does not exist' % s)
            c.setCurrentIndex(ind)
        
    def handleResult(self, result, params):
        if self.resetInpPlots:
            self.resetInpPlots = False
            self.clearInpPlots()

        ## Plot the results
        #plot = self.ui.topPlotWidget.plot(result['primary'].view(numpy.ndarray) / self.inpScale, x=result.xvals('Time'), params=params)
        plot = self.ui.topPlotWidget.plot(result['primary'].view(numpy.ndarray), x=result.xvals('Time'), params=params)
        
    def quit(self):
        TaskGui.quit(self)
        if not sip.isdeleted(self.daqUI):
            QtCore.QObject.disconnect(self.daqUI, QtCore.SIGNAL('changed'), self.daqChanged)
        self.ui.topPlotWidget.close()
        self.ui.bottomPlotWidget.close()
예제 #8
0
class DAQGenericTaskGui(TaskGui):

    #sigSequenceChanged = QtCore.Signal(object)  ## defined upstream

    def __init__(self, dev, task, ownUi=True):
        TaskGui.__init__(self, dev, task)
        self.plots = weakref.WeakValueDictionary()
        self.channels = {}

        if ownUi:
            self.ui = Ui_Form()
            self.ui.setupUi(self)
            self.stateGroup = WidgetGroup([
                (self.ui.topSplitter, 'splitter1'),
                (self.ui.controlSplitter, 'splitter2'),
                (self.ui.plotSplitter, 'splitter3'),
            ])
            self.createChannelWidgets(self.ui.controlSplitter,
                                      self.ui.plotSplitter)
            self.ui.topSplitter.setStretchFactor(0, 0)
            self.ui.topSplitter.setStretchFactor(1, 1)

        else:
            ## If ownUi is False, then the UI is created elsewhere and createChannelWidgets must be called from there too.
            self.stateGroup = None

    def createChannelWidgets(self, ctrlParent, plotParent):
        ## Create plots and control widgets
        for ch in self.dev._DGConfig:
            (w, p) = self.createChannelWidget(ch)
            plotParent.addWidget(p)
            ctrlParent.addWidget(w)

    def createChannelWidget(self, ch, daqName=None):
        conf = self.dev._DGConfig[ch]
        p = PlotWidget(self)

        units = ''
        if 'units' in conf:
            units = conf['units']

        p.setLabel('left', text=ch, units=units)
        self.plots[ch] = p

        p.registerPlot(self.dev.name() + '.' + ch)

        if conf['type'] in ['ao', 'do']:
            w = OutputChannelGui(self, ch, conf, p, self.dev, self.taskRunner,
                                 daqName)
            w.sigSequenceChanged.connect(self.sequenceChanged)
        elif conf['type'] in ['ai', 'di']:
            w = InputChannelGui(self, ch, conf, p, self.dev, self.taskRunner,
                                daqName)
        else:
            raise Exception("Unrecognized device type '%s'" % conf['type'])
        self.channels[ch] = w

        return (w, p)

    def saveState(self):
        if self.stateGroup is not None:
            state = self.stateGroup.state().copy()
        else:
            state = {}
        state['channels'] = {}
        for ch in self.channels:
            state['channels'][ch] = self.channels[ch].saveState()
        return state

    def restoreState(self, state):
        try:
            if self.stateGroup is not None:
                self.stateGroup.setState(state)
            for ch in state['channels']:
                try:
                    self.channels[ch].restoreState(state['channels'][ch])
                except KeyError:
                    printExc(
                        "Warning: Cannot restore state for channel %s.%s (channel does not exist on this device)"
                        % (self.dev.name(), ch))
                    continue
        except:
            printExc('Error while restoring GUI state:')

    def listSequence(self):
        ## returns sequence parameter names and lengths
        l = {}
        for ch in self.channels:
            chl = self.channels[ch].listSequence()
            for k in chl:
                l[ch + '.' + k] = chl[k]
        return l

    def sequenceChanged(self):
        self.sigSequenceChanged.emit(self.dev.name())

    def taskStarted(self, params):  ## automatically invoked from TaskGui
        ## Pull out parameters for this device
        params = dict([(p[1], params[p]) for p in params
                       if p[0] == self.dev.name()])

        for ch in self.channels:
            ## Extract just the parameters the channel will need
            chParams = {}
            search = ch + '.'
            for k in params:
                if k[:len(search)] == search:
                    chParams[k[len(search):]] = params[k]
            self.channels[ch].taskStarted(chParams)

    def taskSequenceStarted(self):  ## automatically invoked from TaskGui
        for ch in self.channels:
            self.channels[ch].taskSequenceStarted()

    def generateTask(self, params=None):
        if params is None:
            params = {}
        p = {}
        for ch in self.channels:
            ## Extract just the parameters the channel will need
            chParams = {}
            search = ch + '.'
            for k in params:
                if k[:len(search)] == search:
                    chParams[k[len(search):]] = params[k]
            ## request the task from the channel
            p[ch] = self.channels[ch].generateTask(chParams)
        return p

    def handleResult(self, result, params):
        if result is None:
            return
        for ch in self.channels:
            if result.hasColumn(0, ch):
                self.channels[ch].handleResult(result[ch], params)

    def getChanHolding(self, chan):
        """Return the holding value that this channel will use when the task is run."""
        return self.dev.getChanHolding(chan)

    def quit(self):
        TaskGui.quit(self)
        for ch in self.channels:
            self.channels[ch].quit()