For a complex Qt application based on
pymmcore-plus check out napari-micromanager which implements a GUI to control microscopes.
pymmcore-plus is designed seamlessly integrate with Qt GUIs. It accomplishes this in two ways:
run_mda()runs in a thread in order to not block the event loop.
eventsobject will preferentially to use QSignals instead of of signals from the psygnal library. This helps keep things from crashing when working with callbacks in multiple threads.
This example requires qtpy and an Qt backend installed in the env:
pip install qtpy superqt Pyside2 # or pyqt5
Avoiding blocking the Qt event loop#
If you make a blocking call on the thread running the Qt event loop then your GUI will become
pymmcore-plus has two options to avoid this. The recommended way is to
use threads to call
snapImage(), and let pymmcore-plus handle the threading when you use
run_mda(). If instead you want the interaction with the hardware to run in separate Python process
then you can instad use
This example will use the recommended process-local(threads) approach.
The simple application will consist of a counter that increments so long as the event loop is not blocked, and two buttons to call the
snapImage method. One button will call from a thread and the counter should continue, the other will blcok and will stop the counter.
CMMCorePlus.instance()to create the core object. This allows another script in the same process to use the same object.
Use a thread for blocking operations like
import sys from qtpy.QtCore import QTimer from qtpy.QtWidgets import ( QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget, ) from superqt.utils import create_worker from pymmcore_plus import CMMCorePlus class MainWindow(QMainWindow): def __init__(self): super().__init__() self.mmc = CMMCorePlus.instance() self.mmc.loadSystemConfiguration() self.mmc.setExposure(5000) # 5 seconds self.counter = 0 layout = QVBoxLayout() self.l = QLabel("Start") b_blocking = QPushButton("Snap blocking") b_threaded = QPushButton("Snap threaded") b_blocking.pressed.connect(self.snap_blocking) b_threaded.pressed.connect(self.snap_threaded) layout.addWidget(self.l) layout.addWidget(b_blocking) layout.addWidget(b_threaded) w = QWidget() w.setLayout(layout) self.setCentralWidget(w) self.show() self.timer = QTimer() self.timer.setInterval(1000) self.timer.timeout.connect(self._recurring_timer) self.timer.start() def _recurring_timer(self): self.counter += 1 self.l.setText("Counter: %d" % self.counter) def snap_threaded(self): # alternatively you could use the python threading module # or directly use QThreads create_worker( self._mmc.snapImage, _start_thread=True, ) def snap_blocking(self): self.mmc.snapImage() app = QApplication(sys.argv) window = MainWindow() app.exec_()