class PiStageUi(QSplitter): stageConnected = Signal( ) # gets emitted if stage was sucessfully connected stopScan = Signal() xAxeChanged = Signal(object, object) updateCurrPos = Signal(object) def __init__(self, parent): #super(ObjectFT, self).__init__(Qt.Vertical, parent) super().__init__(parent) self.stage = None self.offset = 0. # offset from 0 where t0 is (mm) self.newOff = 0. self.stageRange = (0, 0) layoutWidget = QWidget() layout = QGridLayout() layoutWidget.setLayout(layout) # put layout together self.openStageBtn = QPushButton("Open stage") self.initStageBtn = QPushButton("Init stage") #absolute move #current position self.currentPos = QLabel('') #self.currentPos.setValidator(QDoubleValidator()) #relative move (mm) self.deltaMove_mm = QLineEdit() self.deltaMove_mm.setText('0') self.deltaMove_mm.setValidator(QDoubleValidator()) self.deltaMovePlus_mm = QPushButton('+') self.deltaMoveMinus_mm = QPushButton('-') #relative move (fs) self.deltaMove_fs = QLineEdit() self.deltaMovePlus_fs = QPushButton('+') self.deltaMoveMinus_fs = QPushButton('-') #velocity self.velocityLabel = QLabel('Velocity:') self.velocity = QSlider(Qt.Horizontal) self.velocity.setMinimum(0) self.velocity.setMaximum( 2000) # unit in µm; TODO: try to get max vel. from controller # scan from (fs) self.scanFrom = QLineEdit() self.scanFrom.setText('-100') self.scanFrom.setValidator(QIntValidator()) # scan to (fs) self.scanTo = QLineEdit() self.scanTo.setText('100') self.scanTo.setValidator(QIntValidator()) # scan stepsize (fs) self.scanStep = QLineEdit() self.scanStep.setText('10') self.scanStep.setValidator(QDoubleValidator()) # center here button self.centerBtn = QPushButton('Center here') self.centerBtn.setToolTip('Center scan at current stage position') self.startScanBtn = QPushButton("Start scan") self.stopScanBtn = QPushButton("Stop scan") self.niceBtn = QPushButton('Make it nice') # spacer line hLine = QFrame() hLine.setFrameStyle(QFrame.HLine) hLine.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) # put layout together layout.addWidget(self.openStageBtn, 0, 0) layout.addWidget(self.initStageBtn, 0, 1) layout.addWidget(QLabel("Current pos (mm):"), 1, 0) layout.addWidget(self.currentPos, 1, 1) layout.addWidget(self.velocityLabel, 2, 0) layout.addWidget(self.velocity, 3, 0, 1, 2) layout.addWidget(QLabel('Move relative (mm)'), 4, 0) layout.addWidget(self.deltaMove_mm, 5, 0, 1, 2) layout.addWidget(self.deltaMoveMinus_mm, 6, 0) layout.addWidget(self.deltaMovePlus_mm, 6, 1) layout.addWidget(QLabel('Move relative (fs)'), 7, 0) layout.addWidget(self.deltaMove_fs, 8, 0, 1, 2) layout.addWidget(self.deltaMoveMinus_fs, 9, 0) layout.addWidget(self.deltaMovePlus_fs, 9, 1) layout.addWidget(hLine, 10, 0, 1, 2) layout.addWidget(QLabel('Scan from (fs)'), 11, 0) layout.addWidget(self.scanFrom, 11, 1) layout.addWidget(QLabel('Scan to (fs)'), 12, 0) layout.addWidget(self.scanTo, 12, 1) layout.addWidget(QLabel('Stepsize (fs)'), 13, 0) layout.addWidget(self.scanStep, 13, 1) layout.addWidget(self.startScanBtn, 14, 0) layout.addWidget(self.stopScanBtn, 14, 1) layout.addWidget(self.centerBtn, 15, 1) layout.addWidget(self.niceBtn, 16, 1) layout.setRowStretch(17, 10) layout.setColumnStretch(2, 10) self.addWidget(layoutWidget) # make button and stuff functional self.openStageBtn.released.connect(self.connectStage) self.initStageBtn.released.connect(self.initStage) self.scanFrom.returnPressed.connect(self._xAxeChanged) self.scanTo.returnPressed.connect(self._xAxeChanged) self.centerBtn.released.connect(self._centerHere) self.deltaMovePlus_mm.released.connect( lambda x=1: self.moveRel_mm(float(self.deltaMove_mm.text()))) self.deltaMoveMinus_mm.released.connect( lambda x=-1: self.moveRel_mm(float(self.deltaMove_mm.text()), x)) ################ # thread for updating position #self.currPosThr = GenericThread(self.__getCurrPos) self.updateCurrPos.connect(self.__updateCurrPos) self.currPos_thread = QThread() # create the QThread self.currPos_thread.start() # This causes my_worker.run() to eventually execute in my_thread: self.currPos_worker = GenericWorker(self.__getCurrPos) self.currPos_worker.moveToThread(self.currPos_thread) # my_worker.finished.connect(self.xxx) #self.threadPool.append(my_thread) #self.my_worker = my_worker def connectStage(self): gcs = GCSDevice() try: gcs.InterfaceSetupDlg() msg = QMessageBox() msg.setIcon(QMessageBox.Information) msg.setText(gcs.qIDN()) msg.exec_() self.stage = gcs self.openStageBtn.setEnabled(False) except: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText('Could not connect stage') msg.exec_() def initStage(self): # TODO put this in thread and show egg clock if self.stage is not None: ## Create and display the splash screen #splash_pix = QPixmap('icons/piController.png') #splash = QSplashScreen(splash_pix, Qt.WindowStaysOnTopHint) #splash.setMask(splash_pix.mask()) #splash.show() # TODO: give choice to select stage pitools.startup(self.stage, stages='M-112.1DG-NEW', refmode='FNL') #splash.close() # TODO: show dialog for waiting self.velocityLabel.setText('Velocity: {:f}mm/s'.format( self.stage.qVEL()['1'])) self.velocity.setValue(int(1000 * self.stage.qVEL()['1'])) self.stageConnected.emit() self._xAxeChanged() self.currentPos.setText('{:.7f}'.format(self.stage.qPOS()['1'])) self.__startCurrPosThr() self.stageRange = (self.stage.qTMN()['1'], self.stage.qTMX()['1']) self.scanStep.validator().setBottom(0) self.initStageBtn.setEnabled(False) else: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText('No stage connected') msg.exec_() def gotoPos_mm(self, x): '''Move stage to absolute position in mm''' if self.stageRange[0] <= x <= self.stageRange[1]: self.stage.MOV(self.stage.axes, x) while not pitools.ontarget(self.stage, '1')['1']: time.sleep(0.05) else: print('Requested postition', x, 'outside of range', self.stageRange) def gotoPos_fs(self, x): '''Move stage to absolute position in mm''' self.gotoPos_mm(self._calcAbsPos(x)) def moveRel_mm(self, x=0, sign=1): '''Moves stage relative to current position''' # TODO raise message if outside of range currPos = float(self.currentPos.text()) if self.stageRange[0] <= sign * x + currPos <= self.stageRange[1]: self.stage.MVR(self.stage.axes, sign * x) else: print('Requested postition', x, 'outside of range', self.stageRange) def moveRel_fs(self, x=0, sign=1): '''Moves stage relative to current position; expexts fs''' # TODO raise message if outside of range self.moveRel_mm(self._calcAbsPos(x), sign) def _calcAbsPos(self, x): '''Calculate absolute position on stage from given femtosecond value gets x in fs and returns position mm''' return (x * fsDelay) + self.offset def getDelays_mm(self): '''expects fs and returns mm''' von = self._calcAbsPos(float(self.scanFrom.text())) bis = self._calcAbsPos(float(self.scanTo.text())) #stepSize = int(self.scanStep.text()) stepSize = float(self.scanStep.text()) * fsDelay return np.linspace(von, bis, (np.abs(von) + bis) / stepSize) def getDelays_fs(self): '''expects fs and returns mm''' von = float(self.scanFrom.text()) bis = float(self.scanTo.text()) #stepSize = int(self.scanStep.text()) stepSize = float(self.scanStep.text()) return np.linspace(von, bis, (np.abs(von) + bis) / stepSize) def _xAxeChanged(self): self.xAxeChanged.emit(int(self.scanFrom.text()), int(self.scanTo.text())) def setCenter(self): '''Slot which recieves the new center position in fs and sets offset in mm ''' if self.newOff != 0: self.offset += (self.newOff * fsDelay) print('offset', self.offset, self.newOff) self.newOff = 0. def newOffset(self, newOffset): self.newOff = newOffset def _centerHere(self): self.offset = self.stage.qPOS()['1'] def __startCurrPosThr(self): self.stopCurrPosThr = False #self.currPosThr.start() self.currPos_worker.start.emit() def __stopCurrPosThr(self): self.stopCurrPosThr = True while (self.currPosThr.isRunning()): time.sleep(0.03) def __getCurrPos(self): oldPos = self.stage.qPOS()['1'] while not self.stopCurrPosThr: newPos = self.stage.qPOS()['1'] if oldPos != newPos: oldPos = newPos self.updateCurrPos.emit(newPos) time.sleep(0.5) def __updateCurrPos(self, newPos): self.currentPos.setText('{:.7f}'.format(newPos))
class TiePieUi(QSplitter): ''' Handling user interface to manage TiePie HS4/Diff Oscilloscope ''' scpConnected = Signal() xAxeChanged = Signal(object, object) yAxeChanged = Signal(object, object) triggLevelChanged = Signal(object) def __init__(self, parent): #super(ObjectFT, self).__init__(Qt.Vertical, parent) super().__init__(parent) self.scp = None # variable to hold oscilloscope object self.mutex = QMutex() layoutWidget = QWidget() layout = QGridLayout() layoutWidget.setLayout(layout) self.openDevBtn = QPushButton('Open Osci') # channel stuff self.measCh = QComboBox() self.chSens = QComboBox() self.triggCh = QComboBox() self.frequency = QLineEdit() self.frequency.setValidator(QIntValidator()) self.recordLen = QLineEdit() self.recordLen.setValidator(QIntValidator()) self.delay = QLineEdit() self.delay.setValidator(QDoubleValidator()) # trigger stuff self.triggLevel = QLineEdit() self.triggLevel.setToolTip( 'http://api.tiepie.com/libtiepie/0.5/triggering_scpch.html#triggering_scpch_level' ) self.triggLevel.setText( '0.5' ) # init value otherwise there's trouble with signal changing index of sensitivity self.triggLevel.setValidator(QDoubleValidator(0., 1., 3)) self.hystereses = QLineEdit() self.hystereses.setText('0.05') self.hystereses.setToolTip( 'http://api.tiepie.com/libtiepie/0.5/triggering_scpch.html#triggering_scpch_hysteresis' ) self.hystereses.setValidator(QDoubleValidator(0., 1., 3)) self.triggKind = QComboBox() # do averages self.averages = QSpinBox() self.averages.setValue(1) self.averages.setRange(1, 10000) # put layout together layout.addWidget(self.openDevBtn, 0, 0) layout.addWidget(QLabel('Measuring Ch'), 1, 0) layout.addWidget(self.measCh, 1, 1) layout.addWidget(QLabel('Ch sensitivity'), 2, 0) layout.addWidget(self.chSens, 2, 1) layout.addWidget(QLabel('Sample freq. (kHz)'), 3, 0) layout.addWidget(self.frequency, 3, 1) layout.addWidget(QLabel('Record length'), 4, 0) layout.addWidget(self.recordLen, 4, 1) layout.addWidget(QLabel('Delay'), 5, 0) layout.addWidget(self.delay, 5, 1) layout.addWidget(QLabel('Trigger Ch'), 6, 0) layout.addWidget(self.triggCh, 6, 1) layout.addWidget(QLabel('Trigger Level (%)'), 7, 0) layout.addWidget(self.triggLevel, 7, 1) layout.addWidget(QLabel('Hystereses'), 8, 0) layout.addWidget(self.hystereses, 8, 1) layout.addWidget(QLabel('Trigger kind'), 9, 0) layout.addWidget(self.triggKind, 9, 1) layout.addWidget(QLabel('Averages'), 10, 0) layout.addWidget(self.averages, 10, 1) layout.setRowStretch(11, 10) layout.setColumnStretch(2, 10) self.addWidget(layoutWidget) # connect UI to get things working self.openDevBtn.released.connect(self.openDev) self.chSens.currentIndexChanged.connect(self._changeSens) self.frequency.returnPressed.connect(self._changeFreq) self.recordLen.returnPressed.connect(self._changeRecordLength) self.triggCh.currentIndexChanged.connect(self._changeTrigCh) self.triggLevel.returnPressed.connect(self._triggLevelChanged) self.triggLevel.textChanged.connect(self._check_state) self.hystereses.returnPressed.connect(self._setHystereses) self.hystereses.textChanged.connect(self._check_state) def openDev(self): # search for devices libtiepie.device_list.update() # try to open an oscilloscope with block measurement support for item in libtiepie.device_list: if item.can_open(libtiepie.DEVICETYPE_OSCILLOSCOPE): self.scp = item.open_oscilloscope() if self.scp.measure_modes & libtiepie.MM_BLOCK: break else: self.scp = None # init UI #print(self.scp.name, 'found') if self.scp is not None: # Set measure mode: self.scp.measure_mode = libtiepie.MM_BLOCK # Set sample frequency: self.scp.sample_frequency = 1e6 # 1 MHz # Set record length: self.scp.record_length = 10000 # 10000 samples # Set pre sample ratio: self.scp.pre_sample_ratio = 0 # 0 % # Set trigger timeout: self.scp.trigger_time_out = 100e-3 # 100 ms # Enable channel 1 for measurement # http://api.tiepie.com/libtiepie/0.5/group__scp__ch__enabled.html self.scp.channels[ 0].enabled = True # by default all channels are enabled self.scp.range = 0.2 self.scp.coupling = libtiepie.CK_DCV # DC Volt # Disable all channel trigger sources for ch in self.scp.channels: ch.trigger.enabled = False # Setup channel trigger on 1 ch = self.scp.channels[0] ch.trigger.enabled = True ch.trigger.kind = libtiepie.TK_RISINGEDGE ch.trigger.levels[0] = 0.5 # 50% ch.trigger.hystereses[0] = 0.05 # 5% # update UI # channel self.measCh.addItems( ['Ch{:d}'.format(i) for i in range(self.scp.channels.count)]) self.chSens.addItems( ['{:.1f} V'.format(i) for i in self.scp.channels[0].ranges]) self.frequency.setValidator( QIntValidator(1, 1e-3 * self.scp.sample_frequency_max)) self.frequency.setText('{:d}'.format( int(self.scp.sample_frequency * 1e-3))) self.recordLen.setValidator( QIntValidator(1, self.scp.record_length_max)) self.recordLen.setText('{:d}'.format(self.scp.record_length)) # trigger self.triggCh.addItems( ['Ch{:d}'.format(i) for i in range(self.scp.channels.count)]) # TODO: doen't work in module anymore!! #self.triggLevel.setText(str(ch.trigger.levels[0])) #self.hystereses.setText(str(ch.trigger.hystereses[0])) self.triggKind.addItems([ '{:s}'.format(i) for i in libtiepie.trigger_kind_str( ch.trigger.kinds).split(', ') ]) self.openDevBtn.setEnabled(False) # tell the world that the scope is connected self.xAxeChanged.emit( 0, 1 / int(self.frequency.text()) * 1e-3 * int(self.recordLen.text())) self.yAxeChanged.emit(-1 * self.scp.range, self.scp.range) self.triggLevelChanged.emit(ch.trigger.levels[0] * 2 * self.scp.range - self.scp.range) self.scpConnected.emit() else: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText('No supported device found') msg.exec_() def getData(self): # function called thread for updating plot avg = int(self.averages.text()) with QMutexLocker(self.mutex): x = np.linspace( 0, 1 / self.scp.sample_frequency * self.scp.record_length, self.scp.record_length) y = np.zeros((avg, self.scp.record_length)) for i in range(avg): self.scp.start() while not self.scp.is_data_ready: time.sleep(0.01) y[i, :] = self.scp.get_data()[self.measCh.currentIndex()] return np.column_stack((x, y.mean(axis=0))) #@Slot def _changeSens(self, i): with QMutexLocker(self.mutex): yMax = self.scp.channels[0].ranges[i] self.scp.range = yMax self.yAxeChanged.emit(-1 * yMax, yMax) self.triggLevelChanged.emit( float(self.triggLevel.text()) * 2 * yMax - yMax) def _changeTrigCh(self, newTrig): print('new trigger channel', newTrig) scope = self.scp with QMutexLocker(self.mutex): # Disable all channel trigger sources for ch in scope.channels: ch.trigger.enabled = False # enable trigger on newly selected channel ch = scope.channels[newTrig] ch.trigger.enabled = True ch.trigger.kind = libtiepie.TK_RISINGEDGE ch.trigger.levels[0] = float(self.triggLevel.text()) ch.trigger.hystereses[0] = float(self.hystereses.text()) def _triggLevelChanged(self): with QMutexLocker(self.mutex): idx = self.triggCh.currentIndex() ch = self.scp.channels[idx] ch.trigger.levels[0] = float(self.triggLevel.text()) self.triggLevelChanged.emit( float(self.triggLevel.text()) * 2 * self.scp.range - self.scp.range) def _changeFreq(self): with QMutexLocker(self.mutex): self.scp.sample_frequency = int(self.frequency.text()) * 1e3 self.xAxeChanged.emit( 0, 1 / self.scp.sample_frequency * self.scp.record_length) def _changeRecordLength(self): with QMutexLocker(self.mutex): self.scp.record_length = int(self.recordLen.text()) self.xAxeChanged.emit( 0, 1 / self.scp.sample_frequency * self.scp.record_length) def _setHystereses(self): with QMutexLocker(self.mutex): self.scp.hystereses = float(self.hystereses.text()) def _check_state(self, *args, **kwargs): '''https://snorfalorpagus.net/blog/2014/08/09/validating-user-input-in-pyqt4-using-qvalidator/''' sender = self.sender() validator = sender.validator() state = validator.validate(sender.text(), 0)[0] if state == QValidator.Acceptable: color = '#FFFFFF' # green elif state == QValidator.Intermediate: color = '#fff79a' # yellow else: color = '#f6989d' # red sender.setStyleSheet('QLineEdit { background-color: %s }' % color)
class MaestroUi(QSplitter): connected = Signal() # gets emitted if stage was sucessfully connected newPlotData = Signal(object) updateAvgTxt = Signal(object) def __init__(self, parent): #super(ObjectFT, self).__init__(Qt.Vertical, parent) super().__init__(parent) self.meter = None self.collectData = True # bool for data collection thread self.avgData = Queue() # need data for averaging and set for holding all self.measure = False self.runDataThr = True self.measureData = [] self.startTime = None layoutWidget = QWidget() layout = QGridLayout() layoutWidget.setLayout(layout) ############## # gui elements self.ipEdit = QLineEdit() rx = QRegExp("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") self.ipEdit.setValidator(QRegExpValidator(rx)) self.ipEdit.setText('127.0.0.1') self.portEdit = QLineEdit() self.portEdit.setValidator(QIntValidator(1, 65535, self)) self.portEdit.setText('5000') self.connectBtn = QPushButton('Connect') self.avgSpin = QSpinBox() self.avgSpin.setValue(1) self.avgSpin.setRange(1, 10000) self.currValDisp = QLabel('0.0') self.startMeasBtn = QPushButton('Start aq') self.stopMeasBtn = QPushButton('Stop aq') ############## # put layout together layout.addWidget(QLabel('IP Address:'), 0, 0) layout.addWidget(self.ipEdit, 1, 0) layout.addWidget(QLabel('Port:'), 0, 1) layout.addWidget(self.portEdit, 1, 1) layout.addWidget(self.connectBtn, 2, 1) layout.addWidget(QLabel('Averages'), 4, 0) layout.addWidget(self.avgSpin, 5, 0) layout.addWidget(self.currValDisp, 5, 1) layout.addWidget(self.startMeasBtn, 6, 0) layout.addWidget(self.stopMeasBtn, 6, 1) layout.setRowStretch(7, 10) self.addWidget(layoutWidget) ############## # Network stuff self.tcpClient = QtNetwork.QTcpSocket() self.tcpClient.readyRead.connect(self.__getSocketData) self.tcpClient.error.connect(lambda x: print(x)) ############## # make button and stuff functional self.connectBtn.released.connect(self.connectMeter) self.avgSpin.valueChanged.connect(self.changeAverage) self.startMeasBtn.released.connect(self._startMeasure) self.stopMeasBtn.released.connect(self._stopMeasure) ############## # thread for getting data from socket self.updateAvgTxt.connect(self.__updateAvgTxt) self.dataAq_Thr = QThread() self.dataAq_Thr.start() self.dataAq_worker = GenericWorker(self.__getData) self.dataAq_worker.moveToThread(self.dataAq_Thr) def connectMeter(self): print('connected') self.tcpClient.connectToHost(self.ipEdit.text(), int(self.portEdit.text())) self.tcpClient.write('start\n'.encode()) self.dataAq_worker.start.emit() def _startMeasure(self): self.measure = True self.measureData = [] # reinitialize measure data array time.sleep(0.1) # time to wait for first data to arrive self.startTime = datetime.now() # datetime object def _stopMeasure(self): self.measure = False #@Slot def __updateAvgTxt(self, text): ''' update current value label ''' self.currValDisp.setText(text) def changeAverage(self): shape = int(self.avgSpin.value()) self.dispData = np.zeros(shape) def __getData(self): ''' Function run in thread ''' while self.runDataThr: tmpData = np.array(int(self.avgSpin.text())*[[datetime.now(), 0]]) for i in range(len(tmpData)): tmpData[i] = self.avgData.get() if self.measure: self.measureData.append( (tmpData[i,0].isoformat().encode(), (tmpData[i,0]-self.startTime).total_seconds(), tmpData[i,1])) #print('mean', tmpData.mean()) self.updateAvgTxt.emit(str(tmpData[:,1].mean())) if self.measure: self.newPlotData.emit(np.float_(np.asarray(self.measureData)[:,1:])) self.avgData.task_done() #@Slot() def __getSocketData(self): ''' to be called if network buffer has more data push data to queue ''' self.avgData.put([datetime.now(), float(self.tcpClient.readLine(1024).decode().rstrip())]) def closeEvent(self, event): if self.tcpClient.isOpen(): self.RunDataThr = False self.tcpClient.write('stop\n'.encode()) time.sleep(0.1) self.tcpClient.close() print(self.tcpClient.isOpen()) #if self.console is not None: # self.console.exit_interpreter() event.accept()