plesty.fss.tracking =================== .. py:module:: plesty.fss.tracking .. autoapi-nested-parse:: Following the lines of a polarization sweep while it is measured. :class:`LineTracker` fits the lines of one spectrum after another; :mod:`plesty.fss.line_fit` finds and fits them the way the analysis does. :class:`LineTracking` is what an experiment's viewer holds: it says which of that experiment's record fields hold the sweep, keeps one tracker per run, and answers both views of :mod:`plesty.fss.monitor` from a single fit per spectrum. Attributes ---------- .. autoapisummary:: plesty.fss.tracking.SAME_LINE_PIXELS Classes ------- .. autoapisummary:: plesty.fss.tracking.SweepSoFar plesty.fss.tracking.LineTracker plesty.fss.tracking.LineTracking Functions --------- .. autoapisummary:: plesty.fss.tracking._spectrum_key Module Contents --------------- .. py:data:: SAME_LINE_PIXELS :value: 3 .. py:class:: SweepSoFar The lines of a sweep as a :class:`LineTracker` knows them after one spectrum. :ivar label: Label of the sweep, e.g. the quantum dot, or ``None``. :ivar positions: Position of each line in nm, shortest wavelength first. :ivar angles: Rotator angle of every spectrum so far, in degrees (``nan`` if unknown). :ivar 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. .. py:attribute:: label :type: Optional[str] .. py:attribute:: positions :type: numpy.ndarray .. py:attribute:: angles :type: numpy.ndarray .. py:attribute:: centers :type: numpy.ndarray .. py:property:: latest :type: numpy.ndarray Fitted position of each line in the newest spectrum. .. py:class:: LineTracker(fit_options: Mapping[str, Any]) Find and fit the lines of a sweep while it is measured. Lines are found the way :class:`~plesty.lib.analyzer.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 :data:`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. :param fit_options: :class:`~plesty.lib.analyzer.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. .. py:attribute:: fixed :type: Optional[numpy.ndarray] :value: None .. py:attribute:: prominence .. py:attribute:: distance .. py:attribute:: average :value: '' .. py:attribute:: fit_options .. py:attribute:: fits :value: 0 .. py:attribute:: _seen :type: dict[str, SweepSoFar] .. py:attribute:: _label :type: Optional[str] :value: None .. py:attribute:: _wavelength :type: Optional[numpy.ndarray] :value: None .. py:attribute:: _spectra :type: list[numpy.ndarray] :value: [] .. py:attribute:: _angles :type: list[float] :value: [] .. py:attribute:: _rows :type: list[numpy.ndarray] :value: [] .. py:attribute:: _positions :type: numpy.ndarray .. py:method:: observe(channels: Mapping[str, Any]) -> SweepSoFar Add one spectrum and return the sweep so far. :param channels: 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. .. py:method:: _reset(label: Optional[str]) -> None Start a new sweep. .. py:method:: _update_lines() -> None Find the lines in the spectra so far; keep the columns of the lines that remain. .. py:method:: _fit(counts: numpy.ndarray) -> numpy.ndarray Fit the lines in one spectrum, from the previous spectrum's positions. .. py:function:: _spectrum_key(label: Optional[str], angle: float, wavelength: numpy.ndarray, counts: numpy.ndarray) -> str Return a key identifying one spectrum, so two views reading it share one fit. .. py:class:: LineTracking(options: Optional[Mapping[str, Any]] = 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. :ivar options: The fit options, or ``None`` to read them from each run's own configuration. Say where the sweep is in the records, and how its lines are found. :param options: :class:`~plesty.lib.analyzer.SpectrumFit` options, or ``None`` to take them from the run's frozen configuration. :param at: Keys leading to the fit options in that configuration, used only when *options* is ``None``. The default is where an experiment that configures a :class:`plesty.fss.FineStructure` under ``analysis`` keeps them. :param counts: Record field holding each spectrum's counts. :param wavelength: Record field holding its wavelength axis, in nm. :param angle: Record field holding the rotator angle in degrees; ``""`` when the run does not record one. :param label: Record field labelling the sweep — the quantum dot, say. A spectrum with a different label starts a new sweep. :param fit_key: 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. :param budget: How many spectra one :meth:`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. .. py:attribute:: options :value: None .. py:attribute:: at :value: ('analysis', 'fit_options') .. py:attribute:: counts :value: 'counts' .. py:attribute:: wavelength :value: 'wavelength' .. py:attribute:: angle :value: '' .. py:attribute:: label :value: '' .. py:attribute:: fit_key :value: 'line_fit_document' .. py:attribute:: budget :value: 4 .. py:attribute:: _analysed :type: dict[str, Optional[SweepSoFar]] .. py:attribute:: _trackers :type: dict[str, Optional[LineTracker]] .. py:method:: tracker(data: Any) -> Optional[LineTracker] The tracker that finds this run's lines, built once per run. :param data: The run's data. :returns: The tracker, or ``None`` when this run gives it nothing to find lines by. .. py:method:: _configured(data: Any) -> dict[str, Any] Return the fit options the run's own configuration carries at :attr:`at`. .. py:method:: sweep(data: Any) -> Optional[SweepSoFar] 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. :param data: The run's data. :returns: The sweep so far, or ``None`` when nothing is tracked or no spectrum has been committed. .. py:method:: _analysis(data: Any) -> Optional[SweepSoFar] 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. :param data: The run's data. :returns: The fitted positions of every line in every spectrum of the sweep.