6.3. How a Dashboard extension works
This page follows an extension from the moment it is started to the moment it quits, with the objects involved. It is
what the core extensions (DAQ Scan, DAQ_Logger, PID, data mixer, sequencer, ramping…) do, and an
extension of a plugin package works exactly the same way, the only differences being the EXTENSION_NAME and
CLASS_NAME that make it discoverable (see How an extension is found). Read CustomApp and CustomExt first for the base
classes, CustomApp and CustomExt.
Each diagram is followed by its text version.
6.3.1. Starting an extension
There are two ways to start the same extension class:
from the Dashboard: the user picks it in the Extensions menu, the Dashboard builds the extension window and the extension object;
on its own, with the
main()function of the extension module: it builds a Dashboard (hidden) and does the same thing.
Fig. 6.8 Startup of an extension with its main() function
Text version:
mkQAppcreates the Qt application.load_dashboard_with_arguments(show_dashboard=False, load_extension=False)reads the command line (-xthe experiment to load,-sthe state to apply), builds the Dashboard, and applies the experiment if given.The Dashboard window is hidden: the user works in the extension window and can show the Dashboard from the Dashboard toolbar of the extension (see Experiment, State and the Dashboard toolbar in a CustomExt).
create_extension(dashboard, MyExtension, show_extension=True)creates the window and itsDockArea, instantiatesMyExtension(dockarea, dashboard), adds the toolbars of the extension to the window and shows it. It already shows the window: do not callshow()again.app.exec()runs the Qt event loop; the program ends when the window is closed.
The main() function of an extension module is therefore always the same:
def main():
import sys
from pymodaq_gui.qt_utils import mkQApp
from pymodaq.dashboard import load_dashboard_with_arguments
from pymodaq.utils.gui_utils.loader_utils import create_extension
app = mkQApp('My Extension')
win, dashboard, _ = load_dashboard_with_arguments(show_dashboard=False, load_extension=False)
win.mainwindow.setVisible(False)
win_ext, ext = create_extension(dashboard, MyExtension, show_extension=True)
sys.exit(app.exec())
if __name__ == '__main__':
main()
When started from the Dashboard instead, DashBoard.load_extension calls the same create_extension, keeps the
extension in dashboard.extensions, checks the Show Dashboard action of its toolbar and connects the
status_signal of the extension to the status display of the Dashboard.
6.3.2. Life of the extension object
Fig. 6.9 Life of a CustomExt object
Text version:
__init__calls the base__init__with(parent, dashboard), which creates the settings tree, theH5Manager, theWorkerThreadManagerand theModulesManagerof the extension, then callsself.setup_ui().setup_uibuilds the user interface by calling the methods listed in the table of Lifecycle.do_things_after_experiment_setis called once at the end ofCustomExt.__init__if an experiment is already applied, and each time the user applies another experiment. This is where the extension adapts to the instruments that are available (see Experiment, State and the Dashboard toolbar in a CustomExt).While running, the extension reacts to the user (
value_changedof the settings tree, actions) and to the signals of the control modules. Long work (a scan, a sequence, a ramp) is done by anExtensionWorker(see Supporting objects: threads, files and signals).When the window is closed, the Dashboard or the window calls
quit_fun, which calls your_quit_fun. ReturnFalse(after telling the user) while the extension is running to refuse to quit, andTrueotherwise: the base implementation returnsNone, which prevents the extension from closing. The basequit_funthen exits the worker threads, closes the window and shows the Dashboard again.
6.3.3. What runs where
All the code of an extension (the methods above, the slots connected to actions and to the signals of the control
modules) runs in the GUI thread. The hardware is never reached directly: the extension talks to the control modules
(DAQ_Move and DAQ_Viewer) found in self.modules_manager, which run the instrument plugins in their own
threads, and reacts to their signals (data grabbed, move done…). Never poll or sleep in the GUI thread. Work that is
long but is not instrument access, such as saving, is done in a worker thread (ExtensionWorker and SaverWorker,
see Supporting objects: threads, files and signals).
Fig. 6.10 Threads of an extension
Text version: the extension, the ModulesManager and the control modules live in the GUI thread. Each control module
exchanges commands and data with its instrument plugin, which runs in its own thread, only through signals. The
extension connects to the signals of the control modules (grab_done_signal of a detector, move_done_signal of an
actuator) to get the results, and sends the data to save to a SaverWorker thread with data_to_save_signal.
6.3.4. How an extension is found
Fig. 6.11 Discovery of the extensions
Text version:
The
pyproject.tomlof the plugin package declares the entry point grouppymodaq.extensions(created by theextensions = truefeature flag, see Extension Plugins).pymodaq.extensions.utils.get_extensionsimports each package declared in this group and every module of itsextensionsfolder. A module that has anEXTENSION_NAMEattribute defines an extension:CLASS_NAMEis the name of the class (deriving fromCustomExt) found in the module.The extensions found, those of PyMoDAQ included, are listed in the
ExtensionEnum; the Dashboard adds one action per extension in its Extensions menu (enabled once an experiment is applied), which callsDashBoard.load_extension.A module that cannot be imported is skipped with a warning in the log (check it if your extension is missing).
EXTENSION_NAME is the label displayed in the menu and CLASS_NAME the name of the class. With these two attributes
and an entry point, nothing else is needed for the Dashboard to offer the extension.
6.3.5. A complete small example
The package pymodaq_plugins_mockexamples contains the
smallest useful extension, Move and Grab (extensions/move_and_grab.py, about 130 lines), to start from. With the mock
instruments of PyMoDAQ it shows, in order:
the module variables
EXTENSION_NAMEandCLASS_NAME, and themain()function of this page;the lifecycle methods of Lifecycle, and the Dashboard toolbar of Experiment, State and the Dashboard toolbar in a CustomExt;
do_things_after_experiment_setrefreshing the lists of actuators and detectors;the instruments used through
self.modules_manager(move_actuators_with_callback,grab_data_with_callbackandforget_callback): the next step is done in a callback when the control module signals the end of the previous one, never by waiting in the GUI thread;a position with units (
DataActuator), data asDataToExport, shown in aViewerDispatcherand saved withDataToExportSaverin the file of theH5Manager;a clean shutdown: a timeout handler so that the extension is never stuck busy, and
_quit_funrefusing to quit while a move or a grab is going on.
Its tests (tests/test_move_and_grab.py) show how to test an extension against a Dashboard holding mock instruments.