Analyzer Framework

plesty.lib.analyzer formalizes the middle stage of the platform pipeline device drivers → analyzers → experiments: an Analyzer converts raw device data into meaningful data through a schema-declared, synchronous transform. Analyzers never talk to hardware — they consume data-layer objects produced by devices or experiments, and their results carry a provenance stamp recording how they were derived.

The contract

A subclass declares two class-level schemas and implements exactly one method, analyze():

from plesty.lib.analyzer import Analyzer
from plesty.lib.data import PlestyArray

class SpectrumNormalizer(Analyzer):
    input_schema = {
        "spectrum": {"dtype": "array_float", "unit": "counts",
                     "description": "Raw detector counts."},
    }
    output_schema = {
        "normalized": {"dtype": "array_float", "unit": "a.u.",
                       "description": "Counts scaled to peak 1."},
    }

    def analyze(self, spectrum: PlestyArray) -> dict:
        peak = float(spectrum.max()) or 1.0
        return {"normalized": PlestyArray(spectrum / peak)}

normalizer = SpectrumNormalizer()          # params/calibration go here
result = normalizer(spectrum=raw_counts)   # validating __call__
result["normalized"].unit                  # "a.u." — filled from the schema
result["normalized"].provenance            # {"analyzer": ..., "params": ...}

Calling the analyzer runs the base machinery around the transform:

  1. Input validation — names, dtypes, and shapes are checked against input_schema; plain arrays are coerced to PlestyArray with the schema unit.

  2. analyze() — the subclass transform, pure compute.

  3. Output validation — the returned dict must carry exactly the output_schema names; missing name/unit/description metadata is filled from the schema.

  4. Provenance stamp — every array/table result gets a provenance attribute: analyzer class, module, package version, and the constructor parameters.

Schema entries use the same dtype vocabulary as device schemas (resolve_dtype): float, int, str, bool, array_<numpy_dtype>, table2d, … Arrays may declare a shape where None marks a free dimension ([None, 2]); inputs may declare required: False.

Design rules

  • Synchronous by design. Analysis is CPU-bound compute, unlike the I/O orchestration of the experiment framework. Experiments needing concurrency wrap analyzer calls in an executor.

  • Calibration is a constructor parameter, never hidden module state — it lands in params and therefore in the provenance of every result.

  • No hardware access. An analyzer receives data-layer objects; it never imports device drivers.

  • Experiments call analyzers, not the other way around: a step method may instantiate an analyzer and persist its outputs through the normal run machinery — the provenance stamp survives in the result metadata.

  • GUI helpers stay outside the contract. Interactive tools (ROI selection, previews) live beside the analyzer and produce its inputs or parameters.

Contract gates: AnalyzerPipeline

plesty.lib.test.analyzer_pipeline.AnalyzerPipeline mirrors the device and experiment harnesses with five contract gates, run entirely on synthetic inputs generated from the schema:

Gate

Verifies

1 test_analyzer_subclass

Public Analyzer subclass, instantiable with the given params

2 test_schema_integrity

Both schemas non-empty, dtypes resolvable, shapes well-formed

3 test_analyze_signature

analyze() accepts exactly the declared input names

4 test_mock_roundtrip

Synthetic inputs produce outputs matching output_schema

5 test_provenance_stamped

Results carry the provenance stamp

# tests/test_pipeline.py
from plesty.lib.test.analyzer_pipeline import AnalyzerPipeline

PIPELINE = AnalyzerPipeline(MyAnalyzer, mock_inputs={"frame": example_image})

def test_mock_roundtrip():
    PIPELINE.test_mock_roundtrip()

Transforms that need realistic values (e.g. an image with actual features) override individual synthetic inputs via mock_inputs; everything else is generated (zeros of the declared shape, neutral scalars).