Writing an Analyzer

An analyzer is a class based on Analyzer, from plesty.lib.analyzer. It declares what it takes and what it gives, and implements the calculation in one method. An Analyzer in the quick start builds one step by step.

The contract

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()          # settings, such as a calibration, go here
result = normalizer(spectrum=raw_counts)   # call the object, not analyze()
result["normalized"].unit                  # "a.u.", taken from output_schema
result["normalized"].provenance            # {"analyzer": ..., "params": ...}
  • input_schema names each input, with its type (dtype), unit and description.

  • output_schema names each output the same way.

  • analyze() takes the inputs as keyword arguments and returns a dictionary with exactly the outputs of output_schema.

  • An analyzer may also override report(), which describes what the analysis found; see Reports.

Calling the analyzer object runs four steps around analyze():

  1. Check the inputs: their names, types and shapes must match input_schema. A plain NumPy array becomes a PlestyArray (a NumPy array that also carries a name, unit and description) with the unit from the schema.

  2. Run analyze().

  3. Check the outputs: the returned dictionary must have exactly the names in output_schema. A missing name, unit or description is filled in from the schema.

  4. Record the provenance: every array or table in the outputs gets a provenance attribute naming the analyzer’s class, its module, its package’s version and the settings it was created with. A structured result such as a SpectrumData keeps the provenance it already had, and gains these entries.

Types in a schema

A schema’s dtype uses the same names as a device’s parameters (see Data & Metadata Schemas):

dtype

Means

float, int, str, bool

a single value

array_<numpy type>, such as array_float, array_uint16

a NumPy array of that type

table2d

a table

the kind of a structured data type, such as spectrum

that type, for example a SpectrumData (see Experiment data)

An array may declare a shape, where None means a dimension of any length: "shape": [None, 2] is any number of rows with two columns. An input may declare "required": False; analyze() then needs a default for it.

Rules

  • An analyzer only computes. It never talks to a device and never imports a device module; it receives data that a device or an experiment produced.

  • Settings are constructor arguments, never values hidden in the module. That is what puts them into the provenance of every result, so a result always says how it was made.

  • analyze() is an ordinary function, not async. An experiment that must not wait for it runs it in a thread (for example with asyncio.to_thread).

  • Experiments call analyzers, not the other way round: a step method creates an analyzer, calls it, and returns the outputs, which are then saved with the step.

  • Interactive tools stay outside. A window for choosing a region or previewing a result belongs next to the analyzer, producing its inputs or settings, not inside it.

What will not change

The contract above has not changed since plesty-lib 0.3.2, and an analyzer written against it will keep working. The parts that are fixed: the two schemas, the single analyze(), the four steps of calling the analyzer, the provenance, and the tests of Testing an Analyzer. New additions are possible, such as a new optional schema entry or a new dtype, since they do not break an existing analyzer. Anything else would take a new major version, announced in advance.

If the contract cannot express what your analyzer needs, open an issue on plesty-lib rather than working around it.