All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
PMTWaveformBaselines_module.cc
Go to the documentation of this file.
1 /**
2  * @file PMTWaveformBaselines_module.cc
3  * @brief Extracts and writes PMT waveform baselines.
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date September 14, 2020
6  */
7 
8 // ICARUS libraries
10 // #include "icaruscode/Utilities/DataProductPointerMap.h"
11 
12 // LArSoft libraries
14 #include "larcore/CoreUtils/ServiceUtil.h" // lar::providerFrom()
19 
20 // framework libraries
21 #include "art_root_io/TFileService.h"
22 #include "art_root_io/TFileDirectory.h"
23 #include "art/Framework/Core/ModuleMacros.h"
24 #include "art/Framework/Core/EDProducer.h"
25 #include "art/Framework/Principal/Event.h"
26 #include "art/Framework/Principal/Handle.h"
27 #include "art/Persistency/Common/PtrMaker.h"
28 #include "canvas/Persistency/Common/Assns.h"
29 #include "canvas/Persistency/Common/Ptr.h"
30 #include "canvas/Utilities/InputTag.h"
31 #include "cetlib_except/exception.h"
32 #include "messagefacility/MessageLogger/MessageLogger.h"
33 #include "fhiclcpp/types/Atom.h"
34 
35 // ROOT libraries
36 #include "TH2F.h"
37 #include "TGraph.h"
38 #include "TProfile.h"
39 
40 // C/C++ standard libraries
41 #include <vector>
42 #include <algorithm> // std::partial_sort_copy()
43 #include <iterator> // std::distance()
44 #include <memory> // std::make_unique(), std::allocator
45 #include <string>
46 #include <cmath> // std::ceil()
47 #include <cassert>
48 
49 //------------------------------------------------------------------------------
50 namespace icarus { class PMTWaveformBaselines; }
51 
52 /**
53  * @brief Extracts the baseline of PMT waveforms.
54  *
55  * This module produces a baseline data product for each optical detector
56  * waveform.
57  *
58  * The waveforms on the same channels are currently treated as independent
59  * (which is less than ideal).
60  *
61  *
62  * Output data products
63  * =====================
64  *
65  * * a data product of type `std::vector<icarus::WaveformBaseline>`, with one
66  * baseline per input waveform; the baselines are guaranteed to be in the same
67  * order as the waveforms in the input collection;
68  * * an _art_ association, one baseline per input optical waveform; normally
69  * this is not needed as long as the original PMT waveform data product is
70  * available; the type of the association is
71  * `art::Assns<icarus::WaveformBaseline, raw::OpDetWaveform>`.
72  *
73  *
74  * Output plots
75  * -------------
76  *
77  * * `Baselines`: baseline distribution, per channel; average baseline [ADC] per
78  * event per channel; all waveforms on the same channels in a single event
79  * contribute to the average, and channels with no waveforms in an event do
80  * not contribute an entry for that event.
81  *
82  *
83  *
84  * Input data products
85  * ====================
86  *
87  * * `std::vector<raw::OpDetWaveform>`: a single waveform for each recorded
88  * optical detector activity; the activity belongs to a single channel, but
89  * there may be multiple waveforms on the same channel.
90  *
91  *
92  * Service requirements
93  * ---------------------
94  *
95  * * `TFileService` and `Geometry` if `PlotBaselines` is enabled
96  *
97  *
98  * Configuration parameters
99  * =========================
100  *
101  * A terse description of the parameters is printed by running
102  * `lar --print-description PMTWaveformBaselines`.
103  *
104  * * `OpticalWaveforms` (input tag, mandatory): the data product containing all
105  * optical detector waveforms;
106  * * `OutputCategory` (string, default: `"PMTWaveformBaselines"`): label
107  * for the category of messages in the console output; this is the label
108  * that can be used for filtering messages via MessageFacility service
109  * configuration;
110  * * `PlotBaselines` (flag, default: `true`): whether to produce distributions
111  * of the extracted baselines.
112  * * `BaselineTimeAverage` (real number, default: `600.0`): binning of the
113  * baseline profile vs. time, in seconds. Requires `PlotBaselines` to be set.
114  *
115  */
116 class icarus::PMTWaveformBaselines: public art::EDProducer {
117 
118  public:
119 
120  // --- BEGIN Configuration ---------------------------------------------------
121  struct Config {
122 
123  using Name = fhicl::Name;
124  using Comment = fhicl::Comment;
125 
126  fhicl::Atom<art::InputTag> OpticalWaveforms{
127  Name("OpticalWaveforms"),
128  Comment("label of input digitized optical waveform data product")
129  };
130 
131  fhicl::Atom<std::string> OutputCategory {
132  Name("OutputCategory"),
133  Comment("tag of the module output to console via message facility"),
134  "PMTWaveformBaselines"
135  };
136 
137  fhicl::Atom<bool> PlotBaselines {
138  Name("PlotBaselines"),
139  Comment("produce plots on the extracted baseline"),
140  true
141  };
142 
143  fhicl::Atom<double> BaselineTimeAverage {
144  Name("BaselineTimeAverage"),
145  Comment("binning of the baseline profile vs. time [s]"),
146  [this](){ return PlotBaselines(); }, // enabled if `PlotBaselines` is set
147  600.0
148  };
149 
150  }; // struct Config
151 
152  using Parameters = art::EDProducer::Table<Config>;
153  // --- END Configuration -----------------------------------------------------
154 
155 
156  // --- BEGIN Constructors ----------------------------------------------------
157 
158  explicit PMTWaveformBaselines(Parameters const& config);
159 
160  // --- END Constructors ------------------------------------------------------
161 
162 
163  // --- BEGIN Framework hooks -------------------------------------------------
164  /// Prepares the plots to be filled.
165  virtual void beginJob() override;
166 
167  /// Creates the data products.
168  virtual void produce(art::Event& event) override;
169 
170  /// Remove empty plots.
171  virtual void endJob() override;
172 
173  // --- END Framework hooks ---------------------------------------------------
174 
175 
176  private:
177 
178  // --- BEGIN Configuration variables -----------------------------------------
179 
180  art::InputTag const fOpDetWaveformTag; ///< Input optical waveform tag.
181 
182  bool fPlotBaselines; ///< Whether to produce plots.
183 
184  /// Width of baseline time profile binning [s]
185  double const fBaselineTimeAverage { 0.0 };
186 
187  std::string const fLogCategory; ///< Category name for the console output stream.
188 
189  // --- END Configuration variables -------------------------------------------
190 
191 
192  // --- BEGIN Service variables -----------------------------------------------
193 
194  // --- END Service variables -------------------------------------------------
195 
196 
197  // --- BEGIN Algorithms ------------------------------------------------------
198 
199  // --- END Algorithms --------------------------------------------------------
200 
201  std::size_t fNPlotChannels = 0U; ///< Number of plotted channels
202  TH2* fHBaselines = nullptr; ///< All baselines, per channel.
203 
204  /// For each channel, all event times and their baselines.
205  std::vector<std::vector<std::pair<double, double>>> fBaselinesVsTime;
206 
207 
208  /// Creates all the plots to be filled by the module.
209  void setupPlots();
210 
211  /// Removes the empty plots.
212  void buildBaselineGraphs();
213 
214  /// Extracts a baseline as median from a single waveform.
216  (raw::OpDetWaveform const& waveform) const;
217 
218 }; // icarus::PMTWaveformBaselines
219 
220 
221 
222 //------------------------------------------------------------------------------
223 //--- Implementation
224 //------------------------------------------------------------------------------
225 namespace {
226 
227  /// Extracts the median of the collection between the specified iterators.
228  template <typename BIter, typename EIter>
229  auto median(BIter begin, EIter end) {
230 
231  using value_type = typename BIter::value_type;
232 
233  std::vector<value_type> data{ begin, end };
234  assert(!data.empty());
235 
236  auto const middle = data.begin() + data.size() / 2;
237  std::nth_element(data.begin(), middle, data.end());
238 
239  return *middle;
240 
241  } // median()
242 
243 } // local namespace
244 
245 
246 //------------------------------------------------------------------------------
247 //--- icarus::PMTWaveformBaselines
248 //------------------------------------------------------------------------------
250  (Parameters const& config)
251  : art::EDProducer(config)
252  // configuration
253  , fOpDetWaveformTag(config().OpticalWaveforms())
254  , fPlotBaselines(config().PlotBaselines())
255  , fBaselineTimeAverage(config().BaselineTimeAverage())
256  , fLogCategory(config().OutputCategory())
257 {
258  //
259  // optional configuration parameters
260  //
261 
262  //
263  // configuration report (currently, more like a placeholder)
264  //
265  mf::LogInfo(fLogCategory)
266  << "Using the standard (median) algorithm, waveform by waveform, on '"
267  << fOpDetWaveformTag.encode() << "'.";
268 
269  //
270  // declaration of input
271  //
272  consumes<std::vector<raw::OpDetWaveform>>(fOpDetWaveformTag);
273 
274  //
275  // declaration of output
276  //
277  produces<std::vector<icarus::WaveformBaseline>>();
278  produces<art::Assns<icarus::WaveformBaseline, raw::OpDetWaveform>>();
279 
280 } // icarus::PMTWaveformBaselines::PMTWaveformBaselines()
281 
282 
283 //------------------------------------------------------------------------------
285 
286  //
287  // set up the plots, if needed
288  //
289  if (fPlotBaselines) setupPlots();
290 
291 } // icarus::PMTWaveformBaselines::beginJob()
292 
293 
294 //------------------------------------------------------------------------------
295 void icarus::PMTWaveformBaselines::produce(art::Event& event) {
296 
297  //
298  // fetch input
299  //
300  auto const& waveformHandle
301  = event.getValidHandle<std::vector<raw::OpDetWaveform>>(fOpDetWaveformTag);
302  auto const& waveforms = *waveformHandle;
303 
304  /*
305  * this may be needed in a future where processing happens per channel
306  * rather than per waveform:
307  // map address of waveform to art pointer to that waveform
308  auto const& opDetWavePtrs
309  = util::mapDataProductPointers(event, waveformHandle);
310  */
311 
312 
313  //
314  // compute all the baselines
315  //
316  std::vector<icarus::WaveformBaseline> baselines;
317  baselines.reserve(waveforms.size());
318 
319  std::vector<lar::util::StatCollector<double>> averages;
320  if (fHBaselines || !fBaselinesVsTime.empty())
321  averages.resize(fNPlotChannels);
322 
323  art::Assns<icarus::WaveformBaseline, raw::OpDetWaveform> baselineToWaveforms;
324 
325  art::PtrMaker<icarus::WaveformBaseline> const makeBaselinePtr(event);
326 
327  for (auto const& [ iWaveform, waveform ]: util::enumerate(waveforms)) {
328  assert(iWaveform == baselines.size());
329 
330  icarus::WaveformBaseline const baseline { baselineFromMedian(waveform) };
331 
332  if (!averages.empty())
333  averages[waveform.ChannelNumber()].add(baseline.baseline());
334  baselines.push_back(baseline);
335 
336  baselineToWaveforms.addSingle(
337  makeBaselinePtr(iWaveform),
338  art::Ptr<raw::OpDetWaveform>(waveformHandle, iWaveform)
339  );
340 
341  } // for waveforms
342 
343  //
344  // plot filling
345  //
346  if (!averages.empty()) {
347 
348  double const eventTime = static_cast<double>(event.time().timeHigh())
349  + static_cast<double>(event.time().timeHigh()) * 1e-9;
350 
351  for (auto const& [ channel, stat ]: util::enumerate(averages)) {
352  if (stat.N() == 0) continue;
353 
354  double const aveBaseline = stat.Average();
355 
356  fHBaselines->Fill(double(channel), aveBaseline);
357 
358  if (channel < fBaselinesVsTime.size())
359  fBaselinesVsTime[channel].emplace_back(eventTime, aveBaseline);
360 
361  } // for baselines
362 
363  } // if plots
364 
365  //
366  // output
367  //
368  event.put(
369  std::make_unique<std::vector<icarus::WaveformBaseline>>
370  (std::move(baselines))
371  );
372  event.put(
373  std::make_unique<art::Assns<icarus::WaveformBaseline, raw::OpDetWaveform>>
374  (std::move(baselineToWaveforms))
375  );
376 
377 
378 } // icarus::PMTWaveformBaselines::produce()
379 
380 
381 //------------------------------------------------------------------------------
383 
384  if (fPlotBaselines) buildBaselineGraphs();
385 
386 } // icarus::PMTWaveformBaselines::endJob()
387 
388 
389 //------------------------------------------------------------------------------
391 
392  auto const& tfs = *(art::ServiceHandle<art::TFileService>());
393  auto const& geom = *(lar::providerFrom<geo::Geometry const>());
394 
395  fNPlotChannels = geom.NOpChannels();
396 
397  fHBaselines = tfs.make<TH2F>(
398  "Baselines",
399  "PMT baseline;channel;baseline per channel [ / 8 ADC ]",
400  fNPlotChannels, 0.0, double(fNPlotChannels),
401  256, 13312.0, 15360.0
402  );
403 
404  // these are graphs, and it is more convenient to carry around their data
405  // in a vector than carrying around the graphs themselves;
406  // `buildBaselineGraphs()` will turn that data into graph at end of the job;
407  // here we just declare which channels we are going to plot.
408  fBaselinesVsTime.resize(fNPlotChannels);
409 
410 } // icarus::PMTWaveformBaselines::setupPlots()
411 
412 
413 //------------------------------------------------------------------------------
415 
416  auto& tfs = *(art::ServiceHandle<art::TFileService>());
417 
418  art::TFileDirectory graphDir = tfs.mkdir("graphs", "Baseline vs. time");
419  art::TFileDirectory profileDir
420  = tfs.mkdir("profiles", "Baseline profiles vs. time");
421 
422  for (auto const channel: util::counter(fBaselinesVsTime.size())) {
423 
424  auto& timeAndBaselines = fBaselinesVsTime[channel];
425  if (timeAndBaselines.empty()) continue;
426 
427  // sort by time (entries with the same time would be sorted by baseline,
428  // but that does not really happen nor it mattered if it happened)
429  std::sort(timeAndBaselines.begin(), timeAndBaselines.end());
430 
431  // graph, one point per event
432  auto* const graph = graphDir.makeAndRegister<TGraph>(
433  "BaselineCh" + std::to_string(channel),
434  "PMT channel #" + std::to_string(channel) + ": baseline vs. run time",
435  timeAndBaselines.size()
436  );
437  assert(graph->GetXaxis());
438  assert(graph->GetYaxis());
439  graph->GetXaxis()->SetTitle("event time");
440  graph->GetYaxis()->SetTitle("baseline [ ADC ]");
441 
442 
443  // profile, one point every 10 minutes (or best offer)
444  double const startTime = timeAndBaselines.front().first;
445  double const totalTime = timeAndBaselines.back().first - startTime;
446  auto const nBins = std::max(1U,
447  static_cast<unsigned int>(std::ceil(totalTime / fBaselineTimeAverage))
448  );
449  double const endTime = startTime + nBins * fBaselineTimeAverage;
450 
451  auto* const profile = profileDir.make<TProfile>(
452  ("BaselineCh" + std::to_string(channel) + "profile").c_str(),
453  ("PMT channel #" + std::to_string(channel) + ": baseline vs. run time")
454  .c_str(),
455  nBins, startTime, endTime
456  );
457  assert(profile->GetXaxis());
458  assert(profile->GetYaxis());
459  profile->GetXaxis()->SetTitle(
460  ("event time [ / " + std::to_string(fBaselineTimeAverage) + " s ]")
461  .c_str()
462  );
463  profile->GetYaxis()->SetTitle("average baseline [ ADC ]");
464 
465  for (auto const& [ i, data ]: util::enumerate(timeAndBaselines)) {
466  auto const [ time, baseline ] = data;
467  graph->SetPoint(i, time, baseline);
468  profile->Fill(time, baseline);
469  } // for
470 
471  } // for
472 
473 } // icarus::PMTWaveformBaselines::buildBaselineGraphs()
474 
475 
476 //------------------------------------------------------------------------------
478  (raw::OpDetWaveform const& waveform) const
479 {
480 
482  { waveform.empty()? 0.0f: median(waveform.begin(), waveform.end()) };
483 
484 } // icarus::PMTWaveformBaselines::baselineFromMedian()
485 
486 
487 //------------------------------------------------------------------------------
488 DEFINE_ART_MODULE(icarus::PMTWaveformBaselines)
489 
490 
491 //------------------------------------------------------------------------------
std::vector< std::vector< std::pair< double, double > > > fBaselinesVsTime
For each channel, all event times and their baselines.
virtual void endJob() override
Remove empty plots.
Utilities related to art service access.
virtual void produce(art::Event &event) override
Creates the data products.
Definition of util::enumerate().
Extracts the baseline of PMT waveforms.
PMTWaveformBaselines(Parameters const &config)
void buildBaselineGraphs()
Removes the empty plots.
Classes gathering simple statistics.
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
fDetProps &fDetProps fDetProps &fDetProps fLogCategory
Access the description of detector geometry.
auto counter(T begin, T end)
Returns an object to iterate values from begin to end in a range-for loop.
Definition: counter.h:285
void setupPlots()
Creates all the plots to be filled by the module.
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
BEGIN_PROLOG baseline
BEGIN_PROLOG vertical distance to the surface Name
bool fPlotBaselines
Whether to produce plots.
art::InputTag const fOpDetWaveformTag
Input optical waveform tag.
auto begin(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:573
double const fBaselineTimeAverage
Width of baseline time profile binning [s].
virtual void beginJob() override
Prepares the plots to be filled.
physics producers discrimopdaq OpticalWaveforms
art::EDProducer::Table< Config > Parameters
std::string to_string(WindowPattern const &pattern)
icarus::WaveformBaseline baselineFromMedian(raw::OpDetWaveform const &waveform) const
Extracts a baseline as median from a single waveform.
std::string const fLogCategory
Category name for the console output stream.
Class containing a waveform baseline value.
do i e
TH2 * fHBaselines
All baselines, per channel.
art::ServiceHandle< art::TFileService > tfs
std::size_t fNPlotChannels
Number of plotted channels.
art framework interface to geometry description