Developing measurement classes

A measurement class describes the domain workflow. It coordinates MGraph, instruments, measurement points, autosave, and evaluation, but contains neither instrument-specific SCPI commands nor Qt widgets.

Derive from Measure

from mpylab.env.Measure import Measure

class ExampleMeasurement(Measure):
    def __init__(self, SearchPaths=None):
        super().__init__(SearchPaths=SearchPaths)
        self.raw_data = {}
        self.processed_data = {}

Measure provides logging, operator interaction, autosave, instrument initialization, RF-off, and pickle restoration. New classes should reuse this infrastructure rather than introduce independent variants.

Measurement workflow

A robust workflow follows these steps:

  1. Prepare the dataset and resume information.

  2. Create MGraph with name and condition mappings.

  3. Create instruments and initialize them through _init_measurement_devices.

  4. Explicitly apply the logical graph context before each frequency-dependent access. Explicit condition evaluation remains sufficient for graphs without a physical controller.

  5. Add only complete measurement points to the result structure.

  6. Call do_autosave regularly.

  7. Switch RF off and close instruments on a finally path.

Use _finalize_measurement_devices for common finalization. Measurement errors must not bypass this safety path.

Applying an explicit graph context

Custom measurement applications should pass frequency, operating mode, and similar domain values together to MGraph.ApplyContext. This keeps condition evaluation, frequency setup, physical switching, and state read-back in one coherent operation:

from mpylab.tools.mgraph import (
    GraphStateMismatchError,
    MGraph,
    TEM_CONDITION_MAP,
)

graph = MGraph(
    dotfile,
    themap=names,
    SearchPaths=search_paths,
    condition_map=TEM_CONDITION_MAP,
    allow_legacy_condition_context=False,
)
graph.CreateDevices()
graph.Init_Devices()

requested_context = {"frequency": frequency, "mode": "GTEM"}
try:
    context_result = graph.ApplyContext(requested_context)
except GraphStateMismatchError as error:
    # MGraph has already switched RF off. The application records
    # error.mismatches and asks the operator for a decision.
    if not operator_explicitly_selected_reapply(error.mismatches):
        raise
    graph.ReapplyContext()
    context_result = graph.ApplyContext(requested_context)

GraphStateMismatchError.mismatches contains the expected and observed state for each controller, together with status or query errors where applicable. The application should record this information and present it neutrally in its CLI or user interface. It must not interpret instrument-specific relay state; that domain knowledge remains in the controller driver.

On a mismatch, MGraph has already called graph-wide RFOff. Aborting is therefore the safe default. Only an explicit operator choice may first restore the last planned state through ReapplyContext. The application then applies the originally requested context again. Merely retrying ApplyContext is deliberately insufficient because that could silently override the external switching action.

CheckContextState forces an immediate check; PollContextState rate-limits repeated queries. RFOn_Devices always checks immediately before enabling RF. Read and NBRead integrate a rate-limited check into measurement loops. This is not an asynchronous hardware watchdog, but it detects external intervention at the relevant program boundaries.

The required DOT attributes and driver contract are documented in the DOT reference. The curated MGraph API lists the result and exceptions.

Autosave contract

Before the first autosave, store a structured resume instruction:

self.set_autosave_resume(
    measurement="example",
    method="Measure_Example",
    description=description,
)

On resume, the measurement routine must detect existing data and continue at the first incomplete point. Its test therefore verifies call counts and result data, rather than merely checking that the pickle can be read.

Data structure

Prefer dataset[frequency] = quantity for frequency-dependent data, or use a clearly documented equivalent structure. Keep raw data, evaluated data, and metadata separate. Store references to preparatory measurements with dataset key and source so reports can present traceability without recalculating results.

Evaluation

Measurement and evaluation are separate methods. This allows an evaluation algorithm to run again on an after-measure pickle without accessing hardware. Evaluation does not modify raw data and records the reference datasets and relevant parameters it used.