Usage

Starting the application

After installation, launch the application from the command line:

stoner-measurement

Or from Python:

from stoner_measurement.main import main
main()

Application layout

The main window is split into three panels:

  • Left panel (25 %) — Instrument / plugin list and sequence builder. Drag instruments into the sequence list to build a measurement sequence.

  • Central panel (50 %) — Live PyQtGraph plotting area. Data points produced by each sequence step are plotted here in real time.

  • Right panel (25 %) — Tabbed configuration area. Each loaded plugin contributes a tab with its own configuration controls.

Building and running a sequence

  1. Select an instrument in the left panel and click Add Step.

  2. Repeat for each step you need.

  3. Configure each step via the corresponding tab in the right panel.

  4. Click Run to start the sequence.

Writing a plugin

All measurement plugins inherit from BasePlugin. Choose the appropriate subclass for your plugin type and register it via the stoner_measurement.plugins entry-point group in your package’s pyproject.toml:

[project.entry-points."stoner_measurement.plugins"]
my_instrument = "my_package.my_plugin:MyPlugin"

Plugin types

Measurement trace plugin — subclass TracePlugin:

  • Required: name and execute() — a generator that yields (x, y) tuples for each measured point.

  • Optionally override connect(), configure(), and disconnect() to manage hardware connections.

State-control plugin — subclass StateControlPlugin:

  • Required: name, state_name, units, set_state(), get_state(), and is_at_target().

  • The sequence engine drives this plugin over a scan defined by scan_generator. Other steps can be nested beneath it in the sequence tree.

Monitor plugin — subclass MonitorPlugin:

  • Required: name, quantity_names, units, and read().

Transform plugin — subclass TransformPlugin:

  • Required: name, required_inputs, output_names, and transform().

Minimal example

The following shows the minimum required implementation for a trace plugin:

from stoner_measurement.plugins.trace import TracePlugin

class ThermometerPlugin(TracePlugin):
    @property
    def name(self):
        return "Thermometer"

    def execute(self, parameters):
        for reading in self._hardware.read(parameters.get("samples", 10)):
            yield reading.time, reading.temperature

Optional UI integration

Plugins can hook into the main window UI by overriding any of the following methods.

config_tabs(parent=None) list[tuple[str, QWidget]]

Returns a list of (tab_title, widget) pairs. Each pair becomes one tab in the right-hand configuration panel.

The default implementation wraps config_widget() in a single-element list using name as the tab title. Override config_tabs() directly when a plugin needs more than one tab or a custom tab title.

def config_tabs(self, parent=None):
    settings = self.config_widget(parent=parent)
    about    = QLabel("My plugin v1.0", parent)
    return [
        ("MyPlugin \u2013 Settings", settings),
        ("MyPlugin \u2013 About",    about),
    ]

config_widget(parent=None) QWidget

Returns a single QWidget. Used by the default config_tabs() implementation — override this when a single configuration tab is sufficient.

monitor_widget(parent=None) QWidget | None

Returns an optional live-status widget shown in the left dock panel Monitoring section whilst the plugin is registered. Return None (the default) if no monitoring widget is needed.

def monitor_widget(self, parent=None):
    self._status_label = QLabel("Idle", parent)
    return self._status_label

All TracePlugin and StateControlPlugin subclasses can also optionally provide custom configuration tabs by overriding:

  • _plugin_config_tabs() — return a QWidget that appears as the Settings configuration tab.

  • _about_html() — return an HTML string that appears as an About configuration tab.