# An Analyzer An **analyzer** turns recorded data into a result, without touching an instrument. This page writes one that finds the wavelength where a power scan peaks, runs it on the scan from [An Experiment](experiment.md), and saves what it found as a report. ## Write the analyzer An analyzer is a class based on `Analyzer`. It declares: - **`input_schema`**: the inputs it takes, each with a type, a unit and a description; - **`output_schema`**: the outputs it gives, described the same way; - **`analyze()`**: the calculation, taking the inputs as arguments and returning a dictionary with one entry per output. ```python # power_peak.py import numpy as np from plesty.lib.analyzer import Analyzer from plesty.lib.data import PlestyArray class PowerPeak(Analyzer): input_schema = { "wavelength": {"dtype": "array_float", "unit": "nm", "description": "Scan wavelengths."}, "power": {"dtype": "array_float", "unit": "W", "description": "Power at each wavelength."}, } output_schema = { "normalized": {"dtype": "array_float", "unit": "a.u.", "description": "Power scaled to a peak of 1."}, "peak_wavelength": {"dtype": "float", "unit": "nm", "description": "Wavelength of the largest power."}, } def analyze(self, wavelength: PlestyArray, power: PlestyArray) -> dict: peak = float(power.max()) or 1.0 return { "normalized": PlestyArray(power / peak), "peak_wavelength": float(wavelength[int(np.argmax(power))]), } ``` `"array_float"` means an array of floating-point numbers, and `"float"` a single number. [Data & Metadata Schemas](../common/data_schemas.md) lists every type an analyzer can take and give. `PlestyArray` is a NumPy array that also carries a name, a unit and a description. You can pass a plain NumPy array as an input: the analyzer converts it and attaches the unit from `input_schema`. ## Call it You do not call `analyze()` yourself. Call the analyzer object, and it checks your data on the way in and out: 1. the inputs must match `input_schema`: right names, right types; 2. `analyze()` runs; 3. the outputs must match `output_schema`, and any unit or description missing from them is taken from it; 4. every array in the outputs records how it was made: by which analyzer, which version of its package, and with which settings. This record is called its **provenance**. ```python import numpy as np from power_peak import PowerPeak analyzer = PowerPeak() outputs = analyzer(wavelength=np.array([500.0, 600.0, 700.0]), power=np.array([0.002, 0.008, 0.004])) print(outputs["peak_wavelength"]) # 600.0 print(outputs["normalized"].unit) # a.u. print(outputs["normalized"].provenance) # {'analyzer': 'PowerPeak', ...} ``` ## Run it on a recorded scan The records of a run (see [Run it and read the result](experiment.md#run-it-and-read-the-result)) are dictionaries, one per finished step. Collect the values the analyzer needs into arrays and call it: ```python import numpy as np from plesty.lib.experiment import Run, latest_run from power_peak import PowerPeak run = Run(latest_run("runs", name="powerscan")) records = list(run.records()) outputs = PowerPeak()( wavelength=np.array([r["wavelength_nm"] for r in records]), power=np.array([r["power_w"] for r in records]), ) print(outputs["peak_wavelength"]) ``` ## Save a report A **report** is a document of what an analysis found: tables, figures and text. `report(outputs)` makes one; by default it has a table of every output. Save it as a file, and anyone can turn it into HTML or LaTeX later, without having your analyzer installed: ```python from pathlib import Path from plesty.lib.report import REPORT_SUFFIX, Report path = Path(run.path) / f"power_peak{REPORT_SUFFIX}" # REPORT_SUFFIX is ".report.json" PowerPeak().report(outputs).save(path) html = Report.load(path).to_html() # or .to_latex() ``` To write your own report, with a sentence about the result, a table laid out for a paper, or a plot, override `report()` in your analyzer. [Reports](../analysis/reports.md) shows how. ## Advanced topics | You want to … | Read | |---|---| | know every rule an analyzer's schemas and outputs must follow | [The contract](../analysis/writing.md#the-contract) | | know which parts of the analyzer API will not change in later plesty-lib versions | [What will not change](../analysis/writing.md#what-will-not-change) | | write your own report: a sentence on the result, a table laid out for a paper, a figure | [Reports](../analysis/reports.md) | | take a spectrum, or another structured measurement, as input | [Experiment data](../common/data_schemas.md#experiment-data) | | fit the peaks of a spectrum with the analyzer that comes with plesty-lib | [SpectrumFit](../analysis/spectrum_fit.md) | | test your analyzer automatically | [Testing an Analyzer](../testing/analyzer.md) |