Qudi Measurement Modules
Qudi user applications revolve around “qudi measurement modules”, namely hardware, logic and graphical user interface (GUI) modules.
⚠ WARNING:
The term “module” is a bit misleading in this context since it usually refers to a.pyfile containing one or multiple definitions and/or statements.In our case a “qudi/measurement module” refers to a non-abstract subclass ofqudi.core.Base(LogicBaseandGuiBaseare subclasses ofBaseas well) that is declared in a Python module usually located atqudi/[hardware|logic|gui]/(exception: qudi extension directories).Historical reasons… you know…
At the heart of each qudi measurement application stands a logic module. Logic modules are the “brains” of each application and are responsible for control, configuration, monitoring, data analysis and instrument orchestration to name only a few common tasks.
If an application requires hardware instrumentation you also need qudi hardware modules to provide abstracted hardware interfaces to logic modules. If you want to know more about hardware abstraction in qudi, please read the hardware interface documentation.
Having a logic module and possibly a hardware module is the bare minimum of a qudi measurement application. The logic module provides a set of methods and properties that can be considered an API for the specific measurement application, i.e. it provides full control over a certain type of measurement. You can now control the logic via an interactive IPython session, e.g. a jupyter notebook using the qudi kernel or the qudi manager GUI console.
QMainWindow instance that can use
everything Qt for Python
(PySide2) has to offer.⚠ WARNING:
GUI modules MUST NOT contain any “intelligent” code.In other words they MUST NOT facilitate any functionality that could not be achieved by using the bare logic module(s) it is connecting to.
Module Features
All qudi measurement module classes are descendants of the abstract
qudi.core.module.Base class which provides the following features:
1. Logging
log.<module>.log.debug('debug message') # ignored by logger unless running in debug mode
<module>.log.info('info message')
<module>.log.warn('warning message')
<module>.log.error('error message') # user prompt unless running in headless mode
<module>.log.critical('critical message') # user prompt unless running in headless mode and
# qudi shutdown attempt
2. Finite State Machine
A very simple finite state machine (FSM) that can be accessed via
property module_state:
FSM state diagram
The qudi module manager uses this FSM to control and monitor qudi measurement modules.
idle or locked state (i.e. if the
module is busy).<module>.module_state().3. Thread Management
Logic modules (subclass of qudi.core.module.LogicBase) will run by
default in their own thread. Hardware and GUI modules will not live in
their own threads by default. This is why it is so important to
facilitate inter-module communication mainly with Qt
Signals in order to automatically respect thread affinity.
You can access the Qt QThread object that represents the native
thread of the module via the property module_thread.
To simply check if a module is running its own thread use the bool
property is_module_threaded.
To manually alter thread affinity, you can explicitly declare
_threaded in the class body of your measurement module
implementation to be True or False to let it run in its own
thread or in the main thread, respectively, i.e.:
from qudi.core.module import Base
class MyExampleModule(Base):
""" Description goes here """
_threaded = True # or alternatively False
...
Spawning and joining threads is handled automatically by the qudi thread manager.
4. Balloon and Pop-Up Messaging
An easy way to notify the user with a message independent of the logging
facility is provided via the two utility methdos
_send_balloon_message and _send_pop_up_message.
By providing a title and message string to these methods, the user will either see a balloon message (if supported by the OS) or a pop-up message with an OK button to dismiss, respectively.
QIcon instance to customize the display duration and appearance.5. Status Variables
Status variables (qudi.core.module.StatusVar members) are
automatically dumped and loaded upon deactivation and activation of the
measurement module, respectively.
In case you want to manually issue a dump of status variables, a module
can call _dump_status_variables.
⚠ WARNING:
Please be aware that dumping status variables can potentially be slow depending on the type and size of the variables. So think carefully before using manual dumping.
See also the qudi status variable documentation.
6. Static Configuration
qudi.core.module.ConfigOption members)
one can facilitate static configuration of your measurement modules.ConfigOption meta variables are
automatically initialized from the corresponding part of the current
qudi config. > ⚠ WARNING: > > ConfigOption variables are only
initialized once at the instantiation of the module and NOT each time
the module is activated.See also the qudi configuration option documentation.
7. Measurement Module Interconnection
Connector meta object members.See also the section further below for more info.
8. Meta Information
Various read-only properties providing meta-information about the module:
property |
description |
|---|---|
|
The name given to the module by the currently loaded qudi configuration |
|
The module base type identifier string ( |
|
A unique |
|
The full path to the default module data directory. Can be overridden by module implementation. |
9. Access to qudi main instance
Each measurement module holds a (weak) reference to the
`qudi.core.application.Qudi <../404.rst>`__ singleton instance. This
object holds references to all running core facilities like the
currently loaded Configuration, the ModuleManager,
ThreadManager and the rpyc servers for remote module and IPython
kernel functionality.
⚠ WARNING:
Designing a measurement module that needs to access the qudi application singleton is generally considered bad practice. Unless you have a very specific and good reason to do so, you should never use this object in your experiment toolchains.
Inter-Module Communication
So, as you might have noticed the relationship of GUI, logic and hardware modules is hierarchical: - GUI modules control one or more logic modules but no other GUI or hardware modules - Logic modules control other logic modules and/or hardware modules but no GUI modules - Hardware modules control no other qudi modules and are just providing an interface to a specific instrument
The connection to another module is done by the
qudi.core.connector.Connector meta object. These connectors declare
the dependency of a module on another module further down the hierarchy,
i.e. it opens up a control flow path to another module.
See the qudi connectors documentation for more details on how connectors work.
Generally the control flow between modules should be signal-driven according to the Qt signal-slot principle.
A common example would be a GUI module triggering the start of a long-running logic method:
from PySide2.QtCore import Signal
from qudi.core.module import Base, LogicBase
from qudi.core.connector import Connector
# GUI module declaration in e.g. qudi/gui/my_gui_module.py
class MyGuiModule(Base):
""" Description goes here """
# Qt signal triggering the start of the measurement
sigStartMeasurement = Signal()
# Connector to get a reference to the measurement logic module
_logic_connector = Connector(interface='MyLogicModule', name='my_logic')
...
def on_activate(self):
self.sigStartMeasurement.connect(self._logic_connector().start_measurement)
self._logic_connector().sigMeasurementFinished.connect(self._measurement_finished)
def trigger_measurement_start(self):
""" Will just emit the sigStartMeasurement signal """
self.sigStartMeasurement.emit()
def _measurement_finished(self):
""" Callback for measurement finished signal from logic module """
print('Logic has finished the measurement')
...
# Logic module declaration in e.g. qudi/logic/my_logic_module.py
class MyLogicModule(LogicBase):
""" Description goes here """
# Qt signal notifying all connected "listeners" about a finished measurement
sigMeasurementFinished = Signal()
...
def start_measurement(self):
""" API method to start a measurement """
# Actually perform your measurement here and emit notification signal upon finishing
self.sigMeasurementFinished.emit()
...
trigger_measurement_start will
return immediately and cause the logic module to asynchronously start
the measurement by running start_measurement._measurement_finished callback and print the message.The same kind of control flow can be established between multiple logic modules, each running in its own thread.
from qudi.core.module import LogicBase
from qudi.core.connector import Connector
class MyLogicModule(LogicBase):
""" Description goes here """
# Connector to get a reference to the hardware module
_hardware_connector = Connector(interface='MyHardwareInterface', name='my_hardware')
...
def do_stuff_in_hardware(self):
""" Will perform some task using the connected hardware """
self._hardware_connector().do_stuff() # direct method call, no signal/slot shenanigans
...