Deferred out of #71 / #73. No demand yet — filed so the design is not re-derived from scratch when it is wanted. Sibling of #77 (which covers making parameters configurable; this one covers disclosing them).
Problem
A spectrogram or PSD figure does not say what was done to the data to produce it. Two different jobs hide in that gap:
- Citation — the processing chain a reader would paste into a methods section. Constant, boring, always the same for a given config.
- Exception report — something happened to the data that the user did not ask for: timestamps regularised, gaps masked, windows dropped. Rare, and the thing that actually changes how a figure should be read.
Mixed into one string, the exception hides inside the boilerplate. Kept separate, the absence of the second line is itself information.
Sketch
Citation (always): Hann 4.00 s · 50 % · demeaned · Δf 0.25 Hz · 256 Hz · dB re µV²/Hz
Caveat (only when): resampled to 256.0 Hz (jitter 8 %) · 3 gaps masked, 41 s ·
12 of 47 windows dropped
Subplot titles already exist (signal_container.py) with 80–90 px of inter-subplot gap budgeted, so a second <sub> line is close to free.
Design notes worth keeping
Effective values, never requested ones. n_window = max(round(window_s/dt), 2) and n_step = max(round(n_window*(1-overlap)), 1) both round. A requested window_s: 4.0 at 249 Hz really runs 3.996 s; a requested overlap: 0.5 on a 7-sample window really runs 0.571. Printing the request would be provenance theatre carrying our authority.
Hence a carrier object, not recomputation at the plot layer. That same rounding means the caller cannot re-derive the facts — a caller recomputing from requested values would print a plausible-looking lie. A frozen SpectralProvenance dataclass threaded build_uniform_grid → stft → _framed_power → the public functions, emitting numbers rather than sentences, with formatting near the plot layer and literals in constants.py. Two optional PlotOptions fields carry the finished strings.
Facts and where each becomes available:
build_uniform_grid — effective dt, whether the resample branch was taken, measured jitter, gap count, total gap duration
stft — n_window, n_step, effective window length, Δf, frame count
psd — frames dropped into gaps
Report magnitude, not a binary alarm (jitter 8 %, not "⚠ jitter"). JITTER_TOLERANCE = 0.05 was tuned against a recording with zero jitter, so it has never been tested against a case that exceeds it. If real ICU timestamps wobble 6–10 %, every plot from that device carries a warning, users habituate within a week, and silence stops meaning anything. Resampling at 6 % is also harmless (0.24 ms at 250 Hz). Reporting the measured number avoids inventing a second unvalidated constant to patch the first.
Intersection/difference split for overlaid PSD traces. The subtitle carries facts identical across every trace in the subplot; facts that differ between traces move to that trace's legend name and hover. Caveats stay subplot-level and unioned.
Trigger to pick this up
Someone asks "what exactly did you do to my data", or tries to describe a ClinicalScope figure in a publication. Until then the tutorial's Known limitations says the figure does not carry this and to keep the database_options file alongside it.
Related
An ADR may be worth writing alongside this, recording the durable rule — defaults track scipy.signal.welch; a divergence needs a stated reason, and every effective parameter is printed on the figure. Held off for now: with a single instance it would be an ADR about one decision. Worth writing when a second spectral default comes up for debate. Distinct from ADR-0006, which answers "what may we compute?" rather than "how do we choose and disclose what we don't expose?".
Deferred out of #71 / #73. No demand yet — filed so the design is not re-derived from scratch when it is wanted. Sibling of #77 (which covers making parameters configurable; this one covers disclosing them).
Problem
A spectrogram or PSD figure does not say what was done to the data to produce it. Two different jobs hide in that gap:
Mixed into one string, the exception hides inside the boilerplate. Kept separate, the absence of the second line is itself information.
Sketch
Subplot titles already exist (
signal_container.py) with 80–90 px of inter-subplot gap budgeted, so a second<sub>line is close to free.Design notes worth keeping
Effective values, never requested ones.
n_window = max(round(window_s/dt), 2)andn_step = max(round(n_window*(1-overlap)), 1)both round. A requestedwindow_s: 4.0at 249 Hz really runs 3.996 s; a requestedoverlap: 0.5on a 7-sample window really runs 0.571. Printing the request would be provenance theatre carrying our authority.Hence a carrier object, not recomputation at the plot layer. That same rounding means the caller cannot re-derive the facts — a caller recomputing from requested values would print a plausible-looking lie. A frozen
SpectralProvenancedataclass threadedbuild_uniform_grid→stft→_framed_power→ the public functions, emitting numbers rather than sentences, with formatting near the plot layer and literals inconstants.py. Two optionalPlotOptionsfields carry the finished strings.Facts and where each becomes available:
build_uniform_grid— effectivedt, whether the resample branch was taken, measured jitter, gap count, total gap durationstft—n_window,n_step, effective window length, Δf, frame countpsd— frames dropped into gapsReport magnitude, not a binary alarm (
jitter 8 %, not "⚠ jitter").JITTER_TOLERANCE = 0.05was tuned against a recording with zero jitter, so it has never been tested against a case that exceeds it. If real ICU timestamps wobble 6–10 %, every plot from that device carries a warning, users habituate within a week, and silence stops meaning anything. Resampling at 6 % is also harmless (0.24 ms at 250 Hz). Reporting the measured number avoids inventing a second unvalidated constant to patch the first.Intersection/difference split for overlaid PSD traces. The subtitle carries facts identical across every trace in the subplot; facts that differ between traces move to that trace's legend name and hover. Caveats stay subplot-level and unioned.
Trigger to pick this up
Someone asks "what exactly did you do to my data", or tries to describe a ClinicalScope figure in a publication. Until then the tutorial's Known limitations says the figure does not carry this and to keep the
database_optionsfile alongside it.Related
An ADR may be worth writing alongside this, recording the durable rule — defaults track
scipy.signal.welch; a divergence needs a stated reason, and every effective parameter is printed on the figure. Held off for now: with a single instance it would be an ADR about one decision. Worth writing when a second spectral default comes up for debate. Distinct from ADR-0006, which answers "what may we compute?" rather than "how do we choose and disclose what we don't expose?".