plesty.lib.analyzer.base_analyzer ================================= .. py:module:: plesty.lib.analyzer.base_analyzer .. autoapi-nested-parse:: Synchronous Analyzer ABC with schema-declared inputs/outputs and provenance. An :class:`Analyzer` converts raw device data into meaningful data (issue plesty-lib#23). It is the middle stage of the platform pipeline *device drivers → analyzers → experiments* and is deliberately **synchronous**: analysis is CPU-bound compute, unlike the I/O orchestration of :class:`~plesty.lib.experiment.Experiment`. Analyzers never talk to hardware — they consume data-layer objects produced by devices or experiments. Subclasses declare their interface with two class-level schemas using the same dtype vocabulary as device schemas (:func:`plesty.lib.data.types.resolve_dtype`), and implement a single :meth:`~Analyzer.analyze` method: .. code-block:: python 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)} result = SpectrumNormalizer()(spectrum=raw_counts)["normalized"] # result.unit == "a.u."; result.provenance records analyzer + params Calling the analyzer (``analyzer(**inputs)``) runs the base machinery: inputs are validated against ``input_schema``, :meth:`~Analyzer.analyze` executes, outputs are validated against ``output_schema`` with missing metadata (name/unit/description) filled from the schema, and every attribute-capable output (:class:`~plesty.lib.data.array.PlestyArray`, :class:`~plesty.lib.data.table.PlestyTable2D`) is stamped with a ``provenance`` dict recording the analyzer class, package version, and constructor parameters — calibration included, which is why calibration handles must be passed to ``__init__`` and never hidden in module state. Attributes ---------- .. autoapisummary:: plesty.lib.analyzer.base_analyzer._INPUT_ENTRY_KEYS plesty.lib.analyzer.base_analyzer._OUTPUT_ENTRY_KEYS plesty.lib.analyzer.base_analyzer._PROVENANCE_BASIC Exceptions ---------- .. autoapisummary:: plesty.lib.analyzer.base_analyzer.AnalyzerSchemaError plesty.lib.analyzer.base_analyzer.AnalysisValidationError Classes ------- .. autoapisummary:: plesty.lib.analyzer.base_analyzer.Analyzer Functions --------- .. autoapisummary:: plesty.lib.analyzer.base_analyzer._distribution_version plesty.lib.analyzer.base_analyzer._provenance_value Module Contents --------------- .. py:exception:: AnalyzerSchemaError Bases: :py:obj:`ValueError` Raised when an analyzer's schema declaration is invalid. Initialize self. See help(type(self)) for accurate signature. .. py:exception:: AnalysisValidationError Bases: :py:obj:`ValueError` Raised when analysis inputs or outputs violate the declared schema. Initialize self. See help(type(self)) for accurate signature. .. py:data:: _INPUT_ENTRY_KEYS .. py:data:: _OUTPUT_ENTRY_KEYS .. py:data:: _PROVENANCE_BASIC .. py:function:: _distribution_version(cls: type) -> str | None Best-effort version of the distribution that ships *cls*. A module ``plesty....`` maps to the distribution ``plesty-`` per the platform packaging convention. Falls back to the top-level package's ``__version__`` attribute, then to ``None``. .. py:function:: _provenance_value(value: Any) -> Any Coerce a constructor parameter into a provenance-safe representation. .. py:class:: Analyzer(**params: Any) Bases: :py:obj:`abc.ABC` Base class for PLESTY analyzers: schema-declared data transforms. Subclasses declare ``input_schema``/``output_schema`` and implement exactly one method, :meth:`analyze`. Analysis parameters (including calibration objects or references) are passed as keyword arguments to the constructor and stored on :attr:`params`, from where they enter the provenance stamp of every result. Schema entries map an input/output name to a dict with keys: * ``dtype`` — **required**: a dtype token understood by :func:`plesty.lib.data.types.resolve_dtype` (``float``, ``array_float``, ``table2d``, …). * ``unit`` — unit filled onto outputs that do not set one. * ``description`` — human-readable meaning; filled onto outputs. * ``shape`` — arrays only: expected shape; ``None`` entries are free dimensions, e.g. ``[None, 2]``. * ``required`` — inputs only: whether the input must be supplied (default ``True``). Store the analysis parameters and validate the schema declarations. :param \*\*params: Analysis parameters and calibration handles. They are introspectable via :attr:`params` and recorded in the provenance stamp of every result. .. py:attribute:: input_schema :type: ClassVar[dict[str, dict[str, Any]]] name → schema entry. :type: Declared inputs of :meth:`analyze` .. py:attribute:: output_schema :type: ClassVar[dict[str, dict[str, Any]]] name → schema entry. :type: Declared outputs of :meth:`analyze` .. py:attribute:: params :type: dict[str, Any] Analysis parameters passed to the constructor (calibration included). .. py:method:: analyze(**inputs: Any) -> dict[str, Any] :abstractmethod: Transform the schema-declared inputs into the schema-declared outputs. Implementations receive validated inputs as keyword arguments and return a dict with exactly the ``output_schema`` names as keys. Output metadata (name/unit/description) may be left unset — the base machinery fills it from the schema. :param \*\*inputs: Validated inputs, one keyword per ``input_schema`` name. :returns: Mapping of output name to result value. .. py:method:: __call__(**inputs: Any) -> dict[str, Any] Validate inputs, run :meth:`analyze`, validate and stamp outputs. :param \*\*inputs: One keyword per ``input_schema`` name; optional inputs may be omitted. :returns: Mapping of output name to result value; array/table results carry a ``provenance`` attribute. :raises AnalysisValidationError: If inputs or outputs violate the schema. .. py:method:: provenance() -> dict[str, Any] Return the provenance stamp: analyzer identity, version, parameters. .. py:method:: _validate_schema(schema: dict[str, dict[str, Any]], allowed_keys: frozenset[str], label: str) -> None :staticmethod: Validate a schema declaration, raising :class:`AnalyzerSchemaError`. .. py:method:: _validate_inputs(inputs: dict[str, Any]) -> dict[str, Any] Check input names, dtypes, and shapes against ``input_schema``. .. py:method:: _finalize_outputs(outputs: Any) -> dict[str, Any] Validate outputs, fill schema metadata, and stamp provenance. .. py:method:: _conform(name: str, value: Any, entry: dict[str, Any], direction: str) -> Any Check one value against its schema entry, coercing raw arrays. .. py:method:: _conform_array(name: str, value: Any, entry: dict[str, Any], direction: str) -> plesty.lib.data.array.PlestyArray Coerce array-likes to :class:`PlestyArray` and check the shape. .. py:method:: _conform_scalar(name: str, value: Any, expected: type, direction: str) -> Any :staticmethod: Check a scalar value, accepting ints where floats are declared.