Author: Chao Peng (Argonne National Laboratory)
prad2dec provides two offline analyzers for raw FADC250 samples
(uint16_t[nsamples], 4 ns/sample at 250 MHz):
| Analyzer | Class | Purpose |
|---|---|---|
| Waveform | fdec::WaveAnalyzer |
Robust local-maxima peak finding for HyCal energy and time observables. Tolerates noisy pedestals; supports multiple peaks per channel. |
| Firmware emulator | fdec::Fadc250FwAnalyzer |
Bit-faithful emulation of the FADC250 firmware Mode 1/2/3 (Hall-D V3 with NSAT/NPED/MAXPED extensions) for offline–firmware cross-checks. |
Both analyzers are stack-allocated and execute side-by-side under
prad2ana_replay_rawdata -p. The remainder of this note describes the
algorithms on a representative waveform with parameter values from
database/daq_config.json.
100 samples (400 ns); a bright pulse on a quiet 146 ADC baseline, followed by a slow scintillation tail.
| Feature | Value |
|---|---|
| Length | 100 samples (400 ns) |
| Baseline | 146 ADC (samples 0–29) |
| Pulse onset | sample 30 (t = 120 ns) |
| Peak | sample 32, 1393 ADC |
| Rise time | 8 ns (cross → peak) |
| Tail | exponential decay; ≈ 20 ADC above baseline at sample 99 |
This is representative of a HyCal PbWO₄ signal: a fast leading edge (≈ 10 ns) followed by a long PMT/scintillator tail.
The analyzer is tuned for production data with realistic baseline contamination. Each design choice below addresses a specific failure mode of the naive "mean ± σ pedestal + first local maximum" recipe:
- Median + MAD pedestal seed. A previous-event tail or early ringing biases a simple-mean seed and inflates its σ-clip band. Median + MAD·1.4826 remains correct under up to 50 % contamination (§ Robustness).
- Adaptive pedestal window. When the leading window flags as
contaminated, the analyzer estimates the pedestal on the trailing
samples and selects whichever has the lower RMS. The choice is
recorded in
Q_PED_TRAILING_WINDOW. - Per-channel pedestal quality bitmask.
Q_PED_*flags expose the pedestal trustworthiness without re-running the analyzer (§ Pedestal quality). - Pedestal slope. Least-squares slope on the surviving samples detects baseline drift that σ-clipping alone misses.
- Noise-scaled local-maximum tolerance.
trend()usesmax(0.1, 0.5·rms)to suppress spurious mini-peaks on quiet channels. - N-consecutive tail termination. Integration ends only after
tail_break_nconsecutive sub-threshold samples, preventing premature truncation by single noise dips. - Inclusive integration bounds.
peak.left/peak.rightare the outermost samples summed intopeak.integral; the JS viewer shading uses the same convention. - Sub-sample peak time. A 3-point quadratic vertex fit refines the peak time below the 4 ns sample grid.
- Pile-up flagging. Peaks whose integration windows lie within
peak_pileup_gapof each other are flagged withQ_PEAK_PILED(§ Peak quality).
The pipeline below walks through each step on the example trace;
figures and numerical values are reproduced by
scripts/plot_wave_analysis.py.
Parameters are defined in fdec::WaveConfig
(prad2dec/include/WaveAnalyzer.h); defaults are shown.
| Field | Default | Unit | Role |
|---|---|---|---|
smooth_order |
2 | order | Triangular kernel order; 1 disables smoothing, N gives a 2N − 1 tap kernel. |
peak_nsigma |
5.0 | × pedestal RMS | Sigma-scaled peak detection threshold above the local baseline. |
min_peak_height |
10.0 | ADC counts | Absolute floor on the detection threshold. Effective threshold is max(peak_nsigma·rms, min_peak_height). |
min_peak_ratio |
0.3 | fraction | When two peaks overlap, a secondary survives only if its smoothed height is ≥ this fraction of the primary. |
int_tail_ratio |
0.1 | fraction | Integration stops when the pedsub waveform falls below r · peak_height. |
tail_break_n |
2 | samples | Required consecutive sub-threshold samples to terminate integration. |
peak_pileup_gap |
2 | samples | Two peaks within this many samples are both flagged Q_PEAK_PILED. |
ped_nsamples |
30 | samples | Window length for the pedestal estimate. |
ped_flatness |
1.0 | ADC counts | Floor on the σ-clip band: samples kept iff ` |
ped_max_iter |
3 | iterations | Maximum σ-clip passes; terminates early on mask convergence or fewer than 5 survivors. |
overflow |
4095 | ADC counts | 12-bit overflow value; peaks at this height are tagged. |
clk_mhz |
250.0 | MHz | Sample rate for time conversion t = pos · 1000/clk_mhz (ns). |
The fadc250_waveform.analyzer block in
database/daq_config.json overrides
any subset of the defaults. Replay, viewer servers, and filter paths
all consume the analyzer through evc::DaqConfig::wave_cfg, so a single
edit propagates to every consumer.
1. Triangular smoothing. With smooth_order = N, the kernel is
buf[i] = (raw[i−1]·w + raw[i] + raw[i+1]·w) / (1 + 2w) with
w = 1 − 1/(N + 1). N = 1 disables smoothing; the kernel half-width
is N − 1.
2. Iterative pedestal. On the first ped_nsamples samples of the
smoothed buffer:
- Seed
meanandrmswith the median and MAD·1.4826 of the window. - Iterate up to
ped_max_iterpasses: drop samples deviating by more thanmax(rms, ped_flatness)from the mean; recompute statistics on the survivors. - Record
nused(surviving sample count), aQ_PED_*quality bitmask, and a least-squares slope (ADC/sample) on the survivors.
For the example trace: mean = 145.61, rms = 0.45, nused = 28,
slope ≈ 0, quality = Q_PED_GOOD.
2b. Adaptive window. If the leading window flags as suspicious
(Q_PED_NOT_CONVERGED, Q_PED_TOO_FEW_SAMPLES, Q_PED_OVERFLOW, or
nused < ped_nsamples / 2) and the buffer is long enough that the
trailing window does not overlap the leading one, the analyzer
estimates the pedestal on the trailing samples and adopts whichever
estimate has the lower RMS (with nused as tiebreaker). The trailing
choice is flagged with Q_PED_TRAILING_WINDOW.
3. Threshold. thr = max(peak_nsigma · rms, min_peak_height).
4. Local-maxima search. The smoothed buffer is scanned for local maxima. A peak is accepted if all three conditions hold:
- it is a local maximum (the
trend()flat-tolerance scales withrmsso noise wiggles do not fragment plateaus); - its height above the local baseline (linear interpolation between
surrounding minima) exceeds
thr; - its height above the pedestal mean exceeds
thr.
5. Integration. Walking outward from the peak, the analyzer sums
pedsub samples until either the value drops below tail_cut = int_tail_ratio · peak_height or below rms. Termination requires
tail_break_n consecutive sub-threshold samples; isolated dips are
held in a pending queue and either committed on recovery or
discarded at termination. peak.left and peak.right are the
inclusive integration bounds.
6. Raw-position correction. pos is recorded as the raw-sample
maximum near the smoothed peak so that peak.height reflects the
actual ADC value rather than a smoothed under-estimate.
7. Sub-sample peak time. A quadratic vertex through
raw[pos − 1, pos, pos + 1] yields
δ = (h_{−1} − h_{+1}) / (2(h_{−1} − 2h_0 + h_{+1})), clamped to
δ ∈ [−1, 1] and applied only when the parabola is concave-down.
The reported time is peak.time = (pos + δ) · 1000 / clk_mhz (ns).
Time resolution improves from the 4 ns sample grid to ≪ 1 ns for
clean peaks.
8. Pile-up flagging. Each newly accepted peak is compared against
all previously accepted peaks; if either window comes within
peak_pileup_gap samples of the other, both peaks receive
Q_PEAK_PILED.
For the example trace:
| Field | Value |
|---|---|
peak.pos |
32 |
peak.time |
126.6 ns (pos + δ = −0.36) |
peak.height |
1247 ADC |
[peak.left, peak.right] |
[29, 48] (20 samples, inclusive) |
peak.integral |
9600 (ADC·sample) |
peak.quality |
Q_PEAK_GOOD |
Each peak carries an 8-bit quality mask (defined in
prad2dec/include/Fadc250Data.h):
| Bit | Flag | Set when |
|---|---|---|
0 |
Q_PEAK_GOOD |
clean peak, no flags |
1<<0 |
Q_PEAK_PILED |
another peak's integration window lies within peak_pileup_gap samples; both peaks of the pair are flagged |
1<<1 |
Q_PEAK_DECONVOLVED |
height and integral have been overwritten by the pile-up deconvolution path (§ Pile-up deconvolution) |
Downstream filters can require peak.quality == Q_PEAK_GOOD for a
clean-pulse subset, or accept Q_PEAK_DECONVOLVED peaks (where the
deconvolution provides corrected amplitudes) while excluding raw
piled peaks.
The analyzer reports four scalars per channel describing the pedestal
estimate, written to the events tree as
hycal.ped_{mean,rms,nused,quality,slope}
(see docs/REPLAYED_DATA.md):
| Field | Type | Use |
|---|---|---|
ped_mean |
float |
Pedestal mean after rejection |
ped_rms |
float |
RMS after rejection |
ped_nused |
uint8 |
Surviving sample count (vs. ped_nsamples) |
ped_slope |
float |
LSQ drift (ADC/sample) on survivors |
ped_quality |
uint8 |
Q_PED_* bitmask |
| Bit | Flag | Set when |
|---|---|---|
0 |
Q_PED_GOOD |
clean estimate |
1<<0 |
Q_PED_NOT_CONVERGED |
ped_max_iter exhausted with kept-mask still moving |
1<<1 |
Q_PED_FLOOR_ACTIVE |
rms < ped_flatness (informational) |
1<<2 |
Q_PED_TOO_FEW_SAMPLES |
< 5 samples survived rejection |
1<<3 |
Q_PED_PULSE_IN_WINDOW |
findPeaks returned a peak with pos inside the pedestal window |
1<<4 |
Q_PED_OVERFLOW |
a raw window sample reached cfg.overflow |
1<<5 |
Q_PED_TRAILING_WINDOW |
adaptive window logic chose the trailing samples (informational) |
A clean-event filter is ped_quality == 0. To exclude only events
where the iterative cut failed,
ped_quality & (Q_PED_NOT_CONVERGED | Q_PED_TOO_FEW_SAMPLES | Q_PED_PULSE_IN_WINDOW) suffices.
The example trace has a clean baseline; algorithm robustness becomes relevant on contaminated baselines. The figure below uses a synthetic 30-sample window where the first 14 samples carry a 2.5 ADC bias (simulating a previous-event tail) on top of the same ±0.4 ADC noise:
- The simple-mean seed lies between the two clusters and pulls the σ-clip band along with it; iteration either locks onto the contaminated subset or rejects most samples and reports the biased seed.
- The median + MAD·1.4826 seed sits on the true baseline; a single σ-clip pass excludes the contaminated samples.
The contamination level is in the regime where σ-clip is most fragile (close to the true baseline, more than half the band). Stronger contamination is rejected by both seeds; the median's advantage lies on the marginal cases that bias the energy resolution silently.
The 100-sample readout (400 ns) typically contains more than one PbWO₄ pulse from accidentals, after-pulses, or beam-related multi-hits. The figure below shows three synthetic pulses at samples 20, 35, 50 with heights 800/600/350 ADC:
- Each pulse rises above its surrounding minima by more than
thrand is found independently by the local-maxima search. - The integration windows of adjacent pulses touch (the slow tail of
pulse N runs into the rising edge of pulse N+1). Step 8 sets
Q_PEAK_PILEDon both peaks of every such pair. - Downstream code may keep all peaks (the integrals remain correct
under the tail-cutoff stopping rule) or filter on
Q_PEAK_GOOD.
Q_PEAK_PILED is independent of the min_peak_ratio rejection
(§ Pipeline step 4): the latter decides whether a peak
is recorded at all; the former describes whether a recorded peak has
a close neighbour.
The local-maxima + tail-cutoff integral biases both height and
integral on Q_PEAK_PILED peaks: each pulse's tail lifts the
apparent baseline of the next, which in turn distorts its peak
height. When a per-type pulse template is available for the channel's
category (PbGlass / PbWO4 / LMS / Veto), the analyzer recovers the
underlying per-pulse amplitudes by per-event Levenberg–Marquardt fits
of a parametric two-tau template.
Model. A piled-up event with K peaks at times t_k is modeled
as a non-negative linear combination of K template instances:
s(t_i) = Σ_k a_k · T(t_i − τ_k; τ_r_k, τ_f_k)
T(·; τ_r, τ_f) is the two-tau pulse fitted by
fit_pulse_template.py. For each
peak the LM frees (a_k, t0_k, τ_r_k, τ_f_k) with the per-type
template providing the initial guess and tight bounds; the rise/fall
shapes are constrained to [τ/factor, τ·factor] of the template
medians (shape_window_factor), and t0_k is constrained to lie
within t0_window_ns of the WaveAnalyzer-reported peak time.
Non-negativity is enforced through bounded amplitude constraints
(a_k ∈ [0, amp_max_factor·peak.height]). K ≤ MAX_PEAKS = 8 in
production, so the LM solve is negligible per event.
Per-type template store
(PulseTemplateStore).
Loaded once at startup from
fit_pulse_template.py output. Two
pieces are consumed:
- The
_by_typeblock — one (τ_r, τ_f) median per category (PbGlass / PbWO4 / LMS / Veto). These are the templates the deconvolver uses, after re-validation against the analyzer'stau_*_range_nsgates. - Each per-channel record's
module_typefield — used only to classify the channel. Per-channel τ_r/τ_f are deliberately ignored; pulse shapes group cleanly by crystal type, and a well-fit category aggregate is more reliable than thousands of single-channel fits with varying statistics.
Lookup(roc_tag, slot, channel) resolves the channel's category and
returns the matching template. Channels with module_type = "Unknown"
or absent from the JSON receive a nullptr and are skipped. File or
parse errors leave the store invalid; analyzers fall back silently to
the local-maxima heights.
In-place output. When deconvolution converges, the affected Peak
objects are overwritten:
peak.height←a_k · T_maxpeak.integral←a_k · Σᵢ T(t_i − τ_k)over the per-peak windowpeak.quality |= Q_PEAK_DECONVOLVED
Failure paths (template missing, template out of validity range, LM non-convergence) leave the WaveAnalyzer values untouched.
Configuration. All knobs live under
fadc250_waveform.analyzer.nnls_deconv in
database/daq_config.json (the
historical name nnls_deconv is retained for compatibility; the
underlying solver is no longer NNLS):
"nnls_deconv": {
"enabled": false,
"template_file": "waveform/pulse_templates_024177.json",
"apply_to_all_peaks": false,
"tau_r_range_ns": [0.5, 15.0],
"tau_f_range_ns": [2.0, 100.0],
"shape_window_factor": 1.5,
"t0_window_ns": 8.0,
"amp_max_factor": 2.0,
"pre_samples": 8,
"post_samples": 40
}The shipped default is enabled: false while the per-type templates
remain under study. enabled and apply_to_all_peaks gate the
runtime path; tau_*_range_ns, shape_window_factor, t0_window_ns,
and amp_max_factor constrain the LM search; pre_samples and
post_samples set the per-peak integral window. template_file is
resolved against PRAD2_DATABASE_DIR.
Wiring. Every code path that uses WaveAnalyzer picks
deconvolution up automatically when (a) daq_config enables it,
(b) a PulseTemplateStore has been loaded, and (c) the analyzer is
bound to the store and the current channel key:
// once at setup
fdec::PulseTemplateStore store;
if (cfg.wave_cfg.nnls_deconv.enabled
&& !cfg.wave_cfg.nnls_deconv.template_file.empty()) {
store.LoadFromFile(db_dir + "/" + cfg.wave_cfg.nnls_deconv.template_file,
cfg.wave_cfg.nnls_deconv);
}
fdec::WaveAnalyzer ana(cfg.wave_cfg);
ana.SetTemplateStore(&store);
// per channel inside the existing loop
ana.SetChannelKey(roc.tag, slot, channel);
ana.Analyze(samples, n, wres); // auto-deconv when conditions metPer-peak deconvolved values are written into the same peak.height
and peak.integral fields the local-maxima path uses; consumers
distinguish the two paths via Q_PEAK_DECONVOLVED.
For visualising deconvolution on individual events, see
deconv_pileup_demo.py.
The Python binding wave_ana.deconvolve() runs whenever a valid
template is supplied, regardless of the production enabled flag.
The same input as
fig 7 — three PbWO₄-like
pulses at samples 20/35/50 with truth heights 800/600/350 ADC on a
146 ADC baseline. The script
scripts/plot_wave_deconv.py calls
fdec::WaveAnalyzer::Deconvolve through the bindings; every plotted
amplitude originates from the C++ solver.
- Open triangles —
WaveAnalyzer.peaks[k].heightfrom the pre-deconv local-maxima path. Tail bleed-through biases peak 1 (the trailing shoulder of pulse 0 lifts its baseline) and peak 2 (twice-piled). - Filled circles —
DeconvOutput.height[k] = a_k · T_max(τ_r_k, τ_f_k)from the LM fit, initialised on the per-type template(τ_r = 10 ns, τ_f = 48 ns). - Black crosses — the synthetic input heights (truth).
- Orange curve —
Σ a_k · T(t − t0_k; τ_r_k, τ_f_k)evaluated at the design-matrix sample times; passes through every filled circle by construction.
Numerical comparison (script stdout):
| Peak | Truth | WA height | Deconv height | Δ(WA) | Δ(deconv) |
|---|---|---|---|---|---|
| 0 | 800 | ≈ 800 | ≈ 800 | small | small |
| 1 | 600 | biased high | ≈ 600 | large | small |
| 2 | 350 | biased high | ≈ 350 | large | small |
Deconvolved heights agree with truth to within ≪ 1 % on the leading peak and a few percent on the buried peaks; the local-maxima method shows tens of percent bias on peaks 1 and 2.
The same script with --evio <path> scans a production EVIO file for
the first physics event with a Q_PEAK_PILED peak on a channel whose
module type is covered by the per-type template store and whose LM
deconvolution converges:
cd docs/technical_notes/waveform_analysis
python scripts/plot_wave_deconv.py --evio /data/evio/data/prad_024202/prad_024202.evio.00000The legend matches fig 8. Truth markers are absent in real data; the metric of interest is the magnitude of the WA → deconv shift on the trailing peak, which signals pile-up bias on the local-maxima estimate. The checked-in figure was generated from run 024202; rerun the script against any local EVIO file to regenerate against your own data.
Two parameters visibly change the analyzer's output on the example trace:
Pedestal ped_flatness × ped_max_iter. All 30 baseline samples
enter pass 1; samples deviating from the running mean by more than
max(rms, ped_flatness) = 1.0 are dropped, and the procedure
repeats. The band collapses onto the dominant cluster (146/147 ADC),
rejecting outliers (143/144/145/150). ped_flatness floors the
band to prevent over-tight clipping on quiet channels.
The demo runs the procedure on the raw samples for plot readability; the C++ runs it on the smoothed buffer, accounting for the 0.9 ADC difference in the converged mean. The kept/rejected pattern is identical.
int_tail_ratio. Integration walks outward from the peak and
stops when the pedsub waveform falls below r · peak_height:
int_tail_ratio |
Window | Samples | Integral |
|---|---|---|---|
| 0.20 | [30, 41] | 12 | 8376 |
| 0.10 (default) | [30, 47] | 18 | 9477 |
| 0.05 | [30, 57] | 28 | 10332 |
The default 0.10 captures the prompt peak plus the first ≈ 70 ns
of the tail. Smaller values recover more tail energy at the cost of
sensitivity to baseline drift and downstream pulses.
Smoothing — smooth_order. Smoothing is invisible at the scale
of a 1247 ADC peak; it matters on small-signal channels where
per-sample fluctuations are comparable to the pulse height. The
figure below uses a small ≈ 24 ADC bump on a baseline with ±3 ADC
noise:
smooth_order |
Spurious local maxima above +2 ADC | Smoothed peak height |
|---|---|---|
| 1 (raw) | 6 | 169 |
| 2 (default) | 3 | 166 |
| 4 | 1 | 162 |
smooth_order = 2 collapses the zig-zag without measurable peak
attenuation. smooth_order = 4 removes essentially all baseline
structure but clips the peak by ≈ 7 ADC and is appropriate only for
very low-S/N channels.
The remaining parameters affect this trace only marginally:
peak_nsigma · rms = 5 · 0.45 = 2.25 ADClies belowmin_peak_height = 10, so the floor wins on this trace; on noisier channels (rms ≳ 2 ADC) thepeak_nsigmarule activates.min_peak_ratioactivates only when peaks share an integration range.
The firmware analyzer reproduces the on-board pipeline so that
offline analysis can be cross-checked against firmware-reported
values without re-running the DAQ. The full algorithm specification
appears in
docs/clas_fadc/FADC250_algorithms.md;
this section walks through the parameters.
Parameters live under the fadc250_waveform.firmware block in
database/daq_config.json.
NSB and NSA are in nanoseconds, floored to whole 4 ns samples by
the analyzer; remaining fields are unitless or in ADC counts.
| Field | Unit | Role |
|---|---|---|
TET |
ADC counts | Trigger Energy Threshold above pedestal; pulse rejected if Vpeak − Vmin ≤ TET. |
NSB |
ns | Window before threshold crossing (Mode 2 integral); floored to whole samples. |
NSA |
ns | Window after threshold crossing; same flooring as NSB. |
NPEAK (= MAX_PULSES) |
— | Max pulses kept per channel per readout (1–4). |
NSAT |
samples | Required consecutive-above-TET samples after Tcross; rejects single-sample spikes. NSAT = 1 reproduces the legacy single-sample crossing. |
NPED |
samples | Number of leading samples summed for the Vnoise estimate. |
MAXPED |
ADC counts | Online outlier rejection on the Vnoise sum; 0 disables. |
CLK_NS |
ns | Sample period (4 ns at 250 MHz). |
Run-config defaults:
"fadc250_waveform": {
"firmware": {
"TET": 10.0, "NSB": 8, "NSA": 128, "NPEAK": 1,
"NSAT": 4, "NPED": 3, "MAXPED": 1, "CLK_NS": 4.0
}
}Step-by-step on the example waveform:
1. Pedestal estimate (Vnoise). Mean of the first NPED = 3
samples with MAXPED = 1 outlier filter (drop any sample whose
deviation from the running mean exceeds 1 ADC). For the trace:
(146 + 147 + 144) / 3 = 145.67; sample 1 (147) is filtered;
refined mean is 145.0.
2. Pulse search. Vmin = Vnoise. The buffer is scanned starting
at sample NPED; the first pulse is detected as soon as a sample
exceeds Vnoise and rises monotonically to a local maximum.
3. Acceptance. Vpeak = 1393; pedsub height is 1393 − 146 = 1247 ≫ TET = 10 → accepted.
4. Tcross. First leading-edge sample whose pedsub value exceeds
TET: sample 30, since 637 − 146 = 491 > 10.
5. NSAT gate. With NSAT = 4, samples 30, 31, 32, 33 must all
exceed TET. They are (491, 1221, 1247, 1093) → accepted.
NSAT = 1 reduces this gate to a no-op.
6. TDC — Va, bracket, fine time.
Va = Vmin + (Vpeak − Vmin) / 2 = 146 + (1393 − 146)/2 = 769.5
Find the bracket on the rising edge: smallest k with s[k] ≥ Va.
Here s[30] = 637 < 769.5, s[31] = 1367 ≥ 769.5 → k = 31,
giving Vba = 637, Vaa = 1367. Fine time:
fine = round( (Va − Vba) / (Vaa − Vba) · 64 )
= round( (769.5 − 637) / (1367 − 637) · 64 )
= round( 0.1815 · 64 ) = 12
coarse = k − 1 = 30
time_units = coarse · 64 + fine = 1932 (LSB = 62.5 ps)
time_ns = time_units · CLK_NS / 64 = 120.75 ns
In fig 2 (left), the dot-dash Va line crosses the rising edge
between the diamond Vba and square Vaa markers; the fine-time
arrow points from the Vba sample to the interpolated zero
crossing.
7. Mode-2 integral. Window
[cross − NSB_s, cross + NSA_s] with NSB_s = NSB / 4 = 2,
NSA_s = NSA / 4 = 32, giving [28, 62] (35 samples = 140 ns).
The integrand is s' = max(0, s − Vnoise):
Σ s'[28..62] = 10589 (pedsub ADC·sample)
The shaded band in fig 2 (right) is this sum.
8. Quality bitmask. 0x00 = Q_DAQ_GOOD for the example. Set bits
indicate:
| Bit | Flag | Condition |
|---|---|---|
1<<0 |
Q_DAQ_PEAK_AT_BOUNDARY |
peak at the last sample |
1<<1 |
Q_DAQ_NSB_TRUNCATED |
cross − NSB_s < 0; window clipped |
1<<2 |
Q_DAQ_NSA_TRUNCATED |
cross + NSA_s ≥ N; window clipped |
1<<3 |
Q_DAQ_VA_OUT_OF_RANGE |
Va not bracketed on the rising edge |
| Field | Waveform (WaveAnalyzer) |
Firmware (Fadc250FwAnalyzer) |
|---|---|---|
| Pedestal | 145.61 ± 0.45 (28/30 samples; median+MAD seed, σ-clip) | 145.0 (3 samples; MAXPED filter) |
| Time | 126.6 ns (peak vertex, sub-sample) | 120.75 ns (mid-amplitude rising edge) |
| Height | 1247 ADC | 1247 ADC |
| Integral window | [29, 48] (20 samples, tail-driven, inclusive) | [28, 62] (35 samples, fixed NSB/NSA) |
| Integral | 9600 | 10589 |
Time observables differ by construction: WaveAnalyzer.peak.time is
the peak-vertex time (sub-sample, ≪ 1 ns precision for clean peaks);
the firmware time is mid-amplitude on the rising edge (62.5 ps LSB).
The firmware's wider window captures more of the slow scintillation
tail; with NSA = 128 ns the integral window stops at sample 62,
excluding samples 63–99.
The figures and numerical values in this note are produced by
scripts that drive the C++ analyzers via the prad2py Python
bindings; no parallel Python re-implementation can drift from the
production code. Build with -DBUILD_PYTHON=ON so that
import prad2py resolves, then run from this directory:
cd docs/technical_notes/waveform_analysis
python scripts/plot_wave_analysis.py # figs 1–7
python scripts/plot_wave_deconv.py [--evio <path>] # figs 8–9The only Python re-implementations in the scripts are pedagogical:
a small σ-clip helper used by the robustness figure to demonstrate
the simple-mean seed (the binding only exposes the median + MAD
seed), and the two-tau evaluation
(1 − exp(−t/τ_r))·exp(−t/τ_f) for the model-trace overlay in
figs 8–9. Heights, integrals, and convergence flags all originate
from the C++ solver.
docs/clas_fadc/FADC250_algorithms.md— full firmware algorithm specification with manual cross-references.prad2dec/include/WaveAnalyzer.h,WaveAnalyzer.cpp— C++ source.prad2dec/include/Fadc250FwAnalyzer.h,Fadc250FwAnalyzer.cpp— C++ source.prad2dec/include/Fadc250Data.h—Peak/Pedestalstructs andQ_PEAK_*/Q_PED_*/Q_DAQ_*flag definitions.docs/REPLAYED_DATA.md— replay-tree branch layout.








