class InitializerHelper(QtCore.QObject): initializing = QtCore.Signal(object) initialized = QtCore.Signal(object) exception = QtCore.Signal(object, object) finished = QtCore.Signal(float) def __init__(self, drivers, register_finalizer, parallel, dependencies): super().__init__() self.drivers = drivers self.register_finalizer = register_finalizer self.parallel = parallel self.dependencies = dependencies def process(self): start = time.time() initialize_many(drivers=self.drivers, register_finalizer=self.register_finalizer, on_initializing=self.on_initializing, on_initialized=self.on_initialized, on_exception=self.on_exception, concurrent=self.parallel, dependencies=self.dependencies) self.finished.emit(time.time() - start) def on_initializing(self, driver): self.initializing.emit(driver) def on_initialized(self, driver): self.initialized.emit(driver) def on_exception(self, driver, ex): self.exception.emit(driver, ex)
class FeatScan(Scan): """A backend to scan a feat for a given instrument. """ #: Signal emitted before starting a new iteration #: Parameters: loop counter, step value, overrun iteration = QtCore.Signal(int, int, bool) #: Signal emitted when the loop finished. #: The parameter is used to inform if the loop was canceled. loop_done = QtCore.Signal(bool) instrument = InstrumentSlot #: Name of the scanned feat #: :type: str def __init__(self, feat_name, *args, **kwargs): super().__init__(*args, **kwargs) self.feat_name = feat_name def _pre_body(self, counter, new_value, overrun): setattr(self.instrument, self.feat_name, new_value) @property def feat_units(self): """Units of the scanned feat. """ target = self.instrument feat_name = self.feat_name feat = target.feats[feat_name] return str(feat.units)
def setupUi(self, parent): self.resize(275, 172) self.setWindowTitle('Convert units') self.layout = QtGui.QVBoxLayout(parent) self.layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) align = (QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) self.layout1 = QtGui.QHBoxLayout() self.label1 = QtGui.QLabel() self.label1.setMinimumSize(QtCore.QSize(100, 0)) self.label1.setText('Convert from:') self.label1.setAlignment(align) self.layout1.addWidget(self.label1) self.source_units = QtGui.QLineEdit() self.source_units.setReadOnly(True) self.layout1.addWidget(self.source_units) self.layout.addLayout(self.layout1) self.layout2 = QtGui.QHBoxLayout() self.label2 = QtGui.QLabel() self.label2.setMinimumSize(QtCore.QSize(100, 0)) self.label2.setText('to:') self.label2.setAlignment(align) self.layout2.addWidget(self.label2) self.destination_units = QtGui.QLineEdit() self.layout2.addWidget(self.destination_units) self.layout.addLayout(self.layout2) self.message = QtGui.QLabel() self.message.setText('') self.message.setAlignment(QtCore.Qt.AlignCenter) self.layout.addWidget(self.message) self.buttonBox = QtGui.QDialogButtonBox() self.buttonBox.setOrientation(QtCore.Qt.Horizontal) self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok) self.layout.addWidget(self.buttonBox) self.buttonBox.setEnabled(False) self.buttonBox.accepted.connect(self.accept) self.destination_units.textChanged.connect(self.check) self.setLayout(self.layout) self.destination_units.setFocus()
class Loop(Backend): #: Signal emitted before starting a new iteration #: Parameters: loop counter, iterations, overrun iteration = QtCore.Signal(int, int, bool) #: Signal emitted when the loop finished. #: The parameter is used to inform if the loop was canceled. loop_done = QtCore.Signal(bool) def __init__(self, **kwargs): super().__init__(**kwargs) self.body = None self._active = False self._internal_func = None def stop(self): self._active = False def start(self, body, interval=0, iterations=0, timeout=0): self._active = True body = body or self.body def internal(counter, overrun=False, schedule=QtCore.QTimer.singleShot): if not self._active: self.loop_done.emit(True) return st = time.time() self.iteration.emit(counter, iterations, overrun) body(counter, iterations, overrun) if iterations and counter + 1 == iterations: self.loop_done.emit(False) return elif not self._active: self.loop_done.emit(True) return sleep = interval - (time.time() - st) schedule(sleep * 1000 if sleep > 0 else 0, lambda: self._internal_func(counter + 1, sleep < 0)) self._internal_func = internal if timeout: QtCore.QTimer.singleShot(timeout * 1000, self.stop) QtCore.QTimer.singleShot(0, lambda: self._internal_func(0))
class AmplitudeScannerUi(Frontend): """A frontend for the AmplitudeScanner backend. Provides controls to select the scan range and to start and stop de scanning. """ # a declarative way to indicate the user interface file to use. # The file must be located next to the python file where this class # is defined. gui = 'amplitude_scanner.ui' # connect widgets to instruments using connect_setup automatically. auto_connect = True # amplitudes request_start = QtCore.Signal(object) def _scan(self): start, stop, steps = (getattr(self.widget, name).value() for name in ('start', 'stop', 'steps')) amplitudes = Q_(np.linspace(start, stop, steps), 'volt') self.request_start.emit(amplitudes) def connect_backend(self): # This method is called after gui has been loaded (referenced in self.widget) # and the backend is connected to the frontend (referenced in self.backend). # In this case, we use it to connect the scan button, to the scan method # and the request_start signal to the _scan_amplitude self.widget.scan_amplitudes.clicked.connect(self._scan) self.request_start.connect(self.backend._scan_amplitude)
class AmplitudeScannerShutter(Backend): """A complex application that requires a function generator, an oscilloscope and a shutter. We could subclass AmplitudeScanner, but we have chosen to embed it instead. """ # Enumerate drivers required by the backend marking them with InstrumentSlot osci = InstrumentSlot fungen = InstrumentSlot shutter = InstrumentSlot # Embedded apps (Notice we embed the backend, not the Frontend) scanner = AmplitudeScanner # This signal will be emited when new data is available. new_data = QtCore.Signal(object, object) def scan_amplitude(self, amplitudes): """Open the shutter an then for each amplitude: - scan the amplitude of the function generator. - sleeps .3 seconds. - measures the trace of the oscilloscope. - yields amplitude, data for each amplitude. Finally, close the shutter. :param amplitudes: iterable of amplitudes. """ self.shutter.opened = True # We use the embedded app to perform the actual operation. # Notice that we have not explicitly instantiated the AmplitudeScanner # nor provided the instruments. Lantz has done this in the back # connecting InstrumentsSlots by name. # i.e. self.osci is self.scanner.osci for amplitude, data in self.scanner.scan_amplitude(amplitudes): self.new_data.emit(amplitude, data) yield amplitude, data self.shutter.opened = False def _scan_amplitude(self, amplitudes): """Because scan_amplitude is a generator, this helper functions is used to iterate over all the items. """ return list(self.scan_amplitude(amplitudes)) def default_scan(self): """A linear scan from 1 to 19 Volts. """ return list(self.scan_amplitude(Q_(list(range(1, 20)), 'volt')))
class LoopOsciMeasure(Backend): """An application that measures from an Osci in a loop. """ # Enumerate drivers required by the backend marking them with InstrumentSlot osci = InstrumentSlot # Embedded apps (Notice we embed the backend, not the Frontend) loop = Loop # This signal will be emitted when new data is available. new_data = QtCore.Signal(object) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.loop.body = self.measure def measure(self, counter, iterations, overrun): data = self.osci.measure() self.new_data.emit(data)
class LoopUi(Frontend): gui = 'loop.ui' auto_connect = False request_start = QtCore.Signal(object, object, object, object) request_stop = QtCore.Signal() def connect_backend(self): super().connect_backend() self.widget.start_stop.clicked.connect(self.on_start_stop_clicked) self.widget.mode.currentIndexChanged.connect(self.on_mode_changed) self.widget.iterations.valueChanged.connect(self.recalculate) self.widget.duration.valueChanged.connect(self.recalculate) self.widget.interval.valueChanged.connect(self.recalculate) self.widget.progress_bar.setValue(0) self._ok_palette = QtGui.QPalette(self.widget.progress_bar.palette()) self._overrun_palette = QtGui.QPalette( self.widget.progress_bar.palette()) self._overrun_palette.setColor(QtGui.QPalette.Highlight, QtGui.QColor(QtCore.Qt.red)) self.backend.iteration.connect(self.on_iteration) self.backend.loop_done.connect(self.on_loop_done) self.request_start.connect(self.backend.start) self.request_stop.connect(self.backend.stop) def on_start_stop_clicked(self, value=None): if self.backend._active: self.widget.start_stop.setText('...') self.widget.start_stop.setEnabled(False) self.request_stop.emit() return self.widget.start_stop.setText('Stop') self.widget.start_stop.setChecked(True) mode = self.widget.mode.currentIndex() interval, iterations, duration = [ getattr(self.widget, name).value() for name in 'interval iterations duration'.split() ] if mode == StopMode.Continuous: self.request_start.emit(None, interval, 0, 0) elif mode == StopMode.Iterations: self.request_start.emit(None, interval, iterations, 0) elif mode == StopMode.Duration: self.request_start.emit(None, interval, 0, duration) elif mode == StopMode.IterationsTimeOut: self.request_start.emit(None, interval, iterations, duration) def recalculate(self, *args): mode = self.widget.mode.currentIndex() if mode == StopMode.Duration: iterations = self.widget.duration.value( ) / self.widget.interval.value() self.widget.iterations.setValue(math.ceil(iterations)) elif mode == StopMode.Iterations: self.widget.duration.setValue(self.widget.iterations.value() * self.widget.interval.value()) def on_iteration(self, counter, iterations, overrun): pbar = self.widget.progress_bar if not counter: if iterations: pbar.setMaximum(iterations + 1) else: pbar.setMaximum(0) if iterations: pbar.setValue(counter + 1) if overrun: pbar.setPalette(self._overrun_palette) else: pbar.setPalette(self._ok_palette) def on_mode_changed(self, new_index): if new_index == StopMode.Continuous: self.widget.duration.setEnabled(False) self.widget.iterations.setEnabled(False) elif new_index == StopMode.Duration: self.widget.duration.setEnabled(True) self.widget.iterations.setEnabled(True) self.widget.duration.setReadOnly(False) self.widget.iterations.setReadOnly(True) elif new_index == StopMode.Iterations: self.widget.duration.setEnabled(True) self.widget.iterations.setEnabled(True) self.widget.duration.setReadOnly(True) self.widget.iterations.setReadOnly(False) elif new_index == StopMode.IterationsTimeOut: self.widget.duration.setEnabled(True) self.widget.iterations.setEnabled(True) self.widget.duration.setReadOnly(False) self.widget.iterations.setReadOnly(False) self.recalculate() def on_loop_done(self, cancelled): self.widget.start_stop.setText('Start') self.widget.start_stop.setEnabled(True) self.widget.start_stop.setChecked(False) if self.widget.progress_bar.maximum(): self.widget.progress_bar.setValue( self.widget.progress_bar.maximum()) else: self.widget.progress_bar.setMaximum(1)
class Loop(Backend): """The Loop backend allows you to execute task periodically. Usage: from lantz.ui.blocks import Loop, LoopUi def measure(counter, iterations, overrun): print(counter, iterations, overrun) data = osci.measure() print(data) app = Loop() app.body = measure start_gui_app(app, LoopUi) """ #: Signal emitted before starting a new iteration #: Parameters: loop counter, iterations, overrun iteration = QtCore.Signal(int, int, bool) #: Signal emitted when the loop finished. #: The parameter is used to inform if the loop was canceled. loop_done = QtCore.Signal(bool) #: The function to be called. It requires three parameters. #: counter - the iteration number #: iterations - total number of iterations #: overrun - a boolean indicating if the time required for the operation #: is longer than the interval. #: :type: (int, int, bool) -> None body = None def __init__(self, **kwargs): super().__init__(**kwargs) self._active = False self._internal_func = None def stop(self): """Request the scanning to be stop. Will stop when the current iteration is finished. """ self._active = False def start(self, body, interval=0, iterations=0, timeout=0): """Request the scanning to be started. :param body: function to be called at each iteration. If None, the class body will be used. :param interval: interval between starts of the iteration. If the body takes too long, the iteration will be as fast as possible and the overrun flag will be True :param iterations: number of iterations :param timeout: total time in seconds that the scanning will take. If overdue, the scanning will be stopped. If 0, there is no timeout. """ self._active = True body = body or self.body def internal(counter, overrun=False, schedule=QtCore.QTimer.singleShot): if not self._active: self.loop_done.emit(True) return st = time.time() self.iteration.emit(counter, iterations, overrun) body(counter, iterations, overrun) if iterations and counter + 1 == iterations: self._active = False self.loop_done.emit(False) return elif not self._active: self.loop_done.emit(True) return sleep = interval - (time.time() - st) schedule(sleep * 1000 if sleep > 0 else 0, lambda: self._internal_func(counter + 1, sleep < 0)) self._internal_func = internal if timeout: QtCore.QTimer.singleShot(timeout * 1000, self.stop) QtCore.QTimer.singleShot(0, lambda: self._internal_func(0))
def update_progress_bar(new, old): fraction = (new.magnitude - start.value()) / (stop.value() - start.value()) progress.setValue(fraction * 100) inst.frequency_changed.connect(update_progress_bar) # <--------- New code---------> # Define a function to read the values from the widget and call scan_frequency class Scanner(QtCore.QObject): def scan(self): # Call the scan frequency scan_frequency(inst, start.value() * Hz, stop.value() * Hz, step.value() * Hz, wait.value() * sec) # When it finishes, set the progress to 100% progress.setValue(100) thread = QtCore.QThread() scanner = Scanner() scanner.moveToThread(thread) thread.start() # Connect the clicked signal of the scan button to the function scan.clicked.connect(scanner.scan) qapp.aboutToQuit.connect(thread.quit) # <--------- End of new code ---------> main.show() exit(qapp.exec_())
class Scan(Backend): """A backend that iterates over an list of values, calling a `body` function in each step. """ #: Signal emitted before starting a new iteration #: Parameters: loop counter, step value, overrun iteration = QtCore.Signal(int, int, bool) #: Signal emitted when the loop finished. #: The parameter is used to inform if the loop was canceled. loop_done = QtCore.Signal(bool) #: The function to be called. It requires three parameters. #: counter - the iteration number. #: current value - the current value of the scan. #: overrun - a boolean indicating if the time required for the operation #: is longer than the interval. #: :type: (int, int, bool) -> None body = None #: To be called before the body. Same signature as body _pre_body = None #: To be called after the body. Same signature as body _post_body = None def __init__(self, **kwargs): super().__init__(**kwargs) self._active = False self._internal_func = None def stop(self): """Request the scanning to be stop. Will stop when the current iteration is finished. """ self._active = False def start(self, body, interval=0, steps=(), timeout=0): """Request the scanning to be started. :param body: function to be called at each iteration. If None, the class body will be used. :param interval: interval between starts of the iteration. If the body takes too long, the iteration will be as fast as possible and the overrun flag will be True :param steps: iterable :param timeout: total time in seconds that the scanning will take. If overdue, the scanning will be stopped. If 0, there is no timeout. """ self._active = True body = body or self.body iterations = len(steps) def internal(counter, overrun=False, schedule=QtCore.QTimer.singleShot): if not self._active: self.loop_done.emit(True) return st = time.time() self.iteration.emit(counter, iterations, overrun) if self._pre_body is not None: self._pre_body(counter, steps[counter], overrun) if body is not None: body(counter, steps[counter], overrun) if self._post_body is not None: self._post_body(counter, steps[counter], overrun) if iterations and counter + 1 == iterations: self._active = False self.loop_done.emit(False) return elif not self._active: self.loop_done.emit(True) return sleep = interval - (time.time() - st) schedule(sleep * 1000 if sleep > 0 else 0, lambda: self._internal_func(counter + 1, sleep < 0)) self._internal_func = internal if timeout: QtCore.QTimer.singleShot(timeout * 1000, self.stop) QtCore.QTimer.singleShot(0, lambda: self._internal_func(0))
class ScanUi(Frontend): """A frontend to the Scan backend. Allows you to create linear sequence of steps between a start a stop, with selectable step size or number of steps. """ gui = 'scan.ui' auto_connect = False #: Signal emitted when a start is requested. #: The parameters are None, interval, vector of steps request_start = QtCore.Signal(object, object, object) #: Signal emitted when a stop is requested. request_stop = QtCore.Signal() def connect_backend(self): super().connect_backend() self.widget.start_stop.clicked.connect(self.on_start_stop_clicked) self.widget.mode.currentIndexChanged.connect(self.on_mode_changed) self.widget.step_count.valueChanged.connect(self.recalculate) self.widget.start.valueChanged.connect(self.recalculate) self.widget.stop.valueChanged.connect(self.recalculate) self.widget.step_size.valueChanged.connect(self.recalculate) self.widget.progress_bar.setValue(0) self._ok_palette = QtGui.QPalette(self.widget.progress_bar.palette()) self._overrun_palette = QtGui.QPalette( self.widget.progress_bar.palette()) self._overrun_palette.setColor(QtGui.QPalette.Highlight, QtGui.QColor(QtCore.Qt.red)) self.backend.iteration.connect(self.on_iteration) self.backend.loop_done.connect(self.on_loop_done) self.request_start.connect(self.backend.start) self.request_stop.connect(self.backend.stop) def on_start_stop_clicked(self, value=None): if self.backend._active: self.widget.start_stop.setText('...') self.widget.start_stop.setEnabled(False) self.request_stop.emit() return self.widget.start_stop.setText('Stop') self.widget.start_stop.setChecked(True) vals = [ getattr(self.widget, name).value() for name in 'start stop step_size step_count wait'.split() ] start, stop, step_size, step_count, interval = vals steps = list(_linspace(start, stop, step_size)) self.request_start.emit(None, interval, steps) def recalculate(self, *args): mode = self.widget.mode.currentIndex() if mode == StepsMode.step_size: step_size, length = _linspace_args(self.widget.start.value(), self.widget.stop.value(), self.widget.step_size.value()) self.widget.step_count.setValue(length) elif mode == StepsMode.step_count: step_size, length = _linspace_args( self.widget.start.value(), self.widget.stop.value(), length=self.widget.step_count.value()) self.widget.step_size.setValue(step_size) def on_iteration(self, counter, iterations, overrun): pbar = self.widget.progress_bar if not counter: if iterations: pbar.setMaximum(iterations + 1) else: pbar.setMaximum(0) if iterations: pbar.setValue(counter + 1) if overrun: pbar.setPalette(self._overrun_palette) else: pbar.setPalette(self._ok_palette) def on_mode_changed(self, new_index): if new_index == StepsMode.step_size: self.widget.step_count.setEnabled(False) self.widget.step_size.setEnabled(True) elif new_index == StepsMode.step_count: self.widget.step_count.setEnabled(True) self.widget.step_size.setEnabled(False) self.recalculate() def on_loop_done(self, cancelled): self.widget.start_stop.setText('Start') self.widget.start_stop.setEnabled(True) self.widget.start_stop.setChecked(False) if self.widget.progress_bar.maximum(): self.widget.progress_bar.setValue( self.widget.progress_bar.maximum()) else: self.widget.progress_bar.setMaximum(1)
def initialize_and_report(widget, drivers, register_finalizer=True, initializing_msg='Initializing ...', initialized_msg='Initialized', concurrent=True, dependencies=None): """Initialize drivers while reporting the status in a QtWidget. :param widget: Qt Widget where the status information is going to be shown. :param drivers: iterable of drivers to initialize. :param register_finalizer: register driver.finalize method to be called at python exit. :param initializing_msg: message to be displayed while initializing. :param initialized_msg: message to be displayed after successful initialization. :param concurrent: indicates that drivers with satisfied dependencies should be initialized concurrently. :param dependencies: indicates which drivers depend on others to be initialized. each key is a driver name, and the corresponding value is an iterable with its dependencies. :return: the QThread doing the initialization. """ timing = {} thread = QtCore.QThread() helper = InitializerHelper(drivers, register_finalizer, concurrent, dependencies) helper.moveToThread(thread) thread.helper = helper if isinstance(widget, QtGui.QTableWidget): def _initializing(driver): timing[driver] = time.time() row = drivers.index(driver) widget.setItem(row, 2, QtGui.QTableWidgetItem(initializing_msg)) def _initialized(driver): delta = time.time() - timing[driver] row = drivers.index(driver) widget.setItem( row, 2, QtGui.QTableWidgetItem(initialized_msg + ' ({:.1f} sec)'.format(delta))) def _exception(driver, e): delta = time.time() - timing[driver] row = drivers.index(driver) widget.setItem( row, 2, QtGui.QTableWidgetItem('{} ({:.1f} sec)'.format(e, delta))) def _done(duration): widget.setItem( len(drivers), 2, QtGui.QTableWidgetItem('{:.1f} sec'.format(duration))) thread.quit() widget.clearContents() widget.setRowCount(len(drivers) + 1) for row, driver in enumerate(drivers): widget.setItem(row, 0, QtGui.QTableWidgetItem(driver.name)) widget.setItem(row, 1, QtGui.QTableWidgetItem(driver.__class__.__name__)) widget.setItem(row, 2, QtGui.QTableWidgetItem('')) widget.resizeColumnToContents(0) widget.horizontalHeader().setStretchLastSection(True) elif isinstance(widget, QtGui.QLineEdit): def _initializing(driver): timing[driver] = time.time() widget.setText('{} ({}) > {}'.format(driver.name, driver.__class__.__name__, initializing_msg)) def _initialized(driver): delta = time.time() - timing[driver] widget.setText('{} ({}) > {} ({:.1f} sec)'.format( driver.name, driver.__class__.__name__, initialized_msg, delta)) def _exception(driver, e): delta = time.time() - timing[driver] widget.setText('{} ({}) > {} ({:.1f} sec)'.format( driver.name, driver.__class__.__name__, e, delta)) def _done(duration): widget.setText('Initialized in {:.1f} sec'.format(duration)) thread.quit() widget.setReadOnly(True) elif isinstance(widget, QtGui.QTextEdit): def _initializing(driver): timing[driver] = time.time() widget.append('{} ({}) > {}'.format(driver.name, driver.__class__.__name__, initializing_msg)) def _initialized(driver): delta = time.time() - timing[driver] widget.append('{} ({}) > {} ({:.1f} sec)'.format( driver.name, driver.__class__.__name__, initialized_msg, delta)) def _exception(driver, e): delta = time.time() - timing[driver] widget.append('{} ({}) > {} ({:.1f} sec)'.format( driver.name, driver.__class__.__name__, e, delta)) def _done(duration): widget.append('Initialized in {:.1f} sec'.format(duration)) thread.quit() widget.setReadOnly(True) else: raise TypeError('Unknown widget type {}.'.format(type(widget))) thread.started.connect(helper.process) helper.initializing.connect(_initializing) helper.initialized.connect(_initialized) helper.exception.connect(_exception) helper.finished.connect(_done) thread.start() return thread