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_schemanames each input, with its type (dtype), unit and description.output_schemanames each output the same way.analyze()takes the inputs as keyword arguments and returns a dictionary with exactly the outputs ofoutput_schema.An analyzer may also override
report(), which describes what the analysis found; see Reports.
Calling the analyzer object runs four steps around analyze():
Check the inputs: their names, types and shapes must match
input_schema. A plain NumPy array becomes aPlestyArray(a NumPy array that also carries a name, unit and description) with the unit from the schema.Run
analyze().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.Record the provenance: every array or table in the outputs gets a
provenanceattribute naming the analyzer’s class, its module, its package’s version and the settings it was created with. A structured result such as aSpectrumDatakeeps 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):
|
Means |
|---|---|
|
a single value |
|
a NumPy array of that type |
|
a table |
the kind of a structured data type, such as |
that type, for example a |
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, notasync. An experiment that must not wait for it runs it in a thread (for example withasyncio.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.