mist-hep 0.1.0
ROOT-backed analysis helpers built on mist
Loading...
Searching...
No Matches
waveform.h
Go to the documentation of this file.
1// SPDX-License-Identifier: MIT
2//
3// mist/hep/signal/waveform.h — signal/waveform analysis on TGraph.
4//
5// Feature extraction and filtering for sampled waveforms represented as a
6// TGraph of (time, amplitude). Header-only. Functions that return a new
7// graph hand back an owning mist::hep::owned::root_ptr; query functions
8// return plain values and never allocate.
9//
10// Salvaged from the ePIC SiPM-characterisation laser/waveform utilities
11// (mist-hep:F-49..F-54), cleaned: the original find_peaks created and drew
12// into a TCanvas as a side-effect — removed here; gauss_filter was a
13// mislabelled plain average — implemented as a real Gaussian-weighted
14// window.
15//
16#pragma once
17
18#include <cmath>
19#include <cstddef>
20#include <limits>
21#include <utility>
22#include <vector>
23
24#include <TGraph.h>
25
26#include <mist/hep/owned.h>
27
29
30namespace owned = ::mist::hep::owned;
31
32// ---------------------------------------------------------------------------
33// amplitude: maximum y over the time window (tmin, tmax). Returns 0 if no
34// point falls in the window (matching the SiPM-original convention).
35// ---------------------------------------------------------------------------
36[[nodiscard]] inline double
37amplitude(const TGraph& g, double tmin, double tmax)
38{
39 double amp = 0.0;
40 for (int i = 0; i < g.GetN(); ++i) {
41 const double x = g.GetPointX(i);
42 if (x > tmin && x < tmax && g.GetPointY(i) > amp) amp = g.GetPointY(i);
43 }
44 return amp;
45}
46
47// ---------------------------------------------------------------------------
48// extremum: {x, y} of the maximum (or minimum, if find_min) over [min_x,
49// max_x]. Passing min_x == max_x scans the whole graph. Returns {NaN, NaN}
50// for an empty graph.
51// ---------------------------------------------------------------------------
52[[nodiscard]] inline std::pair<double, double>
53extremum(const TGraph& g, bool find_min = false,
54 double min_x = 0.0, double max_x = 0.0)
55{
56 const bool whole = (min_x == max_x);
57 const double sign = find_min ? -1.0 : 1.0;
58 double best_x = std::nan(""), best_y = std::nan("");
59 double best = -std::numeric_limits<double>::infinity();
60 for (int i = 0; i < g.GetN(); ++i) {
61 const double x = g.GetPointX(i);
62 if (!whole && (x < min_x || x > max_x)) continue;
63 const double v = sign * g.GetPointY(i);
64 if (v > best) { best = v; best_x = x; best_y = g.GetPointY(i); }
65 }
66 return {best_x, best_y};
67}
68
69// ---------------------------------------------------------------------------
70// threshold_crossings: x positions where the signal crosses `threshold`.
71// `direction` > 0 detects rising crossings, < 0 falling. Uses a hysteresis
72// arm at half-threshold to avoid re-triggering on noise (port of the SiPM
73// get_transitions logic). Assumes time-ordered points.
74// ---------------------------------------------------------------------------
75[[nodiscard]] inline std::vector<double>
76threshold_crossings(const TGraph& g, double threshold, double direction = 1.0)
77{
78 std::vector<double> crossings;
79 bool armed = false;
80 for (int i = 0; i < g.GetN(); ++i) {
81 const double y = g.GetPointY(i);
82 if (!armed && direction * y > direction * threshold * 0.5) continue;
83 armed = true;
84 if (direction * y < direction * threshold) continue;
85 crossings.push_back(g.GetPointX(i));
86 armed = false;
87 }
88 return crossings;
89}
90
91// ---------------------------------------------------------------------------
92// first_above / last_above: the first / last {x, y} where the signal exceeds
93// `threshold` (or falls below it, if negative = true). {NaN, NaN} if none.
94// ---------------------------------------------------------------------------
95[[nodiscard]] inline std::pair<double, double>
96first_above(const TGraph& g, double threshold, bool negative = false)
97{
98 const double sign = negative ? -1.0 : 1.0;
99 for (int i = 0; i < g.GetN(); ++i)
100 if (sign * g.GetPointY(i) > sign * threshold)
101 return {g.GetPointX(i), g.GetPointY(i)};
102 return {std::nan(""), std::nan("")};
103}
104
105[[nodiscard]] inline std::pair<double, double>
106last_above(const TGraph& g, double threshold, bool negative = false)
107{
108 const double sign = negative ? -1.0 : 1.0;
109 for (int i = g.GetN() - 1; i >= 0; --i)
110 if (sign * g.GetPointY(i) > sign * threshold)
111 return {g.GetPointX(i), g.GetPointY(i)};
112 return {std::nan(""), std::nan("")};
113}
114
115// ---------------------------------------------------------------------------
116// maximum_filter: non-overlapping blocks of `block_size` points, each
117// reduced to the (x, y) of the block's maximum. Returns an owned graph.
118// n == 0 returns an empty graph.
119// ---------------------------------------------------------------------------
120[[nodiscard]] inline owned::root_ptr<TGraph>
121maximum_filter(const TGraph& g, std::size_t block_size)
122{
123 auto out = owned::make<TGraph>();
124 if (block_size == 0) return out;
125 const int n = g.GetN();
126 const int step = static_cast<int>(block_size);
127 for (int i = 0; i < n; i += step) {
128 double best_x = g.GetPointX(i), best_y = g.GetPointY(i);
129 for (int j = i + 1; j < std::min(i + step, n); ++j)
130 if (g.GetPointY(j) > best_y) { best_y = g.GetPointY(j); best_x = g.GetPointX(j); }
131 out->SetPoint(out->GetN(), best_x, best_y);
132 }
133 return out;
134}
135
136// ---------------------------------------------------------------------------
137// gaussian_filter: sliding-window smoother with Gaussian weights, window of
138// `window` points (advancing one at a time), standard deviation `sigma` (in
139// points). Output length is N - window + 1. Unlike the SiPM original — which
140// was labelled "gauss" but computed a plain mean — this applies real
141// Gaussian weighting. window == 0 returns an empty graph.
142// ---------------------------------------------------------------------------
143[[nodiscard]] inline owned::root_ptr<TGraph>
144gaussian_filter(const TGraph& g, std::size_t window, double sigma)
145{
146 auto out = owned::make<TGraph>();
147 const int n = g.GetN();
148 const int w = static_cast<int>(window);
149 if (w == 0 || w > n || sigma <= 0.0) return out;
150
151 const double centre = 0.5 * (w - 1);
152 for (int i = 0; i + w <= n; ++i) {
153 double sum_w = 0.0, sum_x = 0.0, sum_y = 0.0;
154 for (int j = 0; j < w; ++j) {
155 const double d = (j - centre) / sigma;
156 const double weight = std::exp(-0.5 * d * d);
157 sum_w += weight;
158 sum_x += weight * g.GetPointX(i + j);
159 sum_y += weight * g.GetPointY(i + j);
160 }
161 out->SetPoint(out->GetN(), sum_x / sum_w, sum_y / sum_w);
162 }
163 return out;
164}
165
166// ---------------------------------------------------------------------------
167// integrate: sum of y^power over points whose x lies in [min_x, max_x]
168// (min_x == max_x integrates the whole graph). With use_bin_width = true each
169// term is multiplied by the (assumed uniform) sample spacing, giving a
170// trapezoid-free Riemann estimate of the integral of y^power. A simple
171// charge/energy proxy for waveforms.
172// ---------------------------------------------------------------------------
173[[nodiscard]] inline double
174integrate(const TGraph& g, double min_x, double max_x,
175 int power = 1, bool use_bin_width = false)
176{
177 if (g.GetN() < 2) return 0.0;
178 const bool whole = (min_x == max_x);
179 const double dx = use_bin_width ? (g.GetPointX(1) - g.GetPointX(0)) : 1.0;
180 double integral = 0.0;
181 for (int i = 0; i < g.GetN(); ++i) {
182 const double x = g.GetPointX(i);
183 if (!whole && (x < min_x || x > max_x)) continue;
184 integral += dx * std::pow(g.GetPointY(i), power);
185 }
186 return integral;
187}
188
189// ---------------------------------------------------------------------------
190// peak: a detected local maximum — its coordinates and point index.
191// ---------------------------------------------------------------------------
192struct peak {
193 double x;
194 double y;
195 int index;
196};
197
198// ---------------------------------------------------------------------------
199// find_peaks: local maxima whose rise above the neighbours `half_window`
200// points away (on both sides) is at least `min_prominence`. After a hit the
201// scan skips `half_window` points to avoid reporting the same peak twice.
202// `negative = true` finds troughs. Pure: no canvas, no drawing (the SiPM
203// original drew into a new TCanvas as a side-effect).
204// ---------------------------------------------------------------------------
205[[nodiscard]] inline std::vector<peak>
206find_peaks(const TGraph& g, double min_prominence,
207 int half_window = 10, bool negative = false)
208{
209 std::vector<peak> peaks;
210 const int n = g.GetN();
211 if (half_window < 1) half_window = 1;
212 const double sign = negative ? -1.0 : 1.0;
213
214 for (int i = 0; i < n; ++i) {
215 const double y = sign * g.GetPointY(i);
216 const int lo = std::max(0, i - half_window);
217 const int hi = std::min(n - 1, i + half_window);
218 const double y_lo = sign * g.GetPointY(lo);
219 const double y_hi = sign * g.GetPointY(hi);
220 if (y - y_lo < min_prominence) continue;
221 if (y - y_hi < min_prominence) continue;
222 peaks.push_back({g.GetPointX(i), g.GetPointY(i), i});
223 i += half_window; // don't re-report the same peak
224 }
225 return peaks;
226}
227
228} // namespace mist::hep::signal
Definition owned.h:33
std::unique_ptr< T, root_deleter > root_ptr
Definition owned.h:60
root_ptr< T > make(Args &&... args)
Definition owned.h:67
Definition waveform.h:28
std::pair< double, double > first_above(const TGraph &g, double threshold, bool negative=false)
Definition waveform.h:96
owned::root_ptr< TGraph > maximum_filter(const TGraph &g, std::size_t block_size)
Definition waveform.h:121
std::pair< double, double > last_above(const TGraph &g, double threshold, bool negative=false)
Definition waveform.h:106
std::vector< peak > find_peaks(const TGraph &g, double min_prominence, int half_window=10, bool negative=false)
Definition waveform.h:206
owned::root_ptr< TGraph > gaussian_filter(const TGraph &g, std::size_t window, double sigma)
Definition waveform.h:144
double integrate(const TGraph &g, double min_x, double max_x, int power=1, bool use_bin_width=false)
Definition waveform.h:174
std::pair< double, double > extremum(const TGraph &g, bool find_min=false, double min_x=0.0, double max_x=0.0)
Definition waveform.h:53
double amplitude(const TGraph &g, double tmin, double tmax)
Definition waveform.h:37
std::vector< double > threshold_crossings(const TGraph &g, double threshold, double direction=1.0)
Definition waveform.h:76
Definition waveform.h:192
double x
Definition waveform.h:193
double y
Definition waveform.h:194
int index
Definition waveform.h:195