plesty.lib.monitor

Monitor framework: schema-declared live views over streams of measured data.

The contract (Monitor) and the streams that feed it (plesty.lib.monitor.sources) are toolkit-free — importing this package pulls in no GUI. Concrete views live in their own modules (for spectroscopy: plesty-common-monitors); the dockable window that shows them is plesty.lib.ui.

Submodules

Attributes

COLOR_SCALES

TRACE_KINDS

FrameMapper

Reader

COLORS

SCALES

Drawing

Exceptions

FrameValidationError

Raised when a submitted frame violates the declared input_schema.

MonitorSchemaError

Raised when a monitor's channel or trace declaration is invalid.

PlotError

Raised when a plot declares something that cannot be drawn.

VizNotAvailable

Raised when an experiment's viewer cannot be loaded in this environment.

Classes

DataSource

What Monitor.pump() needs from a stream.

Frame

One immutable update delivered to a monitor.

Monitor

Base class for PLESTY monitors: schema-declared live views.

NullRenderer

Renderer that records draw calls instead of drawing them.

Renderer

Draw target of a monitor — the seam between projection and toolkit.

TraceData

Drawable payload produced for one trace by Monitor.update().

TraceSpec

Static declaration of one drawable element of a monitor's view.

PushSource

Thread-safe buffer of frames pushed in by the producer.

ReplaySource

A finished run handed out a few rows at a time, as if it were live.

RunSource

Frames tailed from the record file of an experiment run.

TelemetrySource

Frames built from the telemetry events of one or more devices.

RunData

The rows a run has committed, and the answers a plot needs from them.

Drawable

One element of a plot: what to draw, and how it should look.

FunctionPlot

A plot written as a function — what Viz.plot() wraps.

Image

A 2-D block of values, drawn as a map with a colour bar.

Line

A curve through (x, y).

Plot

One view of a run: a title, and what to draw from the run's data.

Scalar

A single number, shown large.

Scatter

Points at (x, y), drawn without a line between them.

Text

A line of text placed in the plot — a result, a warning, a caption.

Viz

The monitor / render command line over one experiment's panels.

Functions

utc_timestamp(→ str)

Return the current UTC time as an ISO-8601 string.

records(→ Iterable[dict[str, Any]])

Iterate the result records of a finished run, oldest first.

columns(→ list[str])

Every field name the rows carry, in the order first seen.

load_viz(→ Viz)

Return the Viz an experiment's viewer module declares.

Package Contents

plesty.lib.monitor.COLOR_SCALES: tuple[str, Ellipsis] = ('linear', 'sqrt')
plesty.lib.monitor.TRACE_KINDS: tuple[str, Ellipsis] = ('line', 'scatter', 'image', 'scalar', 'text')
class plesty.lib.monitor.DataSource

Bases: Protocol

What Monitor.pump() needs from a stream.

Declared here as a protocol so the ABC module stays free of I/O; the concrete implementations live in plesty.lib.monitor.sources.

poll() → Iterable[Frame]

Return the frames that appeared since the previous call.

Return type:

Iterable[Frame]

class plesty.lib.monitor.Frame

One immutable update delivered to a monitor.

Variables:
  • values – Channel name → value, validated against input_schema.

  • seq – Monotonic index within the stream, assigned by the source.

  • timestamp – UTC ISO-8601 time the update was produced; filled when empty.

  • source – Identifier of the producing source (run id, device id, …).

  • context – Extra JSON-serializable context (step id, run status, file paths) carried alongside the channels but never validated.

values: dict[str, Any]
seq: int = 0
timestamp: str = ''
source: str = ''
context: dict[str, Any]
__post_init__() → None

Stamp the creation time when the producer left it empty.

Return type:

None

__getitem__(channel: str) → Any

Return the value of channel.

Parameters:

channel (str) – Declared channel name.

Returns:

The channel value.

Raises:

KeyError – If the frame carries no such channel.

Return type:

Any

get(channel: str, default: Any = None) → Any

Return the value of channel, or default when it is absent.

Parameters:
  • channel (str) – Declared channel name.

  • default (Any) – Returned when the channel was not delivered.

Returns:

The channel value, or default.

Return type:

Any

exception plesty.lib.monitor.FrameValidationError

Bases: ValueError

Raised when a submitted frame violates the declared input_schema.

Initialize self. See help(type(self)) for accurate signature.

class plesty.lib.monitor.Monitor(source: DataSource | None = None, *, history: int | None = 1, name: str | None = None, **params: Any)

Bases: abc.ABC

Base class for PLESTY monitors: schema-declared live views.

Subclasses declare the channels they consume in input_schema and implement exactly two methods — traces() (what is drawn, declared once) and update() (what to draw for one frame). Frame validation, history, renderer fan-out, and source draining are base machinery.

Channel entries use the dtype vocabulary of device and analyzer schemas (plesty.lib.data.types.resolve_dtype()) and accept the keys dtype (required), unit, description, shape (arrays only; None marks a free dimension), and required (default True).

Initialize the monitor and validate its declarations.

Parameters:
  • source (Optional[DataSource]) – Stream to subscribe to; may be attached later with bind(), or omitted when frames are submitted by hand.

  • history (Optional[int]) – Number of past frames kept in frames (None keeps all, 1 keeps only the newest). Accumulating monitors keep their own series and leave this at 1.

  • name (Optional[str]) – Instance name, used by the shell for layout persistence; defaults to the class name.

  • **params (Any) – View parameters (limits, decimation, calibration), introspectable via params.

Raises:

MonitorSchemaError – If input_schema is invalid or traces() declares nothing.

title: ClassVar[str] = ''

Human-readable view title; falls back to the class name.

input_schema: ClassVar[dict[str, dict[str, Any]]]

name → schema entry.

Type:

Channels this monitor consumes

params
name = 'Monitor'
source = None
frames: collections.deque[Frame]
count = 0
_renderers: list[Renderer] = []
_last: dict[str, TraceData]
_specs
abstractmethod traces() → list[TraceSpec]

Declare the drawable elements of this view.

Called once during construction; the result must be stable for the lifetime of the instance, because renderers build their plot items from it when they attach.

Returns:

The trace specifications, in draw order.

Return type:

list[TraceSpec]

abstractmethod update(frame: Frame) → dict[str, TraceData]

Project one validated frame onto the declared traces.

Implementations are pure with respect to the outside world: they read the frame and their own accumulated state and return what to draw. They must not touch devices, files, or GUI objects. A trace left out of the result keeps whatever it was showing.

Parameters:

frame (Frame) – The validated update.

Returns:

Trace key → payload; the keys must be declared traces.

Return type:

dict[str, TraceData]

submit(frame: Frame) → dict[str, TraceData]

Validate a frame, project it, and push the result to the renderers.

Parameters:

frame (Frame) – The update to process.

Returns:

The projected payloads returned by update().

Raises:

FrameValidationError – If the frame violates input_schema, or update() returns an undeclared trace key.

Return type:

dict[str, TraceData]

pump() → int

Drain the bound source and submit every pending frame.

Runs on the caller’s clock — a GUI timer, a test loop — so the monitor owns no thread of its own.

Returns:

The number of frames submitted.

Return type:

int

bind(source: DataSource) → None

Subscribe this monitor to source.

Parameters:

source (DataSource) – The stream pump() will drain.

Return type:

None

attach(renderer: Renderer) → Renderer

Attach a renderer, build its items, and replay the last drawn data.

Parameters:

renderer (Renderer) – The draw target.

Returns:

The attached renderer, for convenient chaining.

Return type:

Renderer

detach(renderer: Renderer) → None

Remove a previously attached renderer.

Parameters:

renderer (Renderer) – The draw target to remove; unknown renderers are ignored.

Return type:

None

reset() → None

Clear history, projected state, and every attached renderer.

Subclasses that accumulate their own series override this, drop that state, and call super().reset().

Return type:

None

snapshot() → dict[str, TraceData]

Return the most recent payload of every trace drawn so far.

Returns:

Trace key → payload; empty before the first frame.

Return type:

dict[str, TraceData]

trace_spec(key: str) → TraceSpec

Return the declaration of one trace.

Parameters:

key (str) – Trace key.

Returns:

The TraceSpec.

Raises:

KeyError – If the monitor declares no such trace.

Return type:

TraceSpec

property trace_specs: list[TraceSpec]

The declared traces, in draw order.

Return type:

list[TraceSpec]

property display_title: str

title, or the class name.

Type:

Title a shell shows

Return type:

str

status() → str

Return a one-line status shown beside the title in a shell.

The default reports how many frames were consumed; subclasses override it with something the operator cares about (the newest angle, the peak wavelength, the run id).

Returns:

The status line.

Return type:

str

describe() → dict[str, Any]

Return a JSON-serializable description of this view.

Shells use it for layout persistence, and reporting tooling to record what was watched during a run.

Returns:

Monitor identity, parameters, channels, and declared traces.

Return type:

dict[str, Any]

_validate_schema() → None

Check input_schema entry by entry.

Raises:

MonitorSchemaError – On an unknown key, a missing or unresolvable dtype, or a malformed shape.

Return type:

None

_conform_values(values: dict[str, Any]) → dict[str, Any]

Check frame channels against input_schema, coercing array-likes.

Unlike analyzer inputs, live frames arrive from JSON step records and telemetry events, so array channels are coerced from plain lists here instead of being rejected.

Parameters:

values (dict[str, Any]) – The raw channel mapping of the frame.

Returns:

The conformed mapping.

Raises:

FrameValidationError – On an unknown or missing channel, a wrong dtype, or a shape contradicting the declaration.

Return type:

dict[str, Any]

_conform_channel(name: str, value: Any, entry: dict[str, Any]) → Any

Conform one channel value to its declared dtype and shape.

Parameters:
  • name (str)

  • value (Any)

  • entry (dict[str, Any])

Return type:

Any

static _conform_array(name: str, value: Any, entry: dict[str, Any]) → plesty.lib.data.array.PlestyArray

Coerce an array channel and check it against the declared shape.

Parameters:
  • name (str)

  • value (Any)

  • entry (dict[str, Any])

Return type:

plesty.lib.data.array.PlestyArray

static _conform_scalar(name: str, value: Any, expected: type) → Any

Check a scalar channel, accepting ints and NumPy scalars for floats.

Parameters:
  • name (str)

  • value (Any)

  • expected (type)

Return type:

Any

_check_traces(data: dict[str, TraceData]) → None

Check that update() returned declared trace keys only.

Parameters:

data (dict[str, TraceData])

Return type:

None

exception plesty.lib.monitor.MonitorSchemaError

Bases: ValueError

Raised when a monitor’s channel or trace declaration is invalid.

Initialize self. See help(type(self)) for accurate signature.

class plesty.lib.monitor.NullRenderer

Bases: Renderer

Renderer that records draw calls instead of drawing them.

Used by tests and by headless runs that only need the projected data.

Initialize the empty draw log.

drawn: list[dict[str, TraceData]] = []
draw(monitor: Monitor, data: dict[str, TraceData]) → None

Append data to drawn.

Parameters:
  • monitor (Monitor) – The monitor that produced the update; unused.

  • data (dict[str, TraceData]) – Trace key → payload.

Return type:

None

clear(monitor: Monitor) → None

Discard the recorded draw calls.

Parameters:

monitor (Monitor) – The monitor whose view is being reset; unused.

Return type:

None

class plesty.lib.monitor.Renderer

Draw target of a monitor — the seam between projection and toolkit.

The base implementation does nothing, so headless callers and tests use a monitor without any GUI dependency; GUI backends (plesty.lib.ui) subclass it.

setup(monitor: Monitor) → None

Build the drawable items declared by monitor.traces().

Parameters:

monitor (Monitor) – The monitor this renderer was attached to.

Return type:

None

draw(monitor: Monitor, data: dict[str, TraceData]) → None

Draw one update.

Parameters:
Return type:

None

clear(monitor: Monitor) → None

Drop everything drawn so far.

Parameters:

monitor (Monitor) – The monitor whose view is being reset.

Return type:

None

class plesty.lib.monitor.TraceData

Drawable payload produced for one trace by Monitor.update().

Variables:
  • x – Sample positions of a line/scatter trace.

  • y – Sample values of a line/scatter trace.

  • image – 2-D array of an image trace.

  • value – Number shown by a scalar trace.

  • text – Text of a text trace, or an annotation for any other kind.

x: Sequence[float] | None = None
y: Sequence[float] | None = None
image: Any = None
value: float | None = None
text: str | None = None
class plesty.lib.monitor.TraceSpec

Static declaration of one drawable element of a monitor’s view.

Specs are read when a renderer attaches, to build its plot items; only TraceData changes per frame.

Variables:
  • key – Identifier used as the key of the Monitor.update() result.

  • kind – One of TRACE_KINDS.

  • label – Legend label; falls back to key when empty. A label starting with _ keeps the trace out of the legend, as in matplotlib — for a trace the legend would only repeat, such as a fitted curve drawn in the colour of its points.

  • x_label – Axis label including the unit, e.g. "Wavelength (nm)".

  • y_label – Axis label including the unit, e.g. "Power (uW)".

  • color – PLESTY palette token ("cyan", "violet", "mint", "amber", "rose", "blue"). The renderer resolves it against the theme, so monitors never hard-code hex values.

  • width – Line width, or marker size for scatter.

  • marker – Draw point markers on a line trace.

  • scale – Colour scale of an image trace, one of COLOR_SCALES: "linear", or "sqrt" — colours follow the square root of the value, so lines ten times weaker than the strongest still stand out from the background; the colour bar stays labelled in the image’s own values.

key: str
kind: str = 'line'
label: str = ''
x_label: str = ''
y_label: str = ''
color: str = 'cyan'
width: float = 1.5
marker: bool = False
scale: str = 'linear'
__post_init__() → None

Validate the trace kind and colour scale.

Raises:

MonitorSchemaError – If kind or scale is not a known one.

Return type:

None

plesty.lib.monitor.utc_timestamp() → str

Return the current UTC time as an ISO-8601 string.

Return type:

str

plesty.lib.monitor.FrameMapper
class plesty.lib.monitor.PushSource(name: str = 'push', maxlen: int | None = 10000)

Thread-safe buffer of frames pushed in by the producer.

Producers call emit() from any thread; the consumer drains the buffer with poll() on its own thread. When maxlen is set the buffer drops the oldest pending frames instead of growing without bound — a slow consumer never turns into a memory leak.

Initialize the buffer.

Parameters:
  • name (str) – Identifier stamped onto every frame’s source.

  • maxlen (Optional[int]) – Maximum number of pending frames; None for unbounded.

name = 'push'
_queue: collections.deque[plesty.lib.monitor.base_monitor.Frame]
_lock
_seq = 0
emit(values: dict[str, Any], **context: Any) → plesty.lib.monitor.base_monitor.Frame

Buffer one update.

Parameters:
  • values (dict[str, Any]) – Channel name → value.

  • **context (Any) – Extra context carried on the frame.

Returns:

The buffered Frame.

Return type:

plesty.lib.monitor.base_monitor.Frame

poll() → list[plesty.lib.monitor.base_monitor.Frame]

Return and clear the buffered frames.

Returns:

The pending frames in emission order.

Return type:

list[plesty.lib.monitor.base_monitor.Frame]

close() → None

Drop everything still buffered.

Return type:

None

class plesty.lib.monitor.ReplaySource(run_dir: str | pathlib.Path, *, mapper: FrameMapper | None = None, name: str | None = None, rows_per_poll: int = 1, loop: bool = False)

Bases: RunSource

A finished run handed out a few rows at a time, as if it were live.

Watching a stored run play back is how a viewer is demonstrated, how a layout is judged before the beam time that needs it, and how a run that went wrong is re-read at the pace it happened rather than as a finished picture. It is the same source as RunSource — the records are the ones the experiment committed — only rationed.

The wall-clock speed is the consumer’s refresh interval times rows_per_poll: a shell ticking every 500 ms with the default of one row replays two rows per second.

The ration counts records, including those the mapper rejects, so every view reaches a record at the same moment of the replay whatever its mapper keeps. A view that uses only the run’s last record — an analysis done after the sweep — shows it when the replay gets there, not on the first poll.

source = ReplaySource(run_dir, mapper=to_channels, rows_per_poll=2)
monitor.bind(source)   # the run rebuilds itself in the window

Replay a run directory.

Parameters:
  • run_dir (str | pathlib.Path) – The run’s checkpoint directory.

  • mapper (Optional[FrameMapper]) – Turns one record into a view’s channels; see RunSource.

  • name (Optional[str]) – Identifier stamped onto every frame’s source.

  • rows_per_poll (int) – Records handed out per poll(); with the consumer’s refresh interval, this is the replay speed.

  • loop (bool) – Start over once the run is exhausted, instead of stopping at the last row.

Raises:

ValueError – If rows_per_poll is not positive — a replay that hands out nothing would look like a stalled run.

rows_per_poll = 1
loop = False
_pending: list[plesty.lib.monitor.base_monitor.Frame | None] = []
poll() → list[plesty.lib.monitor.base_monitor.Frame]

Return the frames of the next few records of the run.

Returns:

The frames among the next rows_per_poll records — fewer, or none, where the mapper rejected a record; empty once the run is exhausted, unless loop is set.

Return type:

list[plesty.lib.monitor.base_monitor.Frame]

_pending_records() → list[plesty.lib.monitor.base_monitor.Frame | None]

Return one entry per newly committed record: its frame, or None.

Return type:

list[Optional[plesty.lib.monitor.base_monitor.Frame]]

property exhausted: bool

Whether every record of a finished run has been handed out.

False while a record is still buffered or still on disk unread, and false for a run that is still measuring — there, the next record is simply not written yet. A looping replay is never exhausted: the next poll starts it over.

Return type:

bool

rewind() → None

Start the replay over from the first record.

Return type:

None

class plesty.lib.monitor.RunSource(run_dir: str | pathlib.Path, *, mapper: FrameMapper | None = None, name: str | None = None)

Frames tailed from the record file of an experiment run.

The experiment appends one line per completed step to records.jsonl (append_record()), and that line is the commit record: it is complete only once the step is fully persisted. Tailing the file by offset is therefore both safe and cheap — the source reads, never writes, costs one small read per poll however long the run, and can attach to a run that started hours ago and still see every row, including the ones it missed.

Records are emitted in write order; a line still being written (no newline yet) waits for the next poll. Runs written by plesty-lib ≤ 0.3.4 (one data/step_*.json per step) are read the old way.

source = RunSource("runs/qd_fss_20260804-214826",
                   mapper=lambda rec: {"angle_deg": rec["hwp_deg"]})
monitor.bind(source)
monitor.pump()   # → submits every row persisted so far

Follow a run directory.

Parameters:
  • run_dir (str | pathlib.Path) – The run’s checkpoint directory (the one holding plan.json, journal.jsonl, and records.jsonl).

  • mapper (Optional[FrameMapper]) – Turns one record into the channel mapping a monitor declares; returning None drops the record (useful for the bookkeeping steps a plan starts with). The default passes the record through unchanged.

  • name (Optional[str]) – Identifier stamped onto every frame’s source; defaults to the run directory name.

run_dir
mapper = None
name = ''
_offset = 0
_seen: set[str]
_seq = 0
property legacy: bool

Whether the run is in the per-step-document layout (no record file).

Return type:

bool

_new_records() → list[tuple[str, dict[str, Any]]]

Return the (label, record) pairs committed since the last call.

Return type:

list[tuple[str, dict[str, Any]]]

poll() → list[plesty.lib.monitor.base_monitor.Frame]

Return frames for the records committed since last time.

Returns:

The new frames in write order; empty while the run directory does not exist yet, so a viewer may be started before the experiment.

Return type:

list[plesty.lib.monitor.base_monitor.Frame]

_frame(label: str, record: dict[str, Any]) → plesty.lib.monitor.base_monitor.Frame | None

Map one record to a frame, or None when the mapper rejects it.

Parameters:
  • label (str)

  • record (dict[str, Any])

Return type:

Optional[plesty.lib.monitor.base_monitor.Frame]

property unread: bool

Whether committed records exist that no poll has returned yet.

Return type:

bool

rewind() → None

Forget which records were seen, so the next poll replays the run.

Return type:

None

property finished: bool

Whether the run reached a terminal state.

A completed, aborted, or canceled run writes no further records, so a viewer can stop polling (and stop recording) on its own.

Return type:

bool

status() → dict[str, Any]

Return the run status derived from the journal.

Returns:

The summary of plesty.lib.experiment.journal.Journal.status() — state, completed and failed step counts, last event.

Return type:

dict[str, Any]

close() → None

Release nothing — the source holds no handle; kept for symmetry.

Return type:

None

class plesty.lib.monitor.TelemetrySource(devices: Sequence[Any], *, kinds: Sequence[str] | None = None, names: Sequence[str] | None = None, name: str = 'telemetry', maxlen: int | None = 10000)

Bases: PushSource

Frames built from the telemetry events of one or more devices.

Registers a hook on every device (see register_telemetry_hook()) and buffers each event as a frame with the channels device, kind, name, value, and unit, plus the event’s own context. Hooks run on the emitting device’s thread, so buffering is all that happens there.

Subscribe to the devices’ telemetry.

Parameters:
  • devices (Sequence[Any]) – Devices exposing register_telemetry_hook.

  • kinds (Optional[Sequence[str]]) – Event kinds to keep (see TELEMETRY_KINDS); None keeps every kind.

  • names (Optional[Sequence[str]]) – Parameter/metric names to keep; None keeps all.

  • name (str) – Identifier stamped onto every frame’s source.

  • maxlen (Optional[int]) – Maximum number of pending frames.

_devices
_kinds = None
_names = None
_on_event(event: Any) → None

Buffer one telemetry event, honouring the kind/name filters.

Parameters:

event (Any)

Return type:

None

close() → None

Unregister the hooks and drop the buffered frames.

Return type:

None

plesty.lib.monitor.records(run_dir: str | pathlib.Path) → Iterable[dict[str, Any]]

Iterate the result records of a finished run, oldest first.

The offline counterpart of RunSource, for replaying a run into a monitor or feeding an analyzer.

Parameters:

run_dir (str | pathlib.Path) – The run’s checkpoint directory.

Yields:

One record per readable committed result, in write order.

Return type:

Iterable[dict[str, Any]]

plesty.lib.monitor.Reader
class plesty.lib.monitor.RunData(run: plesty.lib.experiment.runs.Run | str, *, reads: Reader | None = None, rows: Sequence[Mapping[str, Any]] | None = None)

The rows a run has committed, and the answers a plot needs from them.

Variables:
  • run – The run being read.

  • reads – Expands a row into extra columns, or None.

Parameters:

Read the run at run.

Parameters:
  • run (plesty.lib.experiment.runs.Run | str) – The run, or a path to one.

  • reads (Optional[Reader]) – Called once per row to add columns the records only point at — a spectrum file becomes wavelength and counts. Its answer is cached, so every plot shares one load.

  • rows (Optional[Sequence[Mapping[str, Any]]]) – Rows to start from instead of reading; used by where() to build a filtered view over rows already read.

run
reads = None
_rows: list[dict[str, Any]]
_source: Any = None
_derived
poll() → int

Ingest whatever has committed since the last call.

Cheap however long the run has been going: the records file is read from the byte offset reached last time, so a poll costs one small read and never re-parses what it already holds.

Returns:

How many rows were added.

Raises:

RuntimeError – On a filtered view, which reads nothing of its own.

Return type:

int

_expand(row: dict[str, Any]) → dict[str, Any]

Return row with whatever reads adds to it.

A reader that fails is not allowed to end the watching: a spectrum still being written, or one file of a thousand that is corrupt, costs that row its extra columns and nothing else.

Parameters:

row (dict[str, Any])

Return type:

dict[str, Any]

property rows: list[dict[str, Any]]

Every row committed so far, oldest first.

Return type:

list[dict[str, Any]]

property latest: dict[str, Any] | None

The newest committed row, whatever kind of step wrote it.

A plan’s steps are not all alike: a sweep commits a spectrum per angle and then an analysis, and the analysis is the newest row. A view of the spectra wants last() instead.

Return type:

Optional[dict[str, Any]]

last(name: str) → dict[str, Any] | None

The newest row that carries name, or None.

What “the current spectrum” means when the newest row is the fit that followed it.

Parameters:

name (str) – The field the row must carry.

Returns:

The row, or None when none has it yet.

Return type:

Optional[dict[str, Any]]

column(name: str, default: Any = np.nan) → numpy.ndarray

One field across every row, as an array.

Parameters:
  • name (str) – The field.

  • default (Any) – Value for a row that does not carry it — nan by default, so a gap stays a gap instead of shifting the axis.

Returns:

One entry per row, in commit order.

Raises:

ValueError – If the field holds arrays rather than single values — naming stack(), which is what such a field is for. Without this the failure is numpy’s, about inhomogeneous shapes, which says nothing about what to do instead.

Return type:

numpy.ndarray

stack(name: str) → numpy.ndarray

An array field across every row, as one 2-D block.

Rows whose entry is missing or a different length are left out rather than padded: a spectral map with a half-row in it is a lie about what was measured.

Parameters:

name (str) – The field.

Returns:

Shape (rows, samples); empty when nothing usable was found.

Return type:

numpy.ndarray

stacked(name: str, along: str = '', default: Any = np.nan) → tuple[numpy.ndarray, numpy.ndarray | None]

The stack of name, and along for exactly the rows it kept.

Asking for the block and its coordinate separately is a bug waiting to happen: stack() leaves out rows that carry no array or the wrong width, and those rows are not necessarily at either end — a sweep that ends in an analysis drops its last row. A coordinate taken from column() would then be one row longer and misaligned, which draws every spectrum against the wrong position. One call decides which rows are usable and answers for both.

Parameters:
  • name (str) – The array field.

  • along (str) – Field holding each kept row’s coordinate; "" for none.

  • default (Any) – Coordinate of a kept row that does not carry along.

Returns:

The block, shape (rows, samples), and the coordinate of each of its rows — or None when along was not asked for.

Return type:

tuple[numpy.ndarray, Optional[numpy.ndarray]]

where(**fields: Any) → RunData

The same data, narrowed to rows whose fields equal fields.

Parameters:

**fields (Any) – Field name to required value.

Returns:

A view over the rows already read — it holds no source and is not polled; poll the data it came from and ask again.

Return type:

RunData

property config: dict[str, Any]

The configuration frozen into the run’s plan.

Return type:

dict[str, Any]

property provenance: dict[str, Any]

What was running when the run started; {} for an older run.

Return type:

dict[str, Any]

status() → dict[str, Any]

The run’s state and step counts, read from its journal.

Return type:

dict[str, Any]

property finished: bool

Whether the run reached a state that produces no more rows.

Return type:

bool

__len__() → int

How many rows have been committed.

Return type:

int

__iter__() → Iterator[dict[str, Any]]

Iterate the rows, oldest first.

Return type:

Iterator[dict[str, Any]]

__bool__() → bool

Whether any row has been committed.

Spelled out because __len__ would otherwise make an empty run falsy in a way a plot guarding on if data: might not intend.

Return type:

bool

__repr__() → str

Return RunData('<run id>', rows=<n>).

Return type:

str

plesty.lib.monitor.columns(rows: Iterable[Mapping[str, Any]]) → list[str]

Every field name the rows carry, in the order first seen.

Parameters:

rows (Iterable[Mapping[str, Any]]) – The rows to survey.

Returns:

The field names — what a generic view offers when it has not been told which fields matter.

Return type:

list[str]

plesty.lib.monitor.COLORS: tuple[str, Ellipsis] = ('cyan', 'violet', 'mint', 'amber', 'rose', 'blue')
plesty.lib.monitor.SCALES: tuple[str, Ellipsis] = ('linear', 'sqrt')
class plesty.lib.monitor.Drawable

One element of a plot: what to draw, and how it should look.

Variables:
  • label – Legend label. One starting with _ stays out of the legend, as in matplotlib — for a trace the legend would only repeat, such as a fitted curve in the colour of its points.

  • color – A token from COLORS.

  • x_label – Axis label including the unit, "Wavelength (nm)".

  • y_label – Axis label including the unit.

kind: ClassVar[str] = 'drawable'
label: str = ''
color: str = 'cyan'
x_label: str = ''
y_label: str = ''
__post_init__() → None

Check the colour token.

Raises:

PlotError – If the colour is not one the theme defines.

Return type:

None

plesty.lib.monitor.Drawing
class plesty.lib.monitor.FunctionPlot(func: DrawFunc, title: str = '', *, name: str = '', weight: float | None = None)

Bases: Plot

A plot written as a function — what Viz.plot() wraps.

Not usually built by hand: decorating a function with plot() makes one of these.

Wrap func as a plot.

Parameters:
  • func (DrawFunc) – Takes the run’s data, returns drawables.

  • title (str) – What the view is called; defaults to the function’s name with underscores as spaces.

  • name (str) – Layout key.

  • weight (Optional[float]) – Relative size.

func
draw(data: Any) → Drawing

Call the wrapped function.

Parameters:

data (Any) – The run’s data.

Returns:

Whatever the function returned; () when it returned nothing, so a plot with nothing to show yet is empty rather than an error.

Return type:

Drawing

class plesty.lib.monitor.Image

Bases: Drawable

A 2-D block of values, drawn as a map with a colour bar.

Variables:
  • z – The values, shape (rows, columns).

  • x – Positions of the columns, for the axis.

  • y – Positions of the rows, for the axis.

  • scale – "linear", or "sqrt" — colours follow the square root of the value, so features ten times weaker than the strongest still stand out from the background. The bar stays labelled in the image’s own values.

kind: ClassVar[str] = 'image'
z: Any = None
x: Any = None
y: Any = None
scale: str = 'linear'
__post_init__() → None

Check the colour token and the colour scale.

Raises:

PlotError – If the scale is not one the renderer knows.

Return type:

None

class plesty.lib.monitor.Line

Bases: Drawable

A curve through (x, y).

Variables:
  • x – Sample positions.

  • y – Sample values.

  • width – Line width.

  • marker – Draw a point marker at every sample as well.

kind: ClassVar[str] = 'line'
x: Any = None
y: Any = None
width: float = 1.5
marker: bool = False
class plesty.lib.monitor.Plot(title: str = '', *, name: str = '', weight: float | None = None, area: str = '')

Bases: abc.ABC

One view of a run: a title, and what to draw from the run’s data.

Subclass this when a view has to remember something between ticks — a line tracker, a running baseline. For a view that is a pure function of the data, plot() takes the function directly and there is no class to write.

Variables:
  • title – What the view is called, on its dock tab.

  • name – Stable identifier, used as the layout key so an arrangement survives a restart; defaults to the title.

  • weight – Relative size when the panels are first laid out.

  • area – Which side of the window it starts on — one of DOCK_AREAS. Views sharing an area are stacked in one column, so this is how a viewer says which of its plots belong side by side. The operator can drag them anywhere afterwards, and the arrangement is remembered.

Parameters:
  • title (str)

  • name (str)

  • weight (Optional[float])

  • area (str)

Name the view.

Parameters:
  • title (str) – Overrides the class attribute.

  • name (str) – Layout key; defaults to the title.

  • weight (Optional[float]) – Overrides the class attribute.

  • area (str) – Overrides the class attribute.

title: str = ''
name: str = ''
weight: float = 1.0
area: str = 'right'
abstractmethod draw(data: Any) → Drawing

Return what to draw, given everything the run has produced.

Called on every tick, on the GUI thread. It should be a projection of the data and nothing else — no device is reachable from here and none should be: watching never disturbs measuring.

Parameters:

data (Any) – The run’s RunData.

Returns:

The drawables, in the order they should be layered.

Return type:

Drawing

build() → Any

Return a widget for this view instead of a plot, or None.

The escape hatch. A view that is not a plot at all — a table of the committed rows, an image browser, a strip of readouts — returns its own widget here and ignores draw(). Anything Qt can show is allowed; the dock does not care what is inside it.

Returns:

None by default, meaning “draw me as a plot”.

Return type:

Any

__repr__() → str

Return <ClassName 'title'>.

Return type:

str

exception plesty.lib.monitor.PlotError

Bases: ValueError

Raised when a plot declares something that cannot be drawn.

Initialize self. See help(type(self)) for accurate signature.

class plesty.lib.monitor.Scalar

Bases: Drawable

A single number, shown large.

Variables:
  • value – The number.

  • unit – Shown after it.

kind: ClassVar[str] = 'scalar'
value: float | None = None
unit: str = ''
class plesty.lib.monitor.Scatter

Bases: Drawable

Points at (x, y), drawn without a line between them.

Variables:
  • x – Sample positions.

  • y – Sample values.

  • size – Marker size.

kind: ClassVar[str] = 'scatter'
x: Any = None
y: Any = None
size: float = 8.0
class plesty.lib.monitor.Text

Bases: Drawable

A line of text placed in the plot — a result, a warning, a caption.

Variables:

text – What to show.

kind: ClassVar[str] = 'text'
text: str = ''
class plesty.lib.monitor.Viz(title: str, *, experiment: str | Sequence[str] | None = None, run_root: str | pathlib.Path | None = None, reads: plesty.lib.monitor.data.Reader | None = None)

The monitor / render command line over one experiment’s panels.

Describe the experiment whose runs are watched.

Parameters:
  • title (str) – Human title of the experiment, shown in the window title and the command-line description.

  • experiment (Union[str, Sequence[str], None]) – The experiment name(s) whose runs to look for when --run is not given — a name or several (an experiment that was renamed still owns its old runs). None takes the newest run of any experiment.

  • run_root (Optional[str | pathlib.Path]) – Where runs are looked for — the experiment’s root, under the shared point (PLESTY_DATA) when relative; None takes the shared point itself, and runs without one. --run-root overrides per call.

  • reads (Optional[plesty.lib.monitor.data.Reader]) – Expands one committed row into the columns the plots want — a record names a spectrum file rather than carrying the array, so something has to turn the path into numbers. One per viewer, cached per row, so a map and a fit of the same sweep load each spectrum once between them. An experiment whose records hold their own numbers omits it.

title
experiment = None
run_root = None
reads = None
plots: list[plesty.lib.monitor.plots.Plot] = []
_panels: PanelsFunc | None = None
add_plot(plot: plesty.lib.monitor.plots.Plot) → plesty.lib.monitor.plots.Plot

Add plot to the window; returns it.

Parameters:

plot (plesty.lib.monitor.plots.Plot) – A Plot.

Returns:

The plot, so a caller can keep a reference to one it will poke.

Return type:

plesty.lib.monitor.plots.Plot

plot(title: str = '', *, name: str = '', weight: float | None = None) → Callable[[plesty.lib.monitor.plots.DrawFunc], plesty.lib.monitor.plots.DrawFunc]

Add a plot written as a function. Use as a decorator.

@viz.plot("Spectrum")
def spectrum(data):
    row = data.latest
    return [Line(row["wavelength"], row["counts"])]

The function is called on every tick with the run’s RunData and returns drawables. It is handed the whole store rather than declared channels, so a plot that starts wanting one more field needs no second declaration to keep in step.

Parameters:
  • title (str) – What the view is called; defaults to the function’s name.

  • name (str) – Layout key, so an arrangement survives a rename of the title; defaults to the title.

  • weight (Optional[float]) – Relative size when the panels are first laid out.

Returns:

The decorator, which returns the function unchanged so it stays callable and testable on its own.

Return type:

Callable[[plesty.lib.monitor.plots.DrawFunc], plesty.lib.monitor.plots.DrawFunc]

panels(func: PanelsFunc) → PanelsFunc

Register a function that turns a Run into panels directly.

Superseded by plot(), and kept for viewers written against the older shape. A viewer that registers plots does not need this; one that registers both shows the panels and then the plots.

Parameters:

func (PanelsFunc)

Return type:

PanelsFunc

data(run: plesty.lib.experiment.runs.Run) → plesty.lib.monitor.data.RunData

Return the store this viewer’s plots read.

Parameters:

run (plesty.lib.experiment.runs.Run) – The run to read.

Returns:

A RunData over it, carrying this viewer’s reads.

Return type:

plesty.lib.monitor.data.RunData

build_parser() → argparse.ArgumentParser

Return the argument parser: shared options plus the two subcommands.

Return type:

argparse.ArgumentParser

view(run: plesty.lib.experiment.runs.Run, *, interval_ms: int = 500, remember_layout: bool = True, title: str | None = None) → Any

Compose this experiment’s panels over run, without showing them.

This is the whole of what an experiment’s viewer is: the panels its panels() function yields, docked together and ticking. What is done with the result is the caller’s business — main() gives it a window of its own, and a host application puts the same object in a layout, because a shell is a widget before it is a window.

So an experiment writes its views once and they are embeddable wherever the module can be imported, with no second composition to keep in step and nothing serialized between them. A console that does not have the module installed cannot call this at all, and opens the window instead — that is the only difference between the two.

Parameters:
  • run (plesty.lib.experiment.runs.Run) – The run to watch.

  • interval_ms (int) – How often the panels are pumped.

  • remember_layout (bool) – Restore and save the arrangement. A host that embeds the view usually says False: the arrangement of a pane inside another window is not the arrangement of the experiment’s own window.

  • title (Optional[str]) – Window title and layout key; None names the experiment and the run.

Returns:

A Shell holding the panels, not yet started — the caller calls start() when it is in place.

Raises:
  • RuntimeError – If the viewer declares neither plots nor panels.

  • ValueError – If nothing came of them; an empty view says less than the reason it is empty.

Return type:

Any

resolve_run(args: argparse.Namespace) → plesty.lib.experiment.runs.Run

Return the Run the arguments name, configured for the subcommand.

Raises:

SystemExit – If no run exists yet — a viewer opened before its experiment is a mistake worth naming, not an empty window.

Parameters:

args (argparse.Namespace)

Return type:

plesty.lib.experiment.runs.Run

main(argv: Sequence[str] | None = None) → int

Run the command line and return the exit code.

Parameters:

argv (Optional[Sequence[str]]) – Argument list; None uses sys.argv.

Raises:
  • RuntimeError – If no panels function was registered.

  • SystemExit – If no run exists, or the panels function yields nothing.

Return type:

int

exception plesty.lib.monitor.VizNotAvailable

Bases: RuntimeError

Raised when an experiment’s viewer cannot be loaded in this environment.

Initialize self. See help(type(self)) for accurate signature.

plesty.lib.monitor.load_viz(module: str) → Viz

Return the Viz an experiment’s viewer module declares.

A host application — a console managing several experiments — embeds an experiment’s own views by importing its viewer here and calling Viz.view(). That works exactly when the module is installed alongside the host, which is the condition, and the honest failure when it is not: the host then opens the viewer as its own process instead.

The module is expected to hold exactly one Viz at module level, which is how every viewer is written — viz = Viz("…") beside the panels function it decorates.

Parameters:

module (str) – Import path of the viewer, plesty.pol_pl.viz.

Returns:

The viewer’s Viz.

Raises:

VizNotAvailable – If the module cannot be imported here — usually because the experiment is not installed in this environment — or does not declare exactly one Viz.

Return type:

Viz