Settings and presets
Every pipeline stage past Stage 0 has knobs — noise smoothing widths,
edge-coherence widths, line shape, decay-time bounds, rescue SNR cutoffs.
ftmwpipeline exposes those knobs through a single layered resolution
chain, at the level of detail a given workflow needs:
one-off experiments: pass keyword arguments to a stage function;
recurring instrument workflows: load a preset YAML by name and override one or two knobs from the CLI;
programmatic sweeps: build a settings dataclass in Python and pass it as
settings=.
All three surfaces resolve to the same settings, so the same parameters produce the same result whichever drives the analysis.
How a stage’s parameters resolve
A stage’s parameters come from layers that are merged per field. Highest precedence first:
explicit — a settings dataclass passed as
settings=(plus the handful of genuine non-knob keyword arguments a stage accepts, such asshapeon the fit)persisted — what the previous run of this stage on this
.ftmwfile usedpreset — a
.ymlpreset loaded by name or path withpreset=recommended — an upstream-stage hint (e.g., Stage 2b’s
recommended_shapefor Stage 5); empty for stages without an upstream feederhard defaults — the library’s stock values
Each knob walks this chain independently. A value you set in an explicit
settings= bundle wins; everything else falls through one layer at a
time until it hits a concrete value, and the hard defaults are
guaranteed to fill any remaining gap so the resolved settings instance is
always complete.
A .yml preset never overrides a value persisted in the .ftmw
file. The preset layer sits below the persisted layer: a preset only
seeds fields the file has not already fixed. This is what keeps a shared
.ftmw reproducible — the file reproduces the same result on its own,
regardless of any preset a recipient happens to have loaded. To force a
value regardless of what the file holds, pass it explicitly through
settings=.
The same template applies to every stage that exposes knobs. The five settings dataclasses, in pipeline order:
Stage |
Settings dataclass |
Sub-blocks |
Persisted at |
|---|---|---|---|
1 |
|
flat (no sub-blocks) |
|
2 |
flat (no sub-blocks) |
|
|
2b |
|
|
|
3 |
|
|
|
4 |
|
|
|
5 |
|
|
Each row is independent: you can tune Stage 2 noise smoothing without touching Stage 5, override Stage 3 SNR cutoffs without re-running the τ calibration, and so on. The persisted layer for one stage is unrelated to the persisted layer for another.
Three ways to drive a stage
The three input surfaces work identically across every stage. The
examples below use Stage 5 (the fit step) because it has the most
visible knobs; substitute estimate_noise, calibrate_tau,
detect_peaks, assign_windows, or fit_peaks and the same
patterns apply.
Stock defaults
The simplest call uses every hard default — Lorentzian shape,
max_decay_factor=5, rescue.max_rounds=5, and the rest of the
documented stock values:
import ftmwpipeline.api as ftmw
ftmw.fit_peaks("exp.ftmw")
Equivalent at the CLI:
ftmwpipeline fit run exp.ftmw
One-off overrides
At the command line, each stage exposes a flag per knob. A flag beats every other layer for its field; unspecified knobs flow through the chain unchanged:
ftmwpipeline fit run exp.ftmw --shape gaussian --max-decay-factor 3.0
Each flag is reconstructed into the stage’s settings object before the
stage runs, so a flag and the corresponding settings= field are the
same explicit override reached by two routes.
In Python, a few knobs survive as first-class convenience keyword
arguments (the fit’s shape and its τ-override pair), part of the
explicit layer as well:
ftmw.fit_peaks("exp.ftmw", shape="gaussian")
Every other knob is set through a settings= dataclass (below) or a
preset YAML.
Presets
A preset is a named bundle of knob values that brings an experiment or
instrument’s recipe under version control. A preset can cover one stage
or several stages at once: a single YAML file can carry a stage2:
block, a stage2b: block, a stage3: block, a stage4: block,
and a stage5: block side-by-side. Each stage’s loader reads only
its own block and ignores the rest, so one preset can drive a complete
instrument-specific recipe.
ftmwpipeline ships exactly one packaged preset, defaults: every
pipeline knob written at its package-wide default value, and nothing else.
Applying it is a no-op — --preset defaults reproduces exactly what the
pipeline does with no preset at all — so its purpose is documentation. It is
the canonical, copy-and-edit starting point: open it, delete the blocks you
do not care about, and change the few values you want to pin.
Use it by bare name (mostly to read it; as a recipe you would copy and edit it first):
ftmwpipeline fit run exp.ftmw --preset defaults
Or, the usual workflow, load a YAML file you wrote yourself by path:
ftmwpipeline fit run exp.ftmw --preset ./my_lab_recipe.yaml
The same preset name (or path) passed to any stage CLI subcommand loads only that stage’s block:
ftmwpipeline noise run exp.ftmw --preset ./my_lab_recipe.yaml
ftmwpipeline tau run exp.ftmw --preset ./my_lab_recipe.yaml
ftmwpipeline peaks run exp.ftmw --preset ./my_lab_recipe.yaml
ftmwpipeline windows run exp.ftmw --preset ./my_lab_recipe.yaml
ftmwpipeline fit run exp.ftmw --preset ./my_lab_recipe.yaml
A stage whose block is missing from the preset loads an empty
XxxSettings and falls through to the next layer of the resolver —
nothing breaks.
A preset and explicit overrides compose: the explicit value wins per field, so you can adopt a preset’s recipe and tweak one knob in the same call. At the command line, that is a preset plus a per-knob flag:
ftmwpipeline fit run exp.ftmw \
--preset ./my_lab_recipe.yaml \
--max-residual-rescue-rounds 3
The flag lands in the explicit layer; the preset seeds the preset layer beneath it (and beneath anything the file has already persisted).
Preset YAML format
A preset is a YAML mapping with up to five top-level stage blocks —
stage2 (noise), stage2b (τ calibration), stage3 (peaks),
stage4 (windows), stage5 (the fit) — plus optional name and
description metadata that the loader carries but ignores. Within a
stage block, knobs are grouped into the same named sub-blocks the settings
dataclass uses (for example stage5 has tau, seeder,
conservative, penalties, rescue, thaw, spur,
baseline, doublet_alternative, peak_survival; stage2 is
flat, with no sub-blocks). Every field is optional: omit a knob and it
falls through the resolver; write it and it is pinned at the preset layer.
An unknown stage block, sub-block, or field name is rejected with an error
so typos surface loudly rather than silently doing nothing.
name: my_lab_recipe
description: my instrument, tuned
stage2:
window_mhz: 120.0 # flat — Stage 2 has no sub-blocks
stage3:
promotion:
min_snr: 4.0
stage5:
shape: gaussian # see the note below before pinning this
tau:
max_decay_factor: 4.0
For the meaning, type, and suggested values of every knob, read the
shipped defaults preset (it lists them all at their defaults) or run
ftmwpipeline settings show for a file’s resolved values and
ftmwpipeline scan list for the tunable knobs and their sweep grids.
Line shape and the Stage 2b recommendation
The defaults preset leaves stage5.shape unset for a reason. Stage 2b
runs a three-way lineshape
vote (stage2b.recommendation.auto_recommend, default true) and
stamps the winning shape on the .ftmw as the recommended layer.
Stage 5 consumes that recommendation only when ``stage5.shape`` is
unset — because a pinned shape: sits in the higher-precedence preset
layer and silently overrides the recommendation. So:
leave
stage5.shapeout to let Stage 2b choose the shape (the out-of-the-box behavior);set
stage5.shape: lorentzianorgaussianto force it, knowingly bypassing the vote.
Declaring the instrument clock tree
The stage5.spur.clocks block declares an instrument’s clock-source
fundamentals so the spur detector can use the locked intermod lattice
instead of the generic integer-MHz anchor (and add a drift lane for any
unlocked source). The defaults preset ships this commented out as a
template; the Blackchirp 2638 instrument is shown as a worked example:
stage5:
spur:
clocks:
- {freq_mhz: 5120, locked: true, label: synth-downconv}
- {freq_mhz: 5760, locked: true, label: synth-upconv}
- {freq_mhz: 16000, locked: true, label: awg}
- {freq_mhz: 8000, locked: true, label: awg-half}
- {freq_mhz: 6250, locked: false, label: scope-adc}
Declare chain fundamentals only — harmonics and products derive. The same
declaration can be supplied at import from a clocks.csv sidecar instead;
see Declaring Instrument Clocks.
Constructing settings in Python
For programmatic sweeps (comparing several knob variants from a notebook, or building a recipe at runtime), construct the relevant settings dataclass directly:
from ftmwpipeline.core.stage_fit_settings import (
StageFitSettings, ShapeSpec
)
from ftmwpipeline.core.peak_shape import PeakShape
s = StageFitSettings(shape=ShapeSpec(kind=PeakShape.GAUSSIAN))
s.tau.max_decay_factor = 3.0
s.rescue.max_rounds = 3
ftmw.fit_peaks("exp.ftmw", settings=s)
Same shape for any stage:
from ftmwpipeline.core.noise_settings import NoiseSettings
s = NoiseSettings()
s.window_mhz = 120.0
s.smoothing_mhz = 600.0
ftmw.estimate_noise("exp.ftmw", settings=s)
from ftmwpipeline.core.peak_detection_settings import PeakDetectionSettings
s = PeakDetectionSettings()
s.promotion.min_snr = 4.0
s.savgol.sg_window = 13
ftmw.detect_peaks("exp.ftmw", settings=s)
settings= and preset= populate different layers, so you can
pass both in one call. A settings= bundle is the explicit layer
and outranks the persisted record; a preset= YAML is the preset
layer and is outranked by it. Reach for settings= to force values
regardless of what the file holds, and for preset= to supply a recipe
that defers to anything already chosen on the file. When both are given,
the settings= fields win per field and the preset seeds the rest:
s = StageFitSettings()
s.rescue.max_rounds = 3
ftmw.fit_peaks("exp.ftmw", preset="./my_lab_recipe.yaml", settings=s)
Persistence and auto-inheritance
Every time a stage runs, its resolved settings are stamped into the
.ftmw file under the canonical record for that stage (see the
Persisted at column in the table above). The next call to the same
stage on that file inherits those settings unless you override them, so
a sequence like:
ftmwpipeline fit run exp.ftmw --preset ./my_lab_recipe.yaml
ftmwpipeline fit show exp.ftmw
# ... look at the fit, decide to tighten rescue ...
ftmwpipeline fit run exp.ftmw --max-residual-rescue-rounds 3
behaves as expected: the second fit keeps the settings from the first call’s preset and only tightens the rescue; the prior recipe need not be re-supplied.
The persisted block is structured to be inspectable on disk:
$ h5dump -A exp.ftmw | head -40
/processing_parameters/stage5_fit
@creation_time = "2026-05-27T17:42:11..."
@preset_name = "my_lab_recipe"
shape/
@kind = "gaussian"
tau/
@max_decay_factor = 5.0
@per_band_tau = TRUE
@tau_penalty_lambda = 500.0
...
conservative/
@significance = 0.05
@max_peaks = 8
...
rescue/
@max_rounds = 5
...
Every sub-block of every stage is its own HDF5 group so you can grep one
block in isolation. Unset fields use the __None__ sentinel string
(the same convention Stage 1’s FTSettings uses).
The preset_name attribute records the bare name (or path) you
supplied to --preset for that run. The reproducibility recipe is
straightforward: the resolved values plus that name describe the run
exactly.
Writing your own preset
A preset is a small YAML file. Each stage’s settings sit under a
top-level per-stage block (stage2:, stage2b:, stage3:,
stage4:, stage5:); blocks compose freely so one YAML can drive
the whole pipeline:
name: my_lab_recipe
description: |
Whatever your lab calls this recipe. Multi-line markdown ok.
stage2:
window_mhz: 120.0
smoothing_mhz: 600.0
stage2b:
stft:
n_seg: 10
polish:
polish_snr_cap: 9.0
gaussian:
snr_min: 20.0
stage3:
promotion:
min_snr: 4.0
weak_medium_snr: 12.0
savgol:
sg_window: 13
primary_pass:
primary_window: blackmanharris
stage4:
coherence:
edge_m: 64
edge_threshold: 8.0
clustering:
max_window_width_mhz: 40.0
contributor:
min_freeze_snr: 50.0
stage5:
shape: gaussian
tau:
max_decay_factor: 3.0
per_band_tau: true
conservative:
max_peaks: 6
rescue:
max_rounds: 3
snr_threshold: 3.0
A few rules:
Each per-stage block is independent — drop the ones you don’t need. A preset with only a
stage5:block leaves Stages 2, 2b, 3, and 4 on hard defaults; a preset with only astage2:block leaves Stage 5 alone, and so on.Inside a stage block, each sub-block (e.g.,
stage5.tau,stage3.promotion) holds a flat map offield_name: value. The fields available are listed in the Sub-blocks column of the table above and in each dataclass’s docstring.Stage 5’s
shapeaccepts the short form (shape: gaussian); the long form (shape: {kind: gaussian}) also works and leaves room for future shape-specific parameter blocks (e.g., Voigt).Only set fields you care about. Anything omitted stays
Noneso the resolver falls through to the next layer (probably the hard defaults).Unknown keys raise
ValueErrorat load time, so typos surface immediately rather than silently doing the wrong thing.nameanddescriptionat the top level are documentation — the parser preserves them but the stages don’t use them.
To use a YAML you wrote, pass its path:
ftmwpipeline fit run exp.ftmw --preset ./my_lab_recipe.yaml
To ship one alongside the package, drop it into
src/ftmwpipeline/presets/ and refer to it by bare name.
Cross-stage recommendations
The recommended layer of the resolution chain is where one stage
hands a hint to a later stage. The Stage 2b → Stage 5 path is the
canonical example: Stage 2b’s τ calibration writes a
recommended_shape attribute on its output group (lorentzian,
gaussian, or voigt), and Stage 5’s resolver reads it as the
recommended layer of the shape field. The attribute carries the
__None__ sentinel until Stage 2b’s 3-way L/G/V discriminator runs;
once it does, Stage 5 picks the recommendation up automatically — one
step weaker than the file’s persisted value, two steps weaker than an
explicit argument or preset. The pipeline recommends a line shape, but an
explicit or persisted choice always overrides it.
If the file has persisted shape: gaussian and Stage 2b later recommends
Lorentzian, the persisted value wins. An explicit --shape lorentzian
likewise always wins, regardless of the recommendation.
The recommended layer of the other four stages (2, 2b, 3, 4) is
reserved but currently empty — no upstream feeder produces a hint for
those stages yet. The layer is kept in every resolver’s signature so a
future cross-stage recommender (e.g., a Stage 1 T_active-driven
Stage 2 smoothing-window suggestion, or a Stage 2b τ_maj feeder
into Stage 4’s leakage.tau_us) can land without API churn.
Where to look in the codebase
Per-stage settings modules (all share the same architectural template):
ftmwpipeline.core.noise_settings—NoiseSettings, withresolve()andload_preset().ftmwpipeline.core.tau_calibration_settings—TauCalibrationSettings.ftmwpipeline.core.peak_detection_settings—PeakDetectionSettings.ftmwpipeline.core.window_planning_settings—WindowPlanningSettings.ftmwpipeline.core.stage_fit_settings—StageFitSettings.
The matching HDF5 persistence groups are the Persisted at column of the settings table above.
Other useful references:
src/ftmwpipeline/presets/— the packaged preset YAML files.ftmwpipeline.core.settings— the Stage 1FTSettingsprecedent the per-stage pattern extends.