All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SharedWaveformBaseline.cxx
Go to the documentation of this file.
1 /**
2  * @file icaruscode/PMT/Algorithms/SharedWaveformBaseline.cxx
3  * @brief Extracts and writes PMT waveform baselines (implementation file).
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date May 5, 2022
6  * @see icaruscode/PMT/Algorithms/SharedWaveformBaseline.h
7  */
8 
9 // library header
11 
12 // LArSoft libraries
15 
16 // framework libraries
17 #include "messagefacility/MessageLogger/MessageLogger.h"
18 
19 // C/C++ standard libraries
20 #include <vector>
21 #include <algorithm> // std::nth_element(), std::copy()
22 #include <iterator> // std::distance(), std::next()
23 #include <ostream>
24 #include <cmath> // std::round()
25 #include <cassert>
26 
27 
28 // -----------------------------------------------------------------------------
29 //--- Implementation
30 //------------------------------------------------------------------------------
31 namespace {
32 
33  /// Extracts the median of the collection between the specified iterators.
34  template <typename BIter, typename EIter>
35  auto median(BIter begin, EIter end) {
36 
37  using value_type = typename BIter::value_type;
38 
39  std::vector<value_type> data{ begin, end };
40  assert(!data.empty());
41 
42  auto const middle = data.begin() + data.size() / 2;
43  std::nth_element(data.begin(), middle, data.end());
44 
45  return *middle;
46 
47  } // median()
48 
49 
50  /**
51  * Returns iterator to the first sample outside `lower`-`upper` range
52  * (inclusive) which is the first of at least `maxLower` samples all below
53  * lower, or of at least `maxUpper` samples all above `upper`.
54  * If all samples are in that range, `end` is returned.
55  */
56  template <typename Iter>
57  Iter findOutOfBoundary(
58  Iter begin, Iter end,
59  typename Iter::value_type lower, typename Iter::value_type upper,
60  unsigned int maxLower, unsigned int maxUpper
61  ) {
62  assert(lower <= upper);
63  assert(maxLower > 0U);
64  assert(maxUpper > 0U);
65 
66  unsigned int nAbove = 0U;
67  unsigned int nBelow = 0U;
68  for (auto it = begin; it != end; ++it) {
69 
70  if (*it > upper) {
71  if (++nAbove >= maxUpper) return it - maxUpper + 1;
72  nBelow = 0U;
73  continue;
74  }
75 
76  if (*it < lower) {
77  if (++nBelow >= maxLower) return it - maxLower + 1;
78  nAbove = 0U;
79  continue;
80  }
81 
82  nAbove = nBelow = 0U;
83 
84  } // while
85 
86  return end;
87  } // findOutOfBoundary()
88 
89 
90  // ---------------------------------------------------------------------------
91  /// Helper for streaming waveforms (`std::cout << waveformIntro(waveform)`).
92  struct waveformIntro {
93  raw::OpDetWaveform const* waveform;
94  waveformIntro(raw::OpDetWaveform const* waveform): waveform(waveform) {}
95  waveformIntro(raw::OpDetWaveform const& waveform): waveform(&waveform) {}
96  };
97 
98 
99  /// Prints an "introduction" to the specified waveform.
100  std::ostream& operator<< (std::ostream& out, waveformIntro wf) {
101  if (wf.waveform) {
102  raw::OpDetWaveform const& waveform = *(wf.waveform);
103  out << "waveform channel="
104  << waveform.ChannelNumber() << " timestamp=" << waveform.TimeStamp()
105  << " size=" << waveform.size();
106  }
107  else out << "<no waveform>";
108  return out;
109  } // operator<< (waveformIntro)
110 
111  // ---------------------------------------------------------------------------
112 
113 } // local namespace
114 
115 
116 //------------------------------------------------------------------------------
117 //--- opdet::SharedWaveformBaseline
118 //------------------------------------------------------------------------------
119 auto opdet::SharedWaveformBaseline::operator()
120  (std::vector<raw::OpDetWaveform const*> const& waveforms) const
121  -> BaselineInfo_t
122 {
123  if (waveforms.empty()) return {};
124 
125  //
126  // first pass: find statistics
127  //
128  std::vector<raw::ADC_Count_t> samples;
129  samples.reserve(fParams.nSample * waveforms.size());
130  std::vector<double> RMSs;
131  RMSs.reserve(waveforms.size());
132 
133  for (raw::OpDetWaveform const* waveform: waveforms) {
134 
135  mf::LogTrace{ fLogCategory } << "Now processing: " << waveformIntro(waveform);
136 
137  if (waveform->size() < fParams.nSample) {
138  mf::LogTrace{ fLogCategory } << waveformIntro(waveform)
139  << ": skipped because shorter than " << fParams.nSample
140  << " samples";
141  continue;
142  }
143 
144  auto const begin = waveform->cbegin();
145  auto const end = std::next(begin, fParams.nSample);
146 
148  for (auto it = begin; it != end; ++it) stats.add(*it);
149  RMSs.push_back(stats.RMS());
150 
151  std::copy(waveform->begin(), waveform->end(), back_inserter(samples));
152  } // for
153 
154  double const medRMS = median(RMSs.cbegin(), RMSs.cend());
155  raw::ADC_Count_t const med = median(samples.cbegin(), samples.cend());
156 
157  mf::LogTrace{ fLogCategory } << "Stats of channel "
158  << waveforms.front()->ChannelNumber() << " from "
159  << fParams.nSample << " starting samples of " << waveforms.size()
160  << " waveforms: median=" << med << " ADC, median RMS of each waveform="
161  << medRMS << " ADC";
162 
163  //
164  // collect the samples
165  //
166  raw::ADC_Count_t const aboveThreshold
167  = med + static_cast<raw::ADC_Count_t>(std::round(medRMS * fParams.nRMS));
168  raw::ADC_Count_t const belowThreshold
169  = med - static_cast<raw::ADC_Count_t>(std::round(medRMS * fParams.nRMS));
170 
171  auto const sampleOutOfBoundary
172  = [this, aboveThreshold, belowThreshold](auto begin, auto end)
173  {
174  return findOutOfBoundary(begin, end,
175  belowThreshold, aboveThreshold,
176  fParams.nExcessSamples, fParams.nExcessSamples);
177  };
178 
180  unsigned int nUsedWaveforms = 0U;
181  for (raw::OpDetWaveform const* waveform: waveforms) {
182 
183  auto const begin = waveform->cbegin();
184  auto const end = std::next(begin, fParams.nSample);
185 
186  //
187  // check whether to use this waveform
188  //
189  auto const firstExcess = sampleOutOfBoundary(begin, end);
190  if (firstExcess != end) {
191 
192  mf::LogTrace log { fLogCategory };
193  log
194  << waveformIntro(waveform) << " has " << fParams.nExcessSamples
195  << " samples in a row out of [ " << belowThreshold << " ; "
196  << aboveThreshold << " ] ADC starting at sample #"
197  << (firstExcess - begin) << ":";
198  for (
199  auto it = firstExcess; it != firstExcess + fParams.nExcessSamples; ++it
200  )
201  log << " " << *it;
202 
203  // should we try to recover part of the waveform here? e.g.
204  /*
205  if (((firstExcess - begin) < fParams.nSample*3/5) || (waveforms.size() >= 5))
206  continue;
207  end = begin + fParams.nSample / 2;
208  */
209 
210  } // if
211 
212  //
213  // include it
214  //
215  ++nUsedWaveforms;
216  stats.add_unweighted(begin, end);
217 
218  } // for
219 
220  return {
221  stats.Average() // baseline
222  , medRMS // RMS
223  , nUsedWaveforms // nWaveforms
224  , (unsigned int) stats.N() // nSamples
225  };
226 
227 } // opdet::SharedWaveformBaseline::operator()
228 
229 
230 //------------------------------------------------------------------------------
Classes gathering simple statistics.
Weight_t RMS() const
Returns the root mean square.
Weight_t Average() const
Returns the value average.
fDetProps &fDetProps fDetProps &fDetProps fLogCategory
int N() const
Returns the number of entries added.
void add_unweighted(Iter begin, Iter end)
Adds entries from a sequence with weight 1.
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
Extracts and writes PMT waveform baselines.
auto begin(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:573
T copy(T const &v)
std::ostream & operator<<(std::ostream &out, lar::example::CheatTrack const &track)
Definition: CheatTrack.h:153
void add(Data_t value, Weight_t weight=Weight_t(1.0))
Adds one entry with specified value and weight.