plesty.fss.monitor

The fine-structure views, for the viewer of any experiment that runs a sweep.

Two views follow the emission lines of a polarization sweep, each line with the same number and colour in both — line 0 is the shortest wavelength:

  • LineSpectrumView — the newest spectrum, with a marker on every line.

  • FineStructureView — every line’s position as a photon-energy shift in µeV from its mean position, against the rotator angle.

Both fill in two stages:

  1. During the sweep — a LineTracker finds the lines the way the analysis does, in the spectra measured so far, and fits each spectrum as it arrives. Early in a sweep a line can still appear or disappear, and the numbering follows.

  2. After the sweep — an experiment that runs plesty.fss.FineStructure and saves its fine_structure and fit outputs with save_result records where it saved them. That record replaces the running estimate with the analysis: the fits of the whole sweep, each line’s fitted sine, and its splitting and polarization axis written in the line’s colour — or that the line has no valid splitting.

An experiment adds them to its Viz by name. It says which of its record fields hold the sweep; nothing else is configured:

from plesty.lib.monitor import Viz
from plesty.fss.monitor import FineStructureView, LineSpectrumView, LineTracking

viz = Viz("Polarization PL", experiment="pol_pl", reads=read_spectrum)
lines = LineTracking(angle="hwp_deg", label="qd")
viz.add_plot(LineSpectrumView(lines, weight=3.0))
viz.add_plot(FineStructureView(lines, angle_label="HWP angle (deg)", weight=3.0))

One LineTracking is shared by both views on purpose: it fits each spectrum once and answers both from that one fit.

These views draw lines, points and text, so they have no error bars and no residual panel; plesty.fss.plot.plot_fine_structure() draws both for reports.

Attributes

LINE_COLORS

Classes

LineTracker

Find and fit the lines of a sweep while it is measured.

LineTracking

Follows a run's emission lines while the sweep is being measured.

SweepSoFar

The lines of a sweep as a LineTracker knows them after one spectrum.

LineSpectrumView

The newest spectrum, with a marker on every line the fine structure follows.

FineStructureView

Energy shift of every line against the rotator angle, with its fit and result.

Functions

line_color(→ str)

The colour of the index-th line, counting from the shortest wavelength.

line_drawing(→ list[Any])

One analysed line: its measured shifts as points, and its fitted sine.

sweep_channels(→ dict[str, Any])

Compute what the view draws from the analyzer's fine_structure and fit outputs.

Module Contents

class plesty.fss.monitor.LineTracker(fit_options: Mapping[str, Any])

Find and fit the lines of a sweep while it is measured.

Lines are found the way SpectrumFit finds them for the analysis: in the average of the spectra — so far — with the same prominence, distance and average (use "median": in the mean of a few spectra a cosmic ray passes as a line). Given peaks, those positions are the lines and nothing is searched.

Each new spectrum is fitted with the analysis’s other fit options, starting from the previous spectrum’s positions. When the lines found change — a line appears or disappears — the positions fitted so far are kept for the lines that remain, so the numbering by wavelength can shift but no spectrum is refitted. A line whose position moves by up to SAME_LINE_PIXELS pixels is the same line.

One tracker serves both views of a run: each spectrum is fitted once, and a view that reads a spectrum later than the other gets the same answer. A spectrum with a different label starts a new sweep.

Validate the fit options.

Parameters:

fit_options (Mapping[str, Any]) – SpectrumFit options, with either the line positions in peaks or a prominence to find them.

Raises:

ValueError – If neither peaks nor prominence is given, or an option is invalid.

fixed: numpy.ndarray | None = None
prominence
distance
average = ''
fit_options
fits = 0
_seen: dict[str, SweepSoFar]
_label: str | None = None
_wavelength: numpy.ndarray | None = None
_spectra: list[numpy.ndarray] = []
_angles: list[float] = []
_rows: list[numpy.ndarray] = []
_positions: numpy.ndarray
observe(channels: Mapping[str, Any]) → SweepSoFar

Add one spectrum and return the sweep so far.

Parameters:

channels (Mapping[str, Any]) – One spectrum’s wavelength in nm and counts, with row (the rotator angle in degrees) and label when known.

Returns:

The lines and their positions after this spectrum.

Return type:

SweepSoFar

_reset(label: str | None) → None

Start a new sweep.

Parameters:

label (Optional[str])

Return type:

None

_update_lines() → None

Find the lines in the spectra so far; keep the columns of the lines that remain.

Return type:

None

_fit(counts: numpy.ndarray) → numpy.ndarray

Fit the lines in one spectrum, from the previous spectrum’s positions.

Parameters:

counts (numpy.ndarray)

Return type:

numpy.ndarray

class plesty.fss.monitor.LineTracking(options: Mapping[str, Any] | None = None, *, at: Sequence[str] = ('analysis', 'fit_options'), counts: str = 'counts', wavelength: str = 'wavelength', angle: str = '', label: str = '', fit_key: str = 'line_fit_document', budget: int = 4)

Follows a run’s emission lines while the sweep is being measured.

Hand the same instance to both views. A tracker fits each spectrum once and answers from its own record afterwards, so two views cost one fit per spectrum rather than two.

The fit options are the analysis’s own, so the lines followed during the sweep are the lines reported after it. Without line positions (peaks) or a prominence to find them nothing is tracked: every bump of the noise would otherwise be a line, and the views simply show none.

Variables:

options – The fit options, or None to read them from each run’s own configuration.

Parameters:
  • options (Optional[Mapping[str, Any]])

  • at (Sequence[str])

  • counts (str)

  • wavelength (str)

  • angle (str)

  • label (str)

  • fit_key (str)

  • budget (int)

Say where the sweep is in the records, and how its lines are found.

Parameters:
  • options (Optional[Mapping[str, Any]]) – SpectrumFit options, or None to take them from the run’s frozen configuration.

  • at (Sequence[str]) – Keys leading to the fit options in that configuration, used only when options is None. The default is where an experiment that configures a plesty.fss.FineStructure under analysis keeps them.

  • counts (str) – Record field holding each spectrum’s counts.

  • wavelength (str) – Record field holding its wavelength axis, in nm.

  • angle (str) – Record field holding the rotator angle in degrees; "" when the run does not record one.

  • label (str) – Record field labelling the sweep — the quantum dot, say. A spectrum with a different label starts a new sweep.

  • fit_key (str) – Record field naming the analysis’s saved line fit. Once a run has written one there is nothing left to track: the analysis fitted every spectrum of the sweep already, and its answer is the one the run reports. Reading it costs one file instead of re-fitting the whole sweep.

  • budget (int) – How many spectra one sweep() may fit. Fitting happens inside a view’s draw, on the thread that answers the window, so an unbounded call is a window that stops answering for as long as the backlog takes. The rest are fitted on the calls that follow. 0 removes the bound, for a caller that is not drawing — a script, or a test that wants the whole sweep in one go.

options = None
at = ('analysis', 'fit_options')
counts = 'counts'
wavelength = 'wavelength'
angle = ''
label = ''
fit_key = 'line_fit_document'
budget = 4
_analysed: dict[str, SweepSoFar | None]
_trackers: dict[str, LineTracker | None]
tracker(data: Any) → LineTracker | None

The tracker that finds this run’s lines, built once per run.

Parameters:

data (Any) – The run’s data.

Returns:

The tracker, or None when this run gives it nothing to find lines by.

Return type:

Optional[LineTracker]

_configured(data: Any) → dict[str, Any]

Return the fit options the run’s own configuration carries at at.

Parameters:

data (Any)

Return type:

dict[str, Any]

sweep(data: Any) → SweepSoFar | None

The lines of the current sweep, as far as it has been measured.

While the sweep runs, every spectrum committed so far is handed to the tracker in order; it fits each one once and answers from its own record afterwards, so calling this from both views costs one fit per spectrum.

Once the analysis has written its line fit there is nothing left to track, and the tracker is not used at all. That matters for opening a finished run: re-fitting ninety spectra to draw a marker on one of them took seconds before the first frame appeared, and produced a worse answer than the analysis had already saved beside it.

Parameters:

data (Any) – The run’s data.

Returns:

The sweep so far, or None when nothing is tracked or no spectrum has been committed.

Return type:

Optional[SweepSoFar]

_analysis(data: Any) → SweepSoFar | None

The sweep as the analysis fitted it, or None before it wrote one.

Read once per run and kept: a view redrawn twice a second must not re-parse the document each tick.

Parameters:

data (Any) – The run’s data.

Returns:

The fitted positions of every line in every spectrum of the sweep.

Return type:

Optional[SweepSoFar]

class plesty.fss.monitor.SweepSoFar

The lines of a sweep as a LineTracker knows them after one spectrum.

Variables:
  • label – Label of the sweep, e.g. the quantum dot, or None.

  • positions – Position of each line in nm, shortest wavelength first.

  • angles – Rotator angle of every spectrum so far, in degrees (nan if unknown).

  • centers – Fitted position of each line in each spectrum so far, (spectra, lines), nan where the fit failed or the line was not yet found.

label: str | None
positions: numpy.ndarray
angles: numpy.ndarray
centers: numpy.ndarray
property latest: numpy.ndarray

Fitted position of each line in the newest spectrum.

Return type:

numpy.ndarray

plesty.fss.monitor.LINE_COLORS: tuple[str, ...] = ('cyan', 'amber', 'violet', 'mint', 'rose', 'blue', 'text_bright', 'slate')
plesty.fss.monitor.line_color(index: int) → str

The colour of the index-th line, counting from the shortest wavelength.

Parameters:

index (int) – Which line.

Returns:

A palette token; the palette repeats for a sweep with more lines than it has colours, which is better than refusing to draw them.

Return type:

str

class plesty.fss.monitor.LineSpectrumView(tracking: plesty.fss.tracking.LineTracking, **kwargs: Any)

Bases: plesty.lib.monitor.common.Spectrum

The newest spectrum, with a marker on every line the fine structure follows.

Each line keeps its colour and its number — line 0, line 1, … — from FineStructureView, so a marker here and a point series there are the same line. A marker sits on the spectrum at the line’s fitted position; a line whose fit failed in this spectrum has none.

Every count is drawn as recorded. This is the library’s Spectrum with the markers added, so everything it takes — which fields hold the axis and the values, the axis labels, the colour of the curve — it takes here too.

Mark the lines tracking follows on the spectrum.

Parameters:
  • tracking (plesty.fss.tracking.LineTracking) – The run’s line tracking, shared with the fine-structure view so each spectrum is fitted once.

  • **kwargs (Any) – Forwarded to Spectrum.

title = 'Spectrum'
tracking
draw(data: Any) → plesty.lib.monitor.Drawing

Draw the newest spectrum, then mark each fitted line on it.

Parameters:

data (Any) – The run’s data.

Returns:

The curve, and one marker per line whose fit succeeded.

Return type:

plesty.lib.monitor.Drawing

class plesty.fss.monitor.FineStructureView(tracking: plesty.fss.tracking.LineTracking | None = None, *, fine_structure_key: str = 'fine_structure_document', fit_key: str = 'line_fit_document', angle_label: str = 'Rotator angle (deg)', title: str = '', name: str = '', weight: float | None = None, area: str = '')

Bases: plesty.lib.monitor.Plot

Energy shift of every line against the rotator angle, with its fit and result.

A splitting is an energy, and the wavelengths it separates differ in the fourth decimal: drawn in nanometres it is a flat line, drawn as a shift from each line’s mean position in µeV it is the measurement.

While the sweep runs the shift is measured from the mean so far, which moves as spectra arrive; once the analysis has written its result, its own fits, sines and splittings replace the running estimate. The text under the plot gives each line’s result in the line’s colour.

A class rather than a function because it remembers which analysis document it has already read — loading it on every tick would re-parse the same file twice a second for the rest of the run.

Draw the sweep tracking follows, and the analysis of it when there is one.

Parameters:
  • tracking (Optional[plesty.fss.tracking.LineTracking]) – The run’s line tracking, shared with the spectrum view; None shows nothing until the analysis is written.

  • fine_structure_key (str) – Record field naming the saved fine_structure result, relative to the run directory.

  • fit_key (str) – Record field naming the saved fit result.

  • angle_label (str) – Label of the angle axis, including the unit.

  • title (str) – What the view is called.

  • name (str) – Layout key.

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

  • area (str) – Which side of the window it starts on.

title = 'Fine structure'
tracking = None
fine_structure_key = 'fine_structure_document'
fit_key = 'line_fit_document'
angle_label = 'Rotator angle (deg)'
_channels: dict[str, Any] | None = None
_loaded_from: tuple[str, str] = ('', '')
analysis(data: Any) → dict[str, Any] | None

What the analysis made of the sweep, or None before it has written it.

Parameters:

data (Any) – The run’s data.

Returns:

The channels of sweep_channels(), read once per document.

Return type:

Optional[dict[str, Any]]

draw(data: Any) → plesty.lib.monitor.Drawing

Draw the analysis when there is one, else the sweep so far.

Parameters:

data (Any) – The run’s data.

Returns:

One point series per line, its fitted sine once analysed, and one line of result text per line.

Return type:

plesty.lib.monitor.Drawing

_draw_sweep(data: Any) → plesty.lib.monitor.Drawing

Each line’s shift from its mean so far, without fitted sines.

Parameters:

data (Any)

Return type:

plesty.lib.monitor.Drawing

_draw_analysis(channels: dict[str, Any]) → plesty.lib.monitor.Drawing

The analysis’s own fits, sines and results.

Parameters:

channels (dict[str, Any])

Return type:

plesty.lib.monitor.Drawing

plesty.fss.monitor.line_drawing(channels: dict[str, Any], index: int, angle_label: str, label: str = '') → list[Any]

One analysed line: its measured shifts as points, and its fitted sine.

Shared by FineStructureView and the analysis report (plesty.fss.report), so the figure in a report is the view on screen.

Parameters:
  • channels (dict[str, Any]) – What sweep_channels() computed.

  • index (int) – Which line.

  • angle_label (str) – Label of the angle axis, including the unit.

  • label (str) – Legend label; line <index> by default.

Returns:

A Scatter of the points and a Line of the sine, in the line’s colour.

Return type:

list[Any]

plesty.fss.monitor.sweep_channels(fine_structure: plesty.fss.data.FineStructureData, fit: Any) → dict[str, Any]

Compute what the view draws from the analyzer’s fine_structure and fit outputs.

Parameters:
Returns:

angle_deg (points), shift_ueV (lines × points, nan where a frame’s fit failed), curve_angle_deg, curve_ueV (lines × curve points) and summary: the result of each line on its own text line, in line order.

Raises:

ValueError – If the fit carries no rotator angle or the two outputs describe different lines.

Return type:

dict[str, Any]