All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
pmtSoftwareTriggerProducer_module.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 // Class: pmtSoftwareTriggerProducer
3 // Plugin Type: producer (Unknown Unknown)
4 // File: pmtSoftwareTriggerProducer_module.cc
5 //
6 // Generated at Thu Feb 17 13:22:51 2022 by Patrick Green using cetskelgen
7 // from version .
8 
9 // Module to implement software trigger metrics to the PMT Trigger simulation
10 // Input: artdaq fragment output from the pmtArtdaqFragmentProducer.cc module
11 // Calculates various PMT metrics for every event (that passes the hardware trigger)
12 // Output: sbnd::trigger::pmtSoftwareTrigger data product
13 
14 // More information can be found at:
15 // https://sbnsoftware.github.io/sbndcode_wiki/SBND_Trigger
16 ////////////////////////////////////////////////////////////////////////
17 
18 #include "art/Framework/Core/EDProducer.h"
19 #include "art/Framework/Core/ModuleMacros.h"
20 #include "art/Framework/Principal/Event.h"
21 #include "art/Framework/Principal/Handle.h"
22 #include "art/Framework/Principal/Run.h"
23 #include "art/Framework/Principal/SubRun.h"
24 #include "canvas/Utilities/InputTag.h"
25 #include "fhiclcpp/ParameterSet.h"
26 #include "messagefacility/MessageLogger/MessageLogger.h"
27 #include "art_root_io/TFileService.h"
28 
29 #include "sbndaq-artdaq-core/Overlays/Common/CAENV1730Fragment.hh"
30 #include "sbndaq-artdaq-core/Overlays/FragmentType.hh"
31 #include "artdaq-core/Data/Fragment.hh"
32 #include "artdaq-core/Data/ContainerFragment.hh"
33 
36 
37 // ROOT includes
38 #include "TH1D.h"
39 #include "TFile.h"
40 #include "TTree.h"
41 
42 #include <memory>
43 #include <algorithm>
44 #include <valarray>
45 #include <numeric>
46 
47 namespace sbnd {
48  namespace trigger {
50  }
51 }
52 
53 class sbnd::trigger::pmtSoftwareTriggerProducer : public art::EDProducer {
54 public:
55  explicit pmtSoftwareTriggerProducer(fhicl::ParameterSet const& p);
56  // The compiler-generated destructor is fine for non-base
57  // classes without bare pointers or other resource use.
58 
59  // Plugins should not be copied or assigned.
64 
65  // Required functions.
66  void produce(art::Event& e) override;
67 
68 private:
69 
70  // Declare member data here.
71 
72  // fhicl parameters
73  art::Persistable is_persistable_;
74  double fTriggerTimeOffset; // offset of trigger time, default 0.5 sec
75  double fBeamWindowLength; // beam window length after trigger time, default 1.6us
76  uint32_t fWvfmLength;
77  bool fVerbose;
78  bool fSaveHists;
79 
80  std::string fBaselineAlgo;
85  double fPEArea; // conversion factor from ADCxns area to PE count
86 
87  // histogram info
88  std::stringstream histname; //raw waveform hist name
89  art::ServiceHandle<art::TFileService> tfs;
90 
91  // event information
92  int fRun, fSubrun;
93  art::EventNumber_t fEvent;
94 
95  // PD information
96  opdet::sbndPDMapAlg pdMap; // photon detector map
97  std::vector<unsigned int> channelList;
98 
99  // beam window
100  // set in artdaqFragment producer, in reality would be provided by event builder
102  uint32_t beamWindowStart;
103  uint32_t beamWindowEnd;
104 
105  // waveforms
106  uint32_t fTriggerTime;
108  std::vector<std::vector<uint16_t>> fWvfmsVec;
109 
110  // pmt information
111  std::vector<sbnd::trigger::pmtInfo> fpmtInfoVec;
112 
113  void checkCAEN1730FragmentTimeStamp(const artdaq::Fragment &frag);
114  void analyzeCAEN1730Fragment(const artdaq::Fragment &frag);
115  void estimateBaseline(int i_ch);
116  void SimpleThreshAlgo(int i_ch);
117 
118 };
119 
120 
122  : EDProducer{p},
123  is_persistable_(p.get<bool>("is_persistable", true) ? art::Persistable::Yes : art::Persistable::No),
124  fTriggerTimeOffset(p.get<double>("TriggerTimeOffset", 0.5)),
125  fBeamWindowLength(p.get<double>("BeamWindowLength", 1.6)),
126  fWvfmLength(p.get<uint32_t>("WvfmLength", 5120)),
127  fVerbose(p.get<bool>("Verbose", false)),
128  fSaveHists(p.get<bool>("SaveHists",false)),
129  fBaselineAlgo(p.get<std::string>("BaselineAlgo", "estimate")),
130  fInputBaseline(p.get<double>("InputBaseline", 8000)),
131  fInputBaselineSigma(p.get<double>("InputBaselineSigma", 2)),
132  fADCThreshold(p.get<double>("ADCThreshold", 7960)),
133  fFindPulses(p.get<bool>("FindPulses", false)),
134  fPEArea(p.get<double>("PEArea", 66.33))
135  // More initializers here.
136 {
137  // Call appropriate produces<>() functions here.
138  produces< sbnd::trigger::pmtSoftwareTrigger >("", is_persistable_);
139 
140  beamWindowStart = fTriggerTimeOffset*1e9;
141  beamWindowEnd = beamWindowStart + fBeamWindowLength*1000;
142 
143  // build PD map and channel list
144  auto subsetCondition = [](auto const& i)->bool { return i["pd_type"] == "pmt_coated" || i["pd_type"] == "pmt_uncoated"; };
145  auto pmtMap = pdMap.getCollectionFromCondition(subsetCondition);
146  for(auto const& i:pmtMap){
147  channelList.push_back(i["channel"]);
148  }
149 }
150 
152 {
153  // Implementation of required member function here.
154 
155  // event information
156  fRun = e.run();
157  fSubrun = e.subRun();
158  fEvent = e.id().event();
159 
160  if (fVerbose) std::cout << "Processing Run: " << fRun << ", Subrun: " << fSubrun << ", Event: " << fEvent << std::endl;
161 
162  // reset for this event
163  foundBeamTrigger = false;
164  fWvfmsFound = false;
165  fWvfmsVec.clear(); fWvfmsVec.resize(15*8); // 15 pmt channels per fragment, 8 fragments per trigger
166  fpmtInfoVec.clear(); fpmtInfoVec.resize(15*8);
167 
168 
169  // get fragment handles
170  std::vector<art::Handle<artdaq::Fragments>> fragmentHandles = e.getMany<std::vector<artdaq::Fragment>>();
171 
172  // loop over fragment handles
173  for (auto &handle : fragmentHandles) {
174  if (!handle.isValid() || handle->size() == 0) continue;
175  if (handle->front().type()==sbndaq::detail::FragmentType::CAENV1730) {
176  if (fVerbose) std::cout << "Found " << handle->size() << " CAEN1730 fragments" << std::endl;
177 
178  // identify whether any fragments correspond to the beam spill
179  // loop over fragments, in steps of 8
180  size_t beamFragmentIdx = 9999;
181  for (size_t fragmentIdx = 0; fragmentIdx < handle->size(); fragmentIdx += 8) {
182  checkCAEN1730FragmentTimeStamp(handle->at(fragmentIdx));
183  if (foundBeamTrigger) {
184  beamFragmentIdx = fragmentIdx;
185  if (fVerbose) std::cout << "Found fragment in time with beam at index: " << beamFragmentIdx << std::endl;
186  break;
187  }
188  }
189  // if set of fragment in time with beam found, process waveforms
190  if (foundBeamTrigger && beamFragmentIdx != 9999) {
191  for (size_t fragmentIdx = beamFragmentIdx; fragmentIdx < beamFragmentIdx+8; fragmentIdx++) {
192  analyzeCAEN1730Fragment(handle->at(fragmentIdx));
193  }
194  fWvfmsFound = true;
195  }
196  }
197  } // end loop over handles
198 
199  // object to store trigger metrics in
200  std::unique_ptr<sbnd::trigger::pmtSoftwareTrigger> pmtSoftwareTriggerMetrics = std::make_unique<sbnd::trigger::pmtSoftwareTrigger>();
201 
202  if (foundBeamTrigger && fWvfmsFound) {
203 
204  pmtSoftwareTriggerMetrics->foundBeamTrigger = true;
205  // store timestamp of trigger, relative to beam window start
206  double triggerTimeStamp = fTriggerTime - beamWindowStart;
207  pmtSoftwareTriggerMetrics->triggerTimestamp = triggerTimeStamp;
208  if (fVerbose) std::cout << "Saving trigger timestamp: " << triggerTimeStamp << " ns" << std::endl;
209 
210  double promptPE = 0;
211  double prelimPE = 0;
212 
213  int nAboveThreshold = 0;
214  // find the waveform bins that correspond to the start and end of the extended spill window (0 -> 1.8 us) within the 10 us waveform
215  // if the triggerTimeStamp < 1000, the beginning of the beam spill is *not* contained within the waveform
216  int beamStartBin = (triggerTimeStamp >= 1000)? 0 : int(500 - abs((triggerTimeStamp-1000)/2));
217  int beamEndBin = (triggerTimeStamp >= 1000)? int(500 + (fBeamWindowLength*1e3 - triggerTimeStamp)/2) : (beamStartBin + (fBeamWindowLength*1e3)/2);
218 
219  // wvfm loop to calculate metrics
220  for (int i_ch = 0; i_ch < 120; ++i_ch){
221  auto &pmtInfo = fpmtInfoVec.at(i_ch);
222  auto wvfm = fWvfmsVec[i_ch];
223 
224  // assign channel
225  pmtInfo.channel = channelList.at(i_ch);
226 
227  // calculate baseline
228  if (fBaselineAlgo == "constant"){ pmtInfo.baseline=fInputBaseline; pmtInfo.baselineSigma = fInputBaselineSigma; }
229  else if (fBaselineAlgo == "estimate") estimateBaseline(i_ch);
230 
231  // count number of PMTs above threshold
232  for (int bin = beamStartBin; bin < beamEndBin; ++bin){
233  auto adc = wvfm[bin];
234  if (adc < fADCThreshold){ nAboveThreshold++; break; }
235  }
236 
237  // quick estimate prompt and preliminary light, assuming sampling rate of 500 MHz (2 ns per bin)
238  double baseline = pmtInfo.baseline;
239  auto prompt_window = std::vector<uint16_t>(wvfm.begin()+500, wvfm.begin()+1000);
240  auto prelim_window = std::vector<uint16_t>(wvfm.begin()+beamStartBin, wvfm.begin()+500);
241  if (fFindPulses == false){
242  double ch_promptPE = (baseline-(*std::min_element(prompt_window.begin(), prompt_window.end())))/8;
243  double ch_prelimPE = (baseline-(*std::min_element(prelim_window.begin(), prelim_window.end())))/8;
244  promptPE += ch_promptPE;
245  prelimPE += ch_prelimPE;
246  }
247 
248  // pulse finder + prompt and prelim calculation with pulses
249  if (fFindPulses == true){
250  SimpleThreshAlgo(i_ch);
251  for (auto pulse : pmtInfo.pulseVec){
252  if (pulse.t_start > 500 && pulse.t_end < 550) promptPE+=pulse.pe;
253  if ((triggerTimeStamp) >= 1000){ if (pulse.t_end < 500) prelimPE+=pulse.pe; }
254  else if (triggerTimeStamp < 1000){
255  if (pulse.t_start > (500 - abs((triggerTimeStamp-1000)/2)) && pulse.t_end < 500) prelimPE+=pulse.pe;
256  }
257  }
258  }
259  } // end of wvfm loop
260 
261  pmtSoftwareTriggerMetrics->nAboveThreshold = nAboveThreshold;
262  pmtSoftwareTriggerMetrics->promptPE = promptPE;
263  pmtSoftwareTriggerMetrics->prelimPE = prelimPE;
264  if (fVerbose) std::cout << "nPMTs Above Threshold: " << nAboveThreshold << std::endl;
265  if (fVerbose) std::cout << "prompt pe: " << promptPE << std::endl;
266  if (fVerbose) std::cout << "prelim pe: " << prelimPE << std::endl;
267 
268  // start histo
269  if (fSaveHists == true){
270  int hist_id = -1;
271  for (size_t i_wvfm = 0; i_wvfm < fWvfmsVec.size(); ++i_wvfm){
272  std::vector<uint16_t> wvfm = fWvfmsVec[i_wvfm];
273  hist_id++;
274  if (fEvent<4){
275  histname.str(std::string());
276  histname << "event_" << fEvent
277  << "_channel_" << i_wvfm;
278  double StartTime = ((fTriggerTime-beamWindowStart) - 500)*1e-3; // us
279  double EndTime = StartTime + (5210*2)*1e-03; // us
280  TH1D *wvfmHist = tfs->make< TH1D >(histname.str().c_str(), "Raw Waveform", wvfm.size(), StartTime, EndTime);
281  wvfmHist->GetXaxis()->SetTitle("t (#mus)");
282  for(unsigned int i = 0; i < wvfm.size(); i++) {
283  wvfmHist->SetBinContent(i + 1, (double)wvfm[i]);
284  }
285  }
286  } // end histo
287  }
288  }
289  else{
290  if (fVerbose) std::cout << "Beam and wvfms not found" << std::endl;
291  pmtSoftwareTriggerMetrics->foundBeamTrigger = false;
292  pmtSoftwareTriggerMetrics->triggerTimestamp = -9999;
293  pmtSoftwareTriggerMetrics->nAboveThreshold = -9999;
294  pmtSoftwareTriggerMetrics->promptPE = -9999;
295  pmtSoftwareTriggerMetrics->prelimPE = -9999;
296  }
297  e.put(std::move(pmtSoftwareTriggerMetrics));
298 
299 }
300 
302 
303  // get fragment metadata
304  sbndaq::CAENV1730Fragment bb(frag);
305  auto const* md = bb.Metadata();
306 
307  // access timestamp
308  uint32_t timestamp = md->timeStampNSec;
309 
310  // access beam signal, in ch15 of first PMT of each fragment set
311  // check entry 500 (0us), at trigger time
312  const uint16_t* data_begin = reinterpret_cast<const uint16_t*>(frag.dataBeginBytes()
313  + sizeof(sbndaq::CAENV1730EventHeader));
314  const uint16_t* value_ptr = data_begin;
315  uint16_t value = 0;
316 
317  size_t ch_offset = (size_t)(15*fWvfmLength);
318  size_t tr_offset = fTriggerTimeOffset*1e3;
319 
320  value_ptr = data_begin + ch_offset + tr_offset; // pointer arithmetic
321  value = *(value_ptr);
322 
323  if (value == 1 && timestamp >= beamWindowStart && timestamp <= beamWindowEnd) {
324  foundBeamTrigger = true;
325  fTriggerTime = timestamp;
326  }
327 }
328 
330 
331  // access fragment ID; index of fragment out of set of 8 fragments
332  int fragId = static_cast<int>(frag.fragmentID());
333 
334  // access waveforms in fragment and save
335  const uint16_t* data_begin = reinterpret_cast<const uint16_t*>(frag.dataBeginBytes()
336  + sizeof(sbndaq::CAENV1730EventHeader));
337  const uint16_t* value_ptr = data_begin;
338  uint16_t value = 0;
339 
340  // channel offset
341  size_t nChannels = 15; // 15 pmts per fragment
342  size_t ch_offset = 0;
343 
344  // loop over channels
345  for (size_t i_ch = 0; i_ch < nChannels; ++i_ch){
346  fWvfmsVec[i_ch + nChannels*fragId].resize(fWvfmLength);
347  ch_offset = (size_t)(i_ch * fWvfmLength);
348  //--loop over waveform samples
349  for(size_t i_t = 0; i_t < fWvfmLength; ++i_t){
350  value_ptr = data_begin + ch_offset + i_t; // pointer arithmetic
351  value = *(value_ptr);
352  fWvfmsVec[i_ch + nChannels*fragId][i_t] = value;
353  } //--end loop samples
354  } //--end loop channels
355 }
356 
358  auto wvfm = fWvfmsVec[i_ch];
359  auto &pmtInfo = fpmtInfoVec[i_ch];
360  // assuming that the first 500 ns doesn't include peaks, find the mean of the ADC count as the baseline
361  std::vector<uint16_t> subset = std::vector<uint16_t>(wvfm.begin(), wvfm.begin()+250);
362  double subset_mean = (std::accumulate(subset.begin(), subset.end(), 0))/(subset.size());
363  double val = 0;
364  for (size_t i=0; i<subset.size();i++){ val += (subset[i] - subset_mean)*(subset[i] - subset_mean);}
365  double subset_stddev = sqrt(val/subset.size());
366 
367  // if the first 500 ns seem to be messy, use the last 500
368  if (subset_stddev > 3){ // make this fcl parameter?
369  val = 0; subset.clear(); subset_stddev = 0;
370  subset = std::vector<uint16_t>(wvfm.end()-500, wvfm.end());
371  subset_mean = (std::accumulate(subset.begin(), subset.end(), 0))/(subset.size());
372  for (size_t i=0; i<subset.size();i++){ val += (subset[i] - subset_mean)*(subset[i] - subset_mean);}
373  subset_stddev = sqrt(val/subset.size());
374  }
375  pmtInfo.baseline = subset_mean;
376  pmtInfo.baselineSigma = subset_stddev;
377 }
378 
380  auto wvfm = fWvfmsVec[i_ch];
381  auto &pmtInfo = fpmtInfoVec[i_ch];
382  double baseline = pmtInfo.baseline;
383  double baseline_sigma = pmtInfo.baselineSigma;
384 
385  bool fire = false; // bool for if pulse has been detected
386  int counter = 0; // counts the bin of the waveform
387 
388  // these should be fcl parameters
389  double start_adc_thres = 5, end_adc_thres = 2;
390  double nsigma_start = 5, nsigma_end = 3;
391 
392  auto start_threshold = ( start_adc_thres > (nsigma_start * baseline_sigma) ? (baseline-start_adc_thres) : (baseline-(nsigma_start * baseline_sigma)));
393  auto end_threshold = ( end_adc_thres > (nsigma_end * baseline_sigma) ? (baseline - end_adc_thres) : (baseline - (nsigma_end * baseline_sigma)));
394 
395  std::vector<sbnd::trigger::pmtPulse> pulse_vec;
397  pulse.area = 0; pulse.peak = 0; pulse.t_start = 0; pulse.t_end = 0; pulse.t_peak = 0;
398  for (auto const &adc : wvfm){
399  if ( !fire && ((double)adc) <= start_threshold ){ // if its a new pulse
400  fire = true;
401  //vic: i move t_start back one, this helps with porch
402  pulse.t_start = counter - 1 > 0 ? counter - 1 : counter;
403  }
404 
405  if( fire && ((double)adc) > end_threshold ){ // found end of a pulse
406  fire = false;
407  //vic: i move t_start forward one, this helps with tail
408  pulse.t_end = counter < ((int)wvfm.size()) ? counter : counter - 1;
409  pulse_vec.push_back(pulse);
410  pulse.area = 0; pulse.peak = 0; pulse.t_start = 0; pulse.t_end = 0; pulse.t_peak = 0;
411  }
412 
413  if(fire){ // if we're in a pulse
414  pulse.area += (baseline-(double)adc);
415  if (pulse.peak > (baseline-(double)adc)) { // Found a new maximum
416  pulse.peak = (baseline-(double)adc);
417  pulse.t_peak = counter;
418  }
419  }
420  counter++;
421  }
422 
423  if(fire){ // Take care of a pulse that did not finish within the readout window.
424  fire = false;
425  pulse.t_end = counter - 1;
426  pulse_vec.push_back(pulse);
427  pulse.area = 0; pulse.peak = 0; pulse.t_start = 0; pulse.t_end = 0; pulse.t_peak = 0;
428  }
429 
430  pmtInfo.pulseVec = pulse_vec;
431  // calculate PE from area
432  for (auto &pulse : pmtInfo.pulseVec){pulse.pe = pulse.area/fPEArea;}
433 }
434 
435 // void sbnd::trigger:pmtSoftwareTriggerProducer::SlidingThreshAlgo(){
436 // }
437 
BEGIN_PROLOG pmtSoftwareTriggerProducer
pdgs p
Definition: selectors.fcl:22
constexpr details::BinObj< T > bin(T value)
Returns a wrapper to print the specified data in binary format.
T abs(T value)
pmtSoftwareTriggerProducer & operator=(pmtSoftwareTriggerProducer const &)=delete
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
BEGIN_PROLOG baseline
std::vector< sbnd::trigger::pmtPulse > pulseVec
do i e
stream1 can override from command line with o or output services user sbnd
temporary value
art::ServiceHandle< art::TFileService > tfs
BEGIN_PROLOG could also be cout
void checkCAEN1730FragmentTimeStamp(const artdaq::Fragment &frag)