All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
opDetSBNDTriggerAlg.cc
Go to the documentation of this file.
3 
4 namespace {
5  double optical_period(detinfo::DetectorClocksData const& clockData,bool is_daphne)
6  {
7  if (is_daphne) return clockData.OpticalClock().TickPeriod()*500/80; //500MHz Apsaia, 80MHz Daphne. TODO load frequency values from fcl ~rodrigoa
8  return clockData.OpticalClock().TickPeriod();
9  }
10 
11  raw::TimeStamp_t tick_to_timestamp(detinfo::DetectorClocksData const& clockData,
12  raw::TimeStamp_t waveform_start,
13  size_t waveform_index,
14  bool is_daphne)
15  {
16  return waveform_start + waveform_index * optical_period(clockData,is_daphne);
17  }
18 }
19 
20 namespace opdet {
21 
26 };
27 
28 // Local static functions
29 // Adds a value to a vector of trigger locations, while keeping the vector sorted
30 void AddTriggerLocation(std::vector<std::array<raw::TimeStamp_t, 2>> &triggers, std::array<raw::TimeStamp_t,2> range) {
31  typedef std::vector<std::array<raw::TimeStamp_t,2>> TimeStamps;
32 
33  TimeStamps::iterator insert = std::lower_bound(triggers.begin(), triggers.end(), range,
34  [](const auto &lhs, const auto &rhs) { return lhs[0] < rhs[0]; });
35  triggers.insert(insert, range);
36 }
37 
38 void AddTriggerPrimitive(std::vector<TriggerPrimitive> &triggers, TriggerPrimitive trigger) {
39  typedef std::vector<TriggerPrimitive> TimeStamps;
40 
41  TimeStamps::iterator insert = std::lower_bound(triggers.begin(), triggers.end(), trigger,
42  [](auto const &lhs, auto const &rhs) { return lhs.start < rhs.start; });
43 
44  triggers.insert(insert, trigger);
45 }
46 
47 void AddTriggerPrimitiveFinish(std::vector<TriggerPrimitive> &triggers, TriggerPrimitive trigger) {
48  typedef std::vector<TriggerPrimitive> TimeStamps;
49 
50  TimeStamps::iterator insert = std::upper_bound(triggers.begin(), triggers.end(), trigger,
51  [](auto const &lhs, auto const &rhs) { return lhs.finish < rhs.finish; });
52 
53  triggers.insert(insert, trigger);
54 }
55 
57  fConfig(config)
58 {
59  // setup the masked channels
61 }
62 
65  const raw::OpDetWaveform &waveform, raw::ADC_Count_t baseline) {
66  std::vector<std::array<raw::TimeStamp_t, 2>> this_trigger_ranges;
67  const std::vector<raw::ADC_Count_t> &adcs = waveform; // upcast to get adcs
68  raw::Channel_t channel = waveform.ChannelNumber();
69  // if (channel > (unsigned)fOpDetMap.size()) return;
70 
71  // initialize the channel in the map no matter what
72  if (fTriggerRangesPerChannel.count(channel) == 0) {
73  fTriggerRangesPerChannel[channel] = std::vector<std::array<raw::TimeStamp_t,2>>();
74  }
75 
76  // get the threshold -- first check if channel is Arapuca or PMT
77  std::string opdet_type = fOpDetMap.pdType(channel);
78  bool is_arapuca = false;
79  if( opdet_type.find("arapuca") != std::string::npos ) is_arapuca = true;
80  bool is_daphne = false;
81  std::string sampling_type = fOpDetMap.electronicsType(channel);
82  if (sampling_type == "daphne") is_daphne = true;
83 
84  int threshold = is_arapuca ? fConfig.TriggerThresholdADCArapuca() : fConfig.TriggerThresholdADCPMT();
85  int polarity = is_arapuca ? fConfig.PulsePolarityArapuca() : fConfig.PulsePolarityPMT();
86 
87  // find the start and end points of the trigger window in this waveform
88  std::array<double, 2> trigger_window = TriggerEnableWindow(clockData, detProp);
89  raw::TimeStamp_t start = tick_to_timestamp(clockData, waveform.TimeStamp(), 0,is_daphne);
90  if (start > trigger_window[1]) return;
91  size_t start_i = start > trigger_window[0] ? 0 : (size_t)((trigger_window[0] - start) / optical_period(clockData,is_daphne));
92 
93  // fix rounding on division if necessary
94  if (!IsTriggerEnabled(clockData,
95  detProp,
96  tick_to_timestamp(clockData, waveform.TimeStamp(), start_i,is_daphne))) {
97  start_i += 1;
98  }
99  assert(IsTriggerEnabled(clockData,
100  detProp,
101  tick_to_timestamp(clockData, waveform.TimeStamp(), start_i,is_daphne)));
102 
103  // if start is past end of waveform, we can return
104  if (start_i >= adcs.size()) return;
105 
106  // get the end time
107  raw::TimeStamp_t end = tick_to_timestamp(clockData, waveform.TimeStamp(), adcs.size() - 1,is_daphne);
108  size_t end_i = end < trigger_window[1] ? adcs.size()-1 : (size_t)((trigger_window[1] - start) / optical_period(clockData,is_daphne));
109 
110  // fix rounding error...
111  if (IsTriggerEnabled(clockData,
112  detProp,
113  tick_to_timestamp(clockData, waveform.TimeStamp(), end_i+1,is_daphne)) && end_i+1 < adcs.size()) {
114  end_i += 1;
115  }
116  assert(end_i+1 == adcs.size() || !IsTriggerEnabled(clockData,
117  detProp,
118  tick_to_timestamp(clockData, waveform.TimeStamp(), end_i+1,is_daphne)));
119 
120  std::vector<std::array<raw::TimeStamp_t, 2>> this_trigger_locations;
121  bool above_threshold = false;
122  bool beam_trigger_added = false;
123  double t_since_last_trigger = 99999.; //[us]
124  double t_deadtime = fConfig.TriggerHoldoff();
125  raw::TimeStamp_t trigger_start;
126  // find all ADC counts above threshold
127  for (size_t i = start_i; i <= end_i; i++) {
128  raw::TimeStamp_t time = tick_to_timestamp(clockData, waveform.TimeStamp(),i,is_daphne);
129  t_since_last_trigger += optical_period(clockData,is_daphne);
130  bool isLive = (t_since_last_trigger > t_deadtime);
131  raw::ADC_Count_t val = polarity * (adcs.at(i) - baseline);
132  // only open new trigger if enough deadtime has passed
133  if (isLive && !above_threshold && val > threshold) {
134  // new trigger! -- get the time
135  trigger_start = time;
136  above_threshold = true;
137  t_since_last_trigger = 0;
138  t_deadtime = fConfig.TriggerHoldoff();
139  }
140  else if (above_threshold && (val < threshold || i+1 == end_i)) {
141  raw::TimeStamp_t trigger_finish = time;
142  AddTriggerLocation(this_trigger_locations, {{trigger_start, trigger_finish}});
143  above_threshold = false;
144  }
145  // add beam trigger (if enabled)
146  // since the clock ticks might not sync up exactly, use the closet sample
147  if( isLive && fConfig.BeamTriggerEnable() && !beam_trigger_added &&
148  fabs(time-fConfig.BeamTriggerTime()) <= optical_period(clockData,is_daphne)/2. ){
149  AddTriggerLocation(this_trigger_locations, {{time,time}});
150  beam_trigger_added = true;
151  t_since_last_trigger = 0;
152  t_deadtime = fConfig.BeamTriggerHoldoff();
153  }
154  }
155 
156  // Add in these triggers to the channel
157  //
158  // Small speed optimization: if this is the first time we are setting the
159  // trigger times for the channel, just move the vector we already built
160  if (fTriggerRangesPerChannel[channel].size() == 0) {
161  fTriggerRangesPerChannel[channel] = std::move(this_trigger_locations);
162  }
163  // Otherwise, merge them in and keep things sorted in time
164  else {
165  for (const std::array<raw::TimeStamp_t, 2> &trigger_range: this_trigger_ranges) {
166  AddTriggerLocation(fTriggerRangesPerChannel[channel], trigger_range);
167  }
168  }
169 
170 }
171 
173  // mask by channel number
174  bool in_masked_list = std::find(fMaskedChannels.begin(), fMaskedChannels.end(), channel) != fMaskedChannels.end();
175  if (in_masked_list) return true;
176 
177  // mask by optical detector type
178  std::string opdet_type = fOpDetMap.pdType(channel);
179  if (opdet_type == "bar" && fConfig.MaskLightBars() /* RIP */) return true;
180  if (opdet_type == "pmt_coated" && fConfig.MaskPMTs()) return true;
181  if (opdet_type == "pmt_uncoated" && fConfig.MaskBarePMTs()) return true;
182  if (opdet_type == "xarapucaprime" && fConfig.MaskXArapucaPrimes()) return true;
183  if (opdet_type == "xarapuca" && fConfig.MaskXArapucas()) return true;
184  if (opdet_type == "xarapuca_vuv" && fConfig.MaskXArapucas()) return true;
185  if (opdet_type == "xarapuca_vis" && fConfig.MaskXArapucas()) return true;
186  if (opdet_type == "xarapucaT1" && fConfig.MaskXArapucas()) return true;
187  if (opdet_type == "xarapucaT2" && fConfig.MaskXArapucas()) return true;
188  if (opdet_type == "arapucaT1" && fConfig.MaskArapucaT1s()) return true;
189  if (opdet_type == "arapucaT2" && fConfig.MaskArapucaT2s()) return true;
190  return false;
191 }
192 
193 void opDetSBNDTriggerAlg::ClearTriggerLocations() {
195  fTriggerRangesPerChannel.clear();
196  fTriggerLocations.clear();
197 }
198 
200  // If each channel is self triggered, there is no "master" set of triggers, and
201  // we don't need to do anything here
203  for (const auto &trigger_ranges: fTriggerRangesPerChannel) {
204  fTriggerLocationsPerChannel[trigger_ranges.first] = std::vector<raw::TimeStamp_t>();
205  for (const std::array<raw::TimeStamp_t, 2> &range: trigger_ranges.second) {
206  fTriggerLocationsPerChannel[trigger_ranges.first].push_back(range[0]);
207  }
208  }
209  return;
210  }
211 
212  // Otherwise we need to merge in the triggers from each channel into
213  // a master set to be issued to each channel
214  //
215  // The exact mechanism for how this is done should mirror the implementation
216  // in the PTB. However, no such implementation is really defined yet,
217  // so we implement a small generic algorithm here. This may likely have
218  // to be changed later.
219 
220  // First re-sort the trigger times to be a sorted global list of (channel, time) values
221  std::vector<TriggerPrimitive> all_trigger_locations;
222  for (const auto &trigger_locations_pair: fTriggerRangesPerChannel) {
223  raw::Channel_t this_channel = trigger_locations_pair.first;
224  // check if this channel contributes to the trigger
225  if (!IsChannelMasked(this_channel)) {
226  // then add in the locations
227  for (std::array<raw::TimeStamp_t,2> trigger_range: trigger_locations_pair.second) {
228  TriggerPrimitive trigger;
229  trigger.start = trigger_range[0];
230  trigger.finish = trigger_range[1];
231  trigger.channel = this_channel;
232  AddTriggerPrimitive(all_trigger_locations, trigger);
233  }
234  }
235  }
236 
237  // Now merge the trigger locations we have
238  //
239  // What is the merging algorithm? This will probably come from the PTB.
240  // For now just require some number of channels in some time window.
241  // Also allow for a trigger holdoff to be set after one trigger is issued.
242  //
243  // Also here is where one might start to simulate a different clock speed
244  // for the trigger, and for simulating drifts between channels and/or
245  // between channel and trigger. However, for now we assume that the
246  // trigger and readout have the same clock and that they are perfectly
247  // synched.
248  bool was_triggering = false;
249 
250  std::vector<TriggerPrimitive> primitives;
251  for (const TriggerPrimitive &primitive: all_trigger_locations) {
252  AddTriggerPrimitiveFinish(primitives, primitive);
253  while (primitives.back().finish < primitive.start) {
254  // remove the final element
255  primitives.resize(primitives.size() - 1);
256  }
257 
258  bool is_triggering = primitives.size() >= fConfig.TriggerChannelCount();
259  if (is_triggering && !was_triggering) {
260  raw::TimeStamp_t this_trigger_time = primitive.start;
261  fTriggerLocations.push_back(this_trigger_time);
262  }
263  was_triggering = is_triggering;
264  }
265 }
266 
269  double start, end;
270 
271  // Enable triggers starting 1 drift period prior to the start of TPC readout,
272  // and continuing over the full range of the TPC readout window
274  double tpcReadoutWindow = detProp.ReadOutWindowSize() * clockData.TPCClock().TickPeriod();
275  double tpcStart = fConfig.BeamTriggerTime() + clockData.TriggerOffsetTPC();
276  start = tpcStart - fConfig.DriftPeriod() - 1; /* Give 1us of wiggle room */
277  end = tpcStart + tpcReadoutWindow;
278  }
279  else {
282  }
283 
284  return {{start, end}};
285 }
286 
290  // otherwise check the start and end
291  std::array<double, 2> trigger_range = TriggerEnableWindow(clockData, detProp);
292 
293  return trigger_time >= trigger_range[0] &&
294  trigger_time <= trigger_range[1];
295 }
296 
297 const std::vector<raw::TimeStamp_t> &opDetSBNDTriggerAlg::GetTriggerTimes(raw::Channel_t channel) const {
299  return fTriggerLocationsPerChannel.at(channel);
300  }
301  return fTriggerLocations;
302 
303 }
304 
306  // Allow for different channels to have different readout window lengths
307  // For now, we don't use this
308  (void) channel;
310 }
311 
313  // Allow for different channels to have different readout window lengths
314  // For now, we don't use this
315  (void) channel;
317 }
318 
320  // Allow for different channels to have different readout window lengths
321  // For now, we don't use this
322  (void) channel;
324 }
325 
327  // Allow for different channels to have different readout window lengths
328  // For now, we don't use this
329  (void) channel;
331 }
332 
333 std::vector<raw::OpDetWaveform> opDetSBNDTriggerAlg::ApplyTriggerLocations(detinfo::DetectorClocksData const& clockData,
334  const raw::OpDetWaveform &waveform) const {
335  // Vector of "triggered" OpDetWaveforms
336  std::vector<raw::OpDetWaveform> ret;
337 
338  // Get the trigger times we found earlier for this channel
339  raw::Channel_t channel = waveform.ChannelNumber();
340  const std::vector<raw::TimeStamp_t> &trigger_times = GetTriggerTimes(channel);
341  if( trigger_times.size() == 0 ) return ret;
342  bool is_daphne = false;
343  std::string sampling_type = fOpDetMap.electronicsType(channel);
344  if (sampling_type == "daphne") is_daphne = true;
345 
346 
347 // std::cout
348 // <<"=======================\n"
349 // <<"Found "<<trigger_times.size()<<" triggers\n";
350 // for(size_t i=0; i<trigger_times.size(); i++) std::cout<<" - "<<trigger_times[i]<<"\n";
351 
352  // Extract waveform of raw ADC counts
353  const std::vector<raw::ADC_Count_t> &adcs = waveform; // upcast to get adcs
354  raw::OpDetWaveform this_waveform;
355 
356  // Set the pre- and post-readout sizes
357  double preTrig = ReadoutWindowPreTrigger(channel);
358  double postTrig = ReadoutWindowPostTrigger(channel);
359  unsigned ro_samples = (preTrig+postTrig)/optical_period(clockData,is_daphne); // samples
360 
361  // Are beam triggers enabled?
362  double beamTrigTime= fConfig.BeamTriggerTime(); // should be 0
363  double preTrigBeam = ReadoutWindowPreTriggerBeam(channel);
364  double postTrigBeam = ReadoutWindowPostTriggerBeam(channel);
365  unsigned ro_samples_beam= (preTrigBeam+postTrigBeam)/optical_period(clockData,is_daphne); // samples
366 
367  // --------------------------------------------
368  // Scan the waveform
369  unsigned trigger_i = 0;
370  double next_trig = trigger_times[trigger_i];
371  bool isReadingOut = false;
372  bool isBeamTrigger = false;
373  unsigned min_ro_samples = ro_samples;
374 
375  for(size_t i=0; i<adcs.size(); i++){
376  double time = tick_to_timestamp(clockData, waveform.TimeStamp(), i,is_daphne);
377 
378  // if we're out of triggers or nearing the end of the waveform, break
379  if( trigger_i >= trigger_times.size() ) break;
380  if( i >= adcs.size()-1-min_ro_samples ) break;
381 
382  // check if the current trigger is from the beam
383  isBeamTrigger = ( fabs(next_trig-beamTrigTime)<optical_period(clockData,is_daphne)/2 );
384 
385  // scan ahead to the "next" trigger if we've reached the end of the previous one
386  double dT = time-next_trig;
387  if( trigger_i < trigger_times.size()-1 &&
388  ((isBeamTrigger && dT >= postTrigBeam)||(!isBeamTrigger && dT >= postTrig))) {
389  for(size_t j=trigger_i+1; j<trigger_times.size(); j++){
390  double this_trig = trigger_times[j];
391  isBeamTrigger = ( fabs(this_trig-beamTrigTime)<optical_period(clockData,is_daphne)/2 );
392  double t1 = this_trig-preTrig;
393  double t2 = this_trig+postTrig;
394  if(isBeamTrigger){
395  t1 = this_trig-preTrigBeam;
396  t2 = this_trig+postTrigBeam;
397  }
398  if( ( fConfig.AllowTriggerOverlap() && (t2 >= time) )
399  ||(!fConfig.AllowTriggerOverlap() && (t1 >= time) ) ){
400  next_trig = this_trig;
401  trigger_i = j;
402  break;
403  }
404  }
405  }
406 
407  // if we're within the trigger window, we're triggering
408  bool isTriggering = false;
409  if( (!isBeamTrigger && time >= (next_trig-preTrig) && time < (next_trig+postTrig))
410  || ( isBeamTrigger && time >= (next_trig-preTrigBeam) && time < (next_trig+postTrigBeam))
411  )
412  isTriggering = true;
413 
414  // if already reading out, keep going!
415  if( isReadingOut && this_waveform.size() < min_ro_samples ){
416  this_waveform.push_back(adcs[i]);
417  }
418  // once we've saved at least 1 full window, only continue adding
419  // to it if we are allowing trigger overlaps. Otherwise, package up
420  // the waveform into an OpDetWaveform object and reset the readout
421  else if( isReadingOut && this_waveform.size() >= min_ro_samples ){
422  if( fConfig.AllowTriggerOverlap() && isTriggering ) {
423  this_waveform.push_back(adcs[i]);
424  } else {
425  ret.push_back(std::move(this_waveform));
426  isReadingOut = false;
427  }
428  }
429 
430  // start new readout
431  if( !isReadingOut && isTriggering ) {
432  this_waveform = raw::OpDetWaveform(time, channel);
433  this_waveform.push_back(adcs[i]);
434  isReadingOut = true;
435  min_ro_samples = ro_samples;
436  if( isBeamTrigger ) min_ro_samples = ro_samples_beam;
437  }
438 
439  }//endloop over ADCs
440 
441 // std::cout<<"We saved a total of "<<ret.size()<<" opdetwaveforms\n";
442 // for(size_t i=0; i<ret.size(); i++) std::cout<<" - timestamp "<<ret.at(i).TimeStamp()<<" size "<<ret.at(i).Waveform().size()<<"\n";
443 
444  return ret;
445 }
446 
447 } // namespace opdet
bool IsTriggerEnabled(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, raw::TimeStamp_t trigger_time) const
double ReadoutWindowPostTriggerBeam(raw::Channel_t channel) const
double ReadoutWindowPreTrigger(raw::Channel_t channel) const
ElecClock const & TPCClock() const noexcept
Borrow a const TPC clock with time set to Trigger time [us].
fhicl::Atom< double > ReadoutWindowPostTriggerBeam
const std::vector< raw::TimeStamp_t > & GetTriggerTimes(raw::Channel_t channel) const
fhicl::Atom< double > TriggerEnableWindowStart
std::array< double, 2 > TriggerEnableWindow(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp) const
fhicl::Atom< bool > TriggerEnableWindowOneDriftBeforeTPCReadout
std::map< raw::Channel_t, std::vector< std::array< raw::TimeStamp_t, 2 > > > fTriggerRangesPerChannel
void FindTriggerLocations(detinfo::DetectorClocksData const &clockData, detinfo::DetectorPropertiesData const &detProp, const raw::OpDetWaveform &waveform, raw::ADC_Count_t baseline)
std::size_t size(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:561
double TimeStamp_t
us since 1970, based on TimeService
opDetSBNDTriggerAlg(const Config &config)
fhicl::Atom< unsigned > TriggerChannelCount
fhicl::Atom< double > ReadoutWindowPreTrigger
fhicl::Atom< double > ReadoutWindowPreTriggerBeam
bool IsChannelMasked(raw::Channel_t channel) const
constexpr double TickPeriod() const noexcept
A single tick period in microseconds.
Definition: ElecClock.h:352
auto vector(Vector const &v)
Returns a manipulator which will print the specified array.
Definition: DumpUtils.h:265
timescale_traits< TriggerTimeCategory >::time_point_t trigger_time
A point in time on the trigger time scale.
void AddTriggerPrimitiveFinish(std::vector< TriggerPrimitive > &triggers, TriggerPrimitive trigger)
double ReadoutWindowPreTriggerBeam(raw::Channel_t channel) const
void AddTriggerPrimitive(std::vector< TriggerPrimitive > &triggers, TriggerPrimitive trigger)
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
BEGIN_PROLOG baseline
void AddTriggerLocation(std::vector< std::array< raw::TimeStamp_t, 2 >> &triggers, std::array< raw::TimeStamp_t, 2 > range)
fhicl::OptionalSequence< unsigned > MaskedChannels
j template void())
Definition: json.hpp:3108
ElecClock const & OpticalClock() const noexcept
Borrow a const Optical clock with time set to Trigger time [us].
std::vector< raw::OpDetWaveform > ApplyTriggerLocations(detinfo::DetectorClocksData const &clockData, const raw::OpDetWaveform &waveform) const
fhicl::Atom< double > ReadoutWindowPostTrigger
std::vector< unsigned > fMaskedChannels
Contains all timing reference information for the detector.
std::vector< raw::TimeStamp_t > fTriggerLocations
std::string electronicsType(size_t ch) const
fhicl::Atom< double > TriggerEnableWindowLength
double ReadoutWindowPostTrigger(raw::Channel_t channel) const
std::string pdType(size_t ch) const override
std::map< raw::Channel_t, std::vector< raw::TimeStamp_t > > fTriggerLocationsPerChannel
auto const detProp