StandardHit3DBuilder_tool.cc
Go to the documentation of this file.
1 /**
2  * @file StandardHit3DBuilder_tool.cc
3  *
4  * @brief This tool provides "standard" 3D hits built (by this tool) from 2D hits
5  *
6  */
7 
8 // Framework Includes
15 #include "art_root_io/TFileDirectory.h"
16 #include "art_root_io/TFileService.h"
18 #include "canvas/Persistency/Common/FindOneP.h"
21 #include "cetlib/cpu_timer.h"
22 #include "fhiclcpp/ParameterSet.h"
24 
25 // LArSoft includes
33 
34 // Eigen
35 #include <Eigen/Core>
36 
37 // std includes
38 #include <iostream>
39 #include <memory>
40 #include <string>
41 #include <numeric> // std::accumulate
42 
43 // Ack!
44 #include "TH1F.h"
45 #include "TTree.h"
46 
47 //------------------------------------------------------------------------------------------------------------------------------------------
48 // implementation follows
49 
50 namespace lar_cluster3d {
51 
52  /**
53  * @brief What follows are several highly useful typedefs which we
54  * want to expose to the outside world
55  */
56 
57  // forward declaration to define an ordering function for our hit set
58  struct Hit2DSetCompare {
59  bool operator()(const reco::ClusterHit2D*, const reco::ClusterHit2D*) const;
60  };
61 
62  using HitVector = std::vector<const reco::ClusterHit2D*>;
63  using PlaneToHitVectorMap = std::map<geo::PlaneID, HitVector>;
64  using TPCToPlaneToHitVectorMap = std::map<geo::TPCID, PlaneToHitVectorMap>;
65  using Hit2DList = std::list<reco::ClusterHit2D>;
66  using Hit2DSet = std::set<const reco::ClusterHit2D*, Hit2DSetCompare>;
67  using WireToHitSetMap = std::map<unsigned int, Hit2DSet>;
68  using PlaneToWireToHitSetMap = std::map<geo::PlaneID, WireToHitSetMap>;
69  using TPCToPlaneToWireToHitSetMap = std::map<geo::TPCID, PlaneToWireToHitSetMap>;
70  using HitVectorMap = std::map<size_t, HitVector>;
71 
72  //using HitPairVector = std::vector<std::unique_ptr<reco::ClusterHit3D>>;
73 
74  /**
75  * @brief StandardHit3DBuilder class definiton
76  */
77  class StandardHit3DBuilder : virtual public IHit3DBuilder {
78  public:
79  /**
80  * @brief Constructor
81  *
82  * @param pset
83  */
84  explicit StandardHit3DBuilder(fhicl::ParameterSet const& pset);
85 
86  /**
87  * @brief Each algorithm may have different objects it wants "produced" so use this to
88  * let the top level producer module "know" what it is outputting
89  */
90  void produces(art::ProducesCollector&) override;
91 
92  void configure(const fhicl::ParameterSet&) override;
93 
94  /**
95  * @brief Given a set of recob hits, run DBscan to form 3D clusters
96  *
97  * @param hitPairList The input list of 3D hits to run clustering on
98  * @param clusterParametersList A list of cluster objects (parameters from associated hits)
99  */
100  void Hit3DBuilder(art::Event&, reco::HitPairList&, RecobHitToPtrMap&) override;
101 
102  /**
103  * @brief If monitoring, recover the time to execute a particular function
104  */
105  float
107  {
108  return m_timeVector[index];
109  }
110 
111  private:
112  /**
113  * @brief Extract the ART hits and the ART hit-particle relationships
114  *
115  * @param evt - the ART event
116  */
117  void CollectArtHits(const art::Event& evt) const;
118 
119  /**
120  * @brief Given the ClusterHit2D objects, build the HitPairMap
121  */
122  void BuildHit3D(reco::HitPairList& hitPairList) const;
123 
124  /**
125  * @brief Create a new 2D hit collection from hits associated to 3D space points
126  */
127  void CreateNewRecobHitCollection(art::Event&,
129  std::vector<recob::Hit>&,
131 
132  /**
133  * @brief Create recob::Wire to recob::Hit associations
134  */
135  void makeWireAssns(const art::Event&,
137  RecobHitToPtrMap&) const;
138 
139  /**
140  * @brief Create raw::RawDigit to recob::Hit associations
141  */
142  void makeRawDigitAssns(const art::Event&,
144  RecobHitToPtrMap&) const;
145 
146  /**
147  * @brief Given the ClusterHit2D objects, build the HitPairMap
148  */
149  size_t BuildHitPairMap(PlaneToHitVectorMap& planeToHitVectorMap,
150  reco::HitPairList& hitPairList) const;
151 
152  /**
153  * @brief Given the ClusterHit2D objects, build the HitPairMap
154  */
156  std::vector<std::pair<HitVector::iterator, HitVector::iterator>>;
157 
158  size_t BuildHitPairMapByTPC(PlaneHitVectorItrPairVec& planeHitVectorItrPairVec,
159  reco::HitPairList& hitPairList) const;
160 
161  /**
162  * @brief This builds a list of candidate hit pairs from lists of hits on two planes
163  */
164  using HitMatchPair = std::pair<const reco::ClusterHit2D*, reco::ClusterHit3D>;
165  using HitMatchPairVec = std::vector<HitMatchPair>;
166  using HitMatchPairVecMap = std::map<geo::WireID, HitMatchPairVec>;
167 
168  int findGoodHitPairs(const reco::ClusterHit2D*,
171  HitMatchPairVecMap&) const;
172 
173  /**
174  * @brief This algorithm takes lists of hit pairs and finds good triplets
175  */
176  void findGoodTriplets(HitMatchPairVecMap&,
179  bool = false) const;
180 
181  /**
182  * @brief Make a HitPair object by checking two hits
183  */
184  bool makeHitPair(reco::ClusterHit3D& pairOut,
185  const reco::ClusterHit2D* hit1,
186  const reco::ClusterHit2D* hit2,
187  float hitWidthSclFctr = 1.,
188  size_t hitPairCntr = 0) const;
189 
190  /**
191  * @brief Make a 3D HitPair object by checking two hits
192  */
193  bool makeHitTriplet(reco::ClusterHit3D& pairOut,
194  const reco::ClusterHit3D& pairIn,
195  const reco::ClusterHit2D* hit2) const;
196 
197  /**
198  * @brief Make a 3D HitPair object from a valid pair and a dead channel in the missing plane
199  */
200  bool makeDeadChannelPair(reco::ClusterHit3D& pairOut,
201  const reco::ClusterHit3D& pair,
202  size_t maxStatus = 4,
203  size_t minStatus = 0,
204  float minOverlap = 0.2) const;
205 
206  /**
207  * @brief A utility routine for finding a 2D hit closest in time to the given pair
208  */
209  const reco::ClusterHit2D* FindBestMatchingHit(const Hit2DSet& hit2DSet,
210  const reco::ClusterHit3D& pair,
211  float pairDeltaTimeLimits) const;
212 
213  /**
214  * @brief A utility routine for returning the number of 2D hits from the list in a given range
215  */
216  int FindNumberInRange(const Hit2DSet& hit2DSet,
217  const reco::ClusterHit3D& pair,
218  float range) const;
219 
220  /**
221  * @brief Jacket the calls to finding the nearest wire in order to intercept the exceptions if out of range
222  */
223  geo::WireID NearestWireID(const Eigen::Vector3f& position, const geo::WireID& wireID) const;
224 
225  /**
226  * @brief Jacket the calls to finding the nearest wire in order to intercept the exceptions if out of range
227  */
228  float DistanceFromPointToHitWire(const Eigen::Vector3f& position,
229  const geo::WireID& wireID) const;
230 
231  /**
232  * @brief Create the internal channel status vector (assume will eventually be event-by-event)
233  */
234  void BuildChannelStatusVec(PlaneToWireToHitSetMap& planeToWiretoHitSetMap) const;
235 
236  /**
237  * @brief Perform charge integration between limits
238  */
239  float chargeIntegral(float, float, float, float, int, int) const;
240 
241  /**
242  * @brief define data structure for keeping track of channel status
243  */
244  using ChannelStatusVec = std::vector<size_t>;
245  using ChannelStatusByPlaneVec = std::vector<ChannelStatusVec>;
246 
247  /**
248  * @brief clear the tuple vectors before processing next event
249  */
250  void clear();
251 
252  /**
253  * @brief Data members to follow
254  */
255  std::vector<art::InputTag> m_hitFinderTagVec;
259  std::vector<int> m_invalidTPCVec;
260  float
261  m_wirePitchScaleFactor; ///< Scaling factor to determine max distance allowed between candidate pairs
262  float m_maxHit3DChiSquare; ///< Provide ability to select hits based on "chi square"
263  bool m_outputHistograms; ///< Take the time to create and fill some histograms for diagnostics
264 
266  float m_wirePitch[3];
267  mutable std::vector<float> m_timeVector; ///<
268 
270 
271  // Define some basic histograms
272  TTree* m_tupleTree; ///< output analysis tree
273 
274  mutable std::vector<float> m_deltaTimeVec;
275  mutable std::vector<float> m_chiSquare3DVec;
276  mutable std::vector<float> m_maxPullVec;
277  mutable std::vector<float> m_overlapFractionVec;
278  mutable std::vector<float> m_overlapRangeVec;
279  mutable std::vector<float> m_maxDeltaPeakVec;
280  mutable std::vector<float> m_maxSideVecVec;
281  mutable std::vector<float> m_pairWireDistVec;
282  mutable std::vector<float> m_smallChargeDiffVec;
283  mutable std::vector<int> m_smallIndexVec;
284  mutable std::vector<float> m_qualityMetricVec;
285  mutable std::vector<float> m_spacePointChargeVec;
286  mutable std::vector<float> m_hitAsymmetryVec;
287 
288  // Get instances of the primary data structures needed
292 
294  mutable size_t m_numBadChannels;
295 
296  mutable bool m_weHaveAllBeenHereBefore = false;
297 
300  };
301 
303  : m_geometry(art::ServiceHandle<geo::Geometry const>{}.get())
305  {
306  this->configure(pset);
307  }
308 
309  //------------------------------------------------------------------------------------------------------------------------------------------
310 
311  void
313  {
314  collector.produces<std::vector<recob::Hit>>();
317  }
318 
319  //------------------------------------------------------------------------------------------------------------------------------------------
320 
321  void
323  {
324  m_hitFinderTagVec = pset.get<std::vector<art::InputTag>>(
325  "HitFinderTagVec", std::vector<art::InputTag>() = {"gaushit"});
326  m_enableMonitoring = pset.get<bool>("EnableMonitoring", true);
327  m_numSigmaPeakTime = pset.get<float>("NumSigmaPeakTime", 3.);
328  m_hitWidthSclFctr = pset.get<float>("HitWidthScaleFactor", 6.);
329  m_deltaPeakTimeSig = pset.get<float>("DeltaPeakTimeSig", 1.7);
330  m_zPosOffset = pset.get<float>("ZPosOffset", 0.0);
331  m_invalidTPCVec = pset.get<std::vector<int>>("InvalidTPCVec", std::vector<int>());
332  m_wirePitchScaleFactor = pset.get<float>("WirePitchScaleFactor", 1.9);
333  m_maxHit3DChiSquare = pset.get<float>("MaxHitChiSquare", 6.0);
334  m_outputHistograms = pset.get<bool>("OutputHistograms", false);
335 
337 
338  // Returns the wire pitch per plane assuming they will be the same for all TPCs
339  m_wirePitch[0] = m_geometry->WirePitch(0);
340  m_wirePitch[1] = m_geometry->WirePitch(1);
341  m_wirePitch[2] = m_geometry->WirePitch(2);
342 
343  // Access ART's TFileService, which will handle creating and writing
344  // histograms and n-tuples for us.
346 
347  if (m_outputHistograms) {
348  m_tupleTree = tfs->make<TTree>("Hit3DBuilderTree", "Tree by StandardHit3DBuilder");
349 
350  clear();
351 
352  m_tupleTree->Branch("DeltaTime2D", "std::vector<float>", &m_deltaTimeVec);
353  m_tupleTree->Branch("ChiSquare3D", "std::vector<float>", &m_chiSquare3DVec);
354  m_tupleTree->Branch("MaxPullValue", "std::vector<float>", &m_maxPullVec);
355  m_tupleTree->Branch("OverlapFraction", "std::vector<float>", &m_overlapFractionVec);
356  m_tupleTree->Branch("OverlapRange", "std::vector<float>", &m_overlapRangeVec);
357  m_tupleTree->Branch("MaxDeltaPeak", "std::vector<float>", &m_maxDeltaPeakVec);
358  m_tupleTree->Branch("MaxSideVec", "std::vector<float>", &m_maxSideVecVec);
359  m_tupleTree->Branch("PairWireDistVec", "std::vector<float>", &m_pairWireDistVec);
360  m_tupleTree->Branch("SmallChargeDiff", "std::vector<float>", &m_smallChargeDiffVec);
361  m_tupleTree->Branch("SmallChargeIdx", "std::vector<int>", &m_smallIndexVec);
362  m_tupleTree->Branch("QualityMetric", "std::vector<float>", &m_qualityMetricVec);
363  m_tupleTree->Branch("SPCharge", "std::vector<float>", &m_spacePointChargeVec);
364  m_tupleTree->Branch("HitAsymmetry", "std::vector<float>", &m_hitAsymmetryVec);
365  }
366 
367  return;
368  }
369 
370  void
372  {
373  m_deltaTimeVec.clear();
374  m_chiSquare3DVec.clear();
375  m_maxPullVec.clear();
376  m_overlapFractionVec.clear();
377  m_overlapRangeVec.clear();
378  m_maxDeltaPeakVec.clear();
379  m_maxSideVecVec.clear();
380  m_pairWireDistVec.clear();
381  m_smallChargeDiffVec.clear();
382  m_smallIndexVec.clear();
383  m_qualityMetricVec.clear();
384  m_spacePointChargeVec.clear();
385  m_hitAsymmetryVec.clear();
386 
387  return;
388  }
389 
390  void
392  {
393  // This is called each event, clear out the previous version and start over
394  if (!m_channelStatus.empty()) m_channelStatus.clear();
395 
396  m_numBadChannels = 0;
398 
399  // Loop through views/planes to set the wire length vectors
400  for (size_t idx = 0; idx < m_channelStatus.size(); idx++) {
402  }
403 
404  // Loop through the channels and mark those that are "bad"
405  for (size_t channel = 0; channel < m_geometry->Nchannels(); channel++) {
406  if (!m_channelFilter->IsGood(channel)) {
407  std::vector<geo::WireID> wireIDVec = m_geometry->ChannelToWire(channel);
408  geo::WireID wireID = wireIDVec[0];
410 
411  m_channelStatus[wireID.Plane][wireID.Wire] = chanStat;
413  }
414  }
415 
416  // add quiet wires in U plane for microboone (this will done "correctly" in near term)
417  // PlaneToWireToHitSetMap::iterator plane0HitItr = planeToWireToHitSetMap.find(geo::PlaneID(0,0,0));
418 
419  // if (plane0HitItr != planeToWireToHitSetMap.end())
420  // {
421  //// WireToHitSetMap& wireToHitSetMap = uPlaneHitItr->second;
422 
423  // for(size_t idx = 2016; idx < 2096; idx++) m_channelStatus[0][idx] = 3;
424  // for(size_t idx = 2192; idx < 2304; idx++) m_channelStatus[0][idx] = 3;
425  // for(size_t idx = 2352; idx < 2384; idx++) m_channelStatus[0][idx] = 3;
426  // //for(size_t idx = 2016; idx < 2096; idx++) if (wireToHitSetMap.find(idx) == wireToHitSetMap.end()) m_channelStatus[0][idx] = 3;
427  // //for(size_t idx = 2192; idx < 2304; idx++) if (wireToHitSetMap.find(idx) == wireToHitSetMap.end()) m_channelStatus[0][idx] = 3;
428  // //for(size_t idx = 2352; idx < 2384; idx++) if (wireToHitSetMap.find(idx) == wireToHitSetMap.end()) m_channelStatus[0][idx] = 3;
429  //// for(size_t idx = 2016; idx < 2384; idx++) m_channelStatus[0][idx] = 3;
430  // }
431 
432  return;
433  }
434 
435  bool
438  {
439  return (*left).getAvePeakTime() < (*right).getAvePeakTime();
440  }
441 
442  struct HitPairClusterOrder {
443  bool
446  {
447  // Watch out for the case where two clusters can have the same number of hits!
448  if (left->second.size() == right->second.size()) return left->first < right->first;
449 
450  return left->second.size() > right->second.size();
451  }
452  };
453 
454  void
456  reco::HitPairList& hitPairList,
457  RecobHitToPtrMap& clusterHitToArtPtrMap)
458  {
459  // Clear the internal data structures
460  m_clusterHit2DMasterList.clear();
461  m_planeToHitVectorMap.clear();
462  m_planeToWireToHitSetMap.clear();
463 
464  m_timeVector.resize(NUMTIMEVALUES, 0.);
465 
466  // Get a hit refiner
467  std::unique_ptr<std::vector<recob::Hit>> outputHitPtrVec(new std::vector<recob::Hit>);
468 
469  // Recover the 2D hits and then organize them into data structures which will be used in the
470  // DBscan algorithm for building the 3D clusters
471  this->CollectArtHits(evt);
472 
473  // If there are no hits in our view/wire data structure then do not proceed with the full analysis
474  if (!m_planeToWireToHitSetMap.empty()) {
475  // Call the algorithm that builds 3D hits
476  this->BuildHit3D(hitPairList);
477 
478  // If we built 3D points then attempt to output a new hit list as well
479  if (!hitPairList.empty())
480  CreateNewRecobHitCollection(evt, hitPairList, *outputHitPtrVec, clusterHitToArtPtrMap);
481  }
482 
483  // Set up to make the associations (if desired)
484  /// Associations with wires.
485  std::unique_ptr<art::Assns<recob::Wire, recob::Hit>> wireAssns(
487 
488  makeWireAssns(evt, *wireAssns, clusterHitToArtPtrMap);
489 
490  /// Associations with raw digits.
491  std::unique_ptr<art::Assns<raw::RawDigit, recob::Hit>> rawDigitAssns(
493 
494  makeRawDigitAssns(evt, *rawDigitAssns, clusterHitToArtPtrMap);
495 
496  // Move everything into the event
497  evt.put(std::move(outputHitPtrVec));
498  evt.put(std::move(wireAssns));
499  evt.put(std::move(rawDigitAssns));
500 
501  // Handle tree output too
502  if (m_outputHistograms) {
503  m_tupleTree->Fill();
504 
505  clear();
506  }
507 
508  return;
509  }
510 
511  void
513  {
514  /**
515  * @brief Driver for processing input 2D hits, transforming to 3D hits and building lists
516  * of associated 3D hits (candidate 3D clusters)
517  */
518  cet::cpu_timer theClockMakeHits;
519 
520  if (m_enableMonitoring) theClockMakeHits.start();
521 
522  // The first task is to take the lists of input 2D hits (a map of view to sorted lists of 2D hits)
523  // and then to build a list of 3D hits to be used in downstream processing
525 
526  size_t numHitPairs = BuildHitPairMap(m_planeToHitVectorMap, hitPairList);
527 
528  if (m_enableMonitoring) {
529  theClockMakeHits.stop();
530 
531  m_timeVector[BUILDTHREEDHITS] = theClockMakeHits.accumulated_real_time();
532  }
533 
534  mf::LogDebug("Cluster3D") << ">>>>> 3D hit building done, found " << numHitPairs << " 3D Hits"
535  << std::endl;
536 
537  return;
538  }
539 
540  //------------------------------------------------------------------------------------------------------------------------------------------
541  namespace {
542 
543  class SetHitEarliestTimeOrder {
544  public:
545  SetHitEarliestTimeOrder() : m_numRMS(1.) {}
546  SetHitEarliestTimeOrder(float numRMS) : m_numRMS(numRMS) {}
547 
548  bool
549  operator()(const reco::ClusterHit2D* left, const reco::ClusterHit2D* right) const
550  {
551  return left->getTimeTicks() - m_numRMS * left->getHit()->RMS() <
552  right->getTimeTicks() - m_numRMS * right->getHit()->RMS();
553  }
554 
555  public:
556  float m_numRMS;
557  };
558 
559  using HitVectorItrPair = std::pair<HitVector::iterator, HitVector::iterator>;
560 
561  class SetStartTimeOrder {
562  public:
563  SetStartTimeOrder() : m_numRMS(1.) {}
564  SetStartTimeOrder(float numRMS) : m_numRMS(numRMS) {}
565 
566  bool
567  operator()(const HitVectorItrPair& left, const HitVectorItrPair& right) const
568  {
569  // Protect against possible issue?
570  if (left.first != left.second && right.first != right.second) {
571  // Sort by "modified start time" of pulse
572  return (*left.first)->getTimeTicks() - m_numRMS * (*left.first)->getHit()->RMS() <
573  (*right.first)->getTimeTicks() - m_numRMS * (*right.first)->getHit()->RMS();
574  }
575 
576  return left.first != left.second;
577  }
578 
579  private:
580  float m_numRMS;
581  };
582 
583  bool
585  {
586  // Sort by "modified start time" of pulse
587  return left.getAvePeakTime() - left.getSigmaPeakTime() <
588  right.getAvePeakTime() - right.getSigmaPeakTime();
589  }
590  }
591 
592  //------------------------------------------------------------------------------------------------------------------------------------------
593 
594  size_t
596  reco::HitPairList& hitPairList) const
597  {
598  /**
599  * @brief Given input 2D hits, build out the lists of possible 3D hits
600  *
601  * The current strategy: ideally all 3D hits would be comprised of a triplet of 2D hits, one from each view
602  * However, we have concern that, in particular, the v-plane may have some inefficiency which we have to be
603  * be prepared to deal with. The idea, then, is to first make the association of hits in the U and W planes
604  * and then look for the match in the V plane. In the event we don't find the match in the V plane then we
605  * will evaluate the situation and in some instances keep the U-W pairs in order to keep efficiency high.
606  */
607  size_t totalNumHits(0);
608  size_t hitPairCntr(0);
609 
610  size_t nTriplets(0);
611  size_t nDeadChanHits(0);
612 
613  // Set up to loop over cryostats and tpcs...
614  for (size_t cryoIdx = 0; cryoIdx < m_geometry->Ncryostats(); cryoIdx++) {
615  for (size_t tpcIdx = 0; tpcIdx < m_geometry->NTPC(); tpcIdx++) {
617  planeToHitVectorMap.find(geo::PlaneID(cryoIdx, tpcIdx, 0));
619  planeToHitVectorMap.find(geo::PlaneID(cryoIdx, tpcIdx, 1));
621  planeToHitVectorMap.find(geo::PlaneID(cryoIdx, tpcIdx, 2));
622 
623  size_t nPlanesWithHits =
624  (mapItr0 != planeToHitVectorMap.end() && !mapItr0->second.empty() ? 1 : 0) +
625  (mapItr1 != planeToHitVectorMap.end() && !mapItr1->second.empty() ? 1 : 0) +
626  (mapItr2 != planeToHitVectorMap.end() && !mapItr2->second.empty() ? 1 : 0);
627 
628  if (nPlanesWithHits < 2) continue;
629 
630  HitVector& hitVector0 = mapItr0->second;
631  HitVector& hitVector1 = mapItr1->second;
632  HitVector& hitVector2 = mapItr2->second;
633 
634  // We are going to resort the hits into "start time" order...
635  std::sort(hitVector0.begin(),
636  hitVector0.end(),
637  SetHitEarliestTimeOrder(m_numSigmaPeakTime)); //SetHitStartTimeOrder);
638  std::sort(hitVector1.begin(),
639  hitVector1.end(),
640  SetHitEarliestTimeOrder(m_numSigmaPeakTime)); //SetHitStartTimeOrder);
641  std::sort(hitVector2.begin(),
642  hitVector2.end(),
643  SetHitEarliestTimeOrder(m_numSigmaPeakTime)); //SetHitStartTimeOrder);
644 
645  PlaneHitVectorItrPairVec hitItrVec = {
646  HitVectorItrPair(hitVector0.begin(), hitVector0.end()),
647  HitVectorItrPair(hitVector1.begin(), hitVector1.end()),
648  HitVectorItrPair(hitVector2.begin(), hitVector2.end())};
649 
650  totalNumHits += BuildHitPairMapByTPC(hitItrVec, hitPairList);
651  }
652  }
653 
654  // Return the hit pair list but sorted by z and y positions (faster traversal in next steps)
655  hitPairList.sort(SetPairStartTimeOrder);
656 
657  // Where are we?
658  mf::LogDebug("Cluster3D") << "Total number hits: " << totalNumHits << std::endl;
659  mf::LogDebug("Cluster3D") << "Created a total of " << hitPairList.size()
660  << " hit pairs, counted: " << hitPairCntr << std::endl;
661  mf::LogDebug("Cluster3D") << "-- Triplets: " << nTriplets
662  << ", dead channel pairs: " << nDeadChanHits << std::endl;
663 
664  return hitPairList.size();
665  }
666 
667  size_t
669  reco::HitPairList& hitPairList) const
670  {
671  /**
672  * @brief Given input 2D hits, build out the lists of possible 3D hits
673  *
674  * The current strategy: ideally all 3D hits would be comprised of a triplet of 2D hits, one from each view
675  * However, we have concern that, in particular, the v-plane may have some inefficiency which we have to be
676  * be prepared to deal with. The idea, then, is to first make the association of hits in the U and W planes
677  * and then look for the match in the V plane. In the event we don't find the match in the V plane then we
678  * will evaluate the situation and in some instances keep the U-W pairs in order to keep efficiency high.
679  */
680 
681  // Define functions to set start/end iterators in the loop below
682  auto SetStartIterator =
683  [](HitVector::iterator startItr, HitVector::iterator endItr, float rms, float startTime) {
684  while (startItr != endItr) {
685  float numRMS(rms);
686  if ((*startItr)->getTimeTicks() + numRMS * (*startItr)->getHit()->RMS() < startTime)
687  startItr++;
688  else
689  break;
690  }
691  return startItr;
692  };
693 
694  auto SetEndIterator =
695  [](HitVector::iterator firstItr, HitVector::iterator endItr, float rms, float endTime) {
696  while (firstItr != endItr) {
697  float numRMS(rms);
698  if ((*firstItr)->getTimeTicks() - numRMS * (*firstItr)->getHit()->RMS() < endTime)
699  firstItr++;
700  else
701  break;
702  }
703  return firstItr;
704  };
705 
706  size_t nTriplets(0);
707  size_t nDeadChanHits(0);
708 
709  //*********************************************************************************
710  // Basically, we try to loop until done...
711  while (1) {
712  // Sort so that the earliest hit time will be the first element, etc.
713  std::sort(hitItrVec.begin(), hitItrVec.end(), SetStartTimeOrder(m_numSigmaPeakTime));
714 
715  // This loop iteration's golden hit
716  const reco::ClusterHit2D* goldenHit = *hitItrVec[0].first;
717 
718  // The range of history... (for this hit)
719  float goldenTimeStart = goldenHit->getTimeTicks() -
720  m_numSigmaPeakTime * goldenHit->getHit()->RMS() -
721  std::numeric_limits<float>::epsilon();
722  float goldenTimeEnd = goldenHit->getTimeTicks() +
723  m_numSigmaPeakTime * goldenHit->getHit()->RMS() +
724  std::numeric_limits<float>::epsilon();
725 
726  // Set iterators to insure we'll be in the overlap ranges
727  HitVector::iterator hitItr1Start = SetStartIterator(
728  hitItrVec[1].first, hitItrVec[1].second, m_numSigmaPeakTime, goldenTimeStart);
729  HitVector::iterator hitItr1End =
730  SetEndIterator(hitItr1Start, hitItrVec[1].second, m_numSigmaPeakTime, goldenTimeEnd);
731  HitVector::iterator hitItr2Start = SetStartIterator(
732  hitItrVec[2].first, hitItrVec[2].second, m_numSigmaPeakTime, goldenTimeStart);
733  HitVector::iterator hitItr2End =
734  SetEndIterator(hitItr2Start, hitItrVec[2].second, m_numSigmaPeakTime, goldenTimeEnd);
735 
736  // Since we'll use these many times in the internal loops, pre make the pairs for the second set of hits
737  size_t curHitListSize(hitPairList.size());
738  HitMatchPairVecMap pair12Map;
739  HitMatchPairVecMap pair13Map;
740 
741  size_t n12Pairs = findGoodHitPairs(goldenHit, hitItr1Start, hitItr1End, pair12Map);
742  size_t n13Pairs = findGoodHitPairs(goldenHit, hitItr2Start, hitItr2End, pair13Map);
743 
744  nDeadChanHits += hitPairList.size() - curHitListSize;
745  curHitListSize = hitPairList.size();
746 
747  if (n12Pairs > n13Pairs)
748  findGoodTriplets(pair12Map, pair13Map, hitPairList);
749  else
750  findGoodTriplets(pair13Map, pair12Map, hitPairList);
751 
752  nTriplets += hitPairList.size() - curHitListSize;
753 
754  hitItrVec[0].first++;
755 
756  int nPlanesWithHits(0);
757 
758  for (auto& pair : hitItrVec)
759  if (pair.first != pair.second) nPlanesWithHits++;
760 
761  if (nPlanesWithHits < 2) break;
762  }
763 
764  return hitPairList.size();
765  }
766 
767  int
769  HitVector::iterator& startItr,
770  HitVector::iterator& endItr,
771  HitMatchPairVecMap& hitMatchMap) const
772  {
773  int numPairs(0);
774 
775  // Loop through the input secon hits and make pairs
776  while (startItr != endItr) {
777  const reco::ClusterHit2D* hit = *startItr++;
778  reco::ClusterHit3D pair;
779 
780  // pair returned with a negative ave time is signal of failure
781  if (!makeHitPair(pair, goldenHit, hit, m_hitWidthSclFctr)) continue;
782 
783  geo::WireID wireID = hit->WireID();
784 
785  hitMatchMap[wireID].emplace_back(hit, pair);
786 
787  numPairs++;
788  }
789 
790  return numPairs;
791  }
792 
793  void
795  HitMatchPairVecMap& pair13Map,
796  reco::HitPairList& hitPairList,
797  bool tagged) const
798  {
799  // Build triplets from the two lists of hit pairs
800  if (!pair12Map.empty()) {
801  // temporary container for dead channel hits
802  std::vector<reco::ClusterHit3D> tempDeadChanVec;
803  reco::ClusterHit3D deadChanPair;
804 
805  // Keep track of which third plane hits have been used
806  std::map<const reco::ClusterHit3D*, bool> usedPairMap;
807 
808  // Initial population of this map with the pair13Map hits
809  for (const auto& pair13 : pair13Map) {
810  for (const auto& hit2Dhit3DPair : pair13.second)
811  usedPairMap[&hit2Dhit3DPair.second] = false;
812  }
813 
814  // The outer loop is over all hit pairs made from the first two plane combinations
815  for (const auto& pair12 : pair12Map) {
816  if (pair12.second.empty()) continue;
817 
818  // This loop is over hit pairs that share the same first two plane wires but may have different
819  // hit times on those wires
820  for (const auto& hit2Dhit3DPair12 : pair12.second) {
821  const reco::ClusterHit3D& pair1 = hit2Dhit3DPair12.second;
822 
823  // populate the map with initial value
824  usedPairMap[&pair1] = false;
825 
826  // The simplest approach here is to loop over all possibilities and let the triplet builder weed out the weak candidates
827  for (const auto& pair13 : pair13Map) {
828  if (pair13.second.empty()) continue;
829 
830  for (const auto& hit2Dhit3DPair13 : pair13.second) {
831  const reco::ClusterHit2D* hit2 = hit2Dhit3DPair13.first;
832  const reco::ClusterHit3D& pair2 = hit2Dhit3DPair13.second;
833 
834  // If success try for the triplet
835  reco::ClusterHit3D triplet;
836 
837  if (makeHitTriplet(triplet, pair1, hit2)) {
838  triplet.setID(hitPairList.size());
839  hitPairList.emplace_back(triplet);
840  usedPairMap[&pair1] = true;
841  usedPairMap[&pair2] = true;
842  }
843  }
844  }
845  }
846  }
847 
848  // One more loop through the other pairs to check for sick channels
849  if (m_numBadChannels > 0) {
850  for (const auto& pairMapPair : usedPairMap) {
851  if (pairMapPair.second) continue;
852 
853  const reco::ClusterHit3D* pair = pairMapPair.first;
854 
855  // Here we look to see if we failed to make a triplet because the partner wire was dead/noisy/sick
856  if (makeDeadChannelPair(deadChanPair, *pair, 4, 0, 0.))
857  tempDeadChanVec.emplace_back(deadChanPair);
858  }
859 
860  // Handle the dead wire triplets
861  if (!tempDeadChanVec.empty()) {
862  // If we have many then see if we can trim down a bit by keeping those with time significance
863  if (tempDeadChanVec.size() > 1) {
864  // Sort by "significance" of agreement
865  std::sort(tempDeadChanVec.begin(),
866  tempDeadChanVec.end(),
867  [](const auto& left, const auto& right) {
868  return left.getDeltaPeakTime() / left.getSigmaPeakTime() <
869  right.getDeltaPeakTime() / right.getSigmaPeakTime();
870  });
871 
872  // What is the range of "significance" from first to last?
873  float firstSig = tempDeadChanVec.front().getDeltaPeakTime() /
874  tempDeadChanVec.front().getSigmaPeakTime();
875  float lastSig =
876  tempDeadChanVec.back().getDeltaPeakTime() / tempDeadChanVec.back().getSigmaPeakTime();
877  float sigRange = lastSig - firstSig;
878 
879  if (lastSig > 0.5 * m_deltaPeakTimeSig && sigRange > 0.5) {
880  // Declare a maximum of 1.5 * the average of the first and last pairs...
881  float maxSignificance = std::max(0.75 * (firstSig + lastSig), 1.0);
882 
883  std::vector<reco::ClusterHit3D>::iterator firstBadElem = std::find_if(
884  tempDeadChanVec.begin(),
885  tempDeadChanVec.end(),
886  [&maxSignificance](const auto& pair) {
887  return pair.getDeltaPeakTime() / pair.getSigmaPeakTime() > maxSignificance;
888  });
889 
890  // But only keep the best 10?
891  if (std::distance(tempDeadChanVec.begin(), firstBadElem) > 20)
892  firstBadElem = tempDeadChanVec.begin() + 20;
893  // Keep at least one hit...
894  else if (firstBadElem == tempDeadChanVec.begin())
895  firstBadElem++;
896 
897  tempDeadChanVec.resize(std::distance(tempDeadChanVec.begin(), firstBadElem));
898  }
899  }
900 
901  for (auto& pair : tempDeadChanVec) {
902  pair.setID(hitPairList.size());
903  hitPairList.emplace_back(pair);
904  }
905  }
906  }
907  }
908 
909  return;
910  }
911 
912  bool
914  const reco::ClusterHit2D* hit1,
915  const reco::ClusterHit2D* hit2,
916  float hitWidthSclFctr,
917  size_t hitPairCntr) const
918  {
919  // Assume failure
920  bool result(false);
921 
922  // We assume in this routine that we are looking at hits in different views
923  // The first mission is to check that the wires intersect
924  const geo::WireID& hit1WireID = hit1->WireID();
925  const geo::WireID& hit2WireID = hit2->WireID();
926 
927  geo::WireIDIntersection widIntersect;
928 
929  if (m_geometry->WireIDsIntersect(hit1WireID, hit2WireID, widIntersect)) {
930  // Wires intersect so now we can check the timing
931  float hit1Peak = hit1->getTimeTicks();
932  float hit1Sigma = hit1->getHit()->RMS();
933 
934  float hit2Peak = hit2->getTimeTicks();
935  float hit2Sigma = hit2->getHit()->RMS();
936 
937  // "Long hits" are an issue... so we deal with these differently
938  int hit1NDF = hit1->getHit()->DegreesOfFreedom();
939  int hit2NDF = hit2->getHit()->DegreesOfFreedom();
940 
941  // Basically, allow the range to extend to the nearest end of the snippet
942  if (hit1NDF < 2)
943  hit1Sigma = std::min(hit1Peak - float(hit1->getHit()->StartTick()),
944  float(hit1->getHit()->EndTick()) - hit1Peak);
945  if (hit2NDF < 2)
946  hit2Sigma = std::min(hit2Peak - float(hit2->getHit()->StartTick()),
947  float(hit2->getHit()->EndTick()) - hit2Peak);
948 
949  // The "hit sigma" is the gaussian fit sigma of the hit, we need to expand a bit to allow hit overlap efficiency
950  float hit1Width = hitWidthSclFctr * hit1Sigma;
951  float hit2Width = hitWidthSclFctr * hit2Sigma;
952 
953  // Coarse check hit times are "in range"
954  if (fabs(hit1Peak - hit2Peak) <= (hit1Width + hit2Width)) {
955  // Check to see that hit peak times are consistent with each other
956  float hit1SigSq = hit1Sigma * hit1Sigma;
957  float hit2SigSq = hit2Sigma * hit2Sigma;
958  float deltaPeakTime = std::fabs(hit1Peak - hit2Peak);
959  float sigmaPeakTime = std::sqrt(hit1SigSq + hit2SigSq);
960 
961  // delta peak time consistency check here
962  if (deltaPeakTime < m_deltaPeakTimeSig *
963  sigmaPeakTime) // 2 sigma consistency? (do this way to avoid divide)
964  {
965  float oneOverWghts = hit1SigSq * hit2SigSq / (hit1SigSq + hit2SigSq);
966  float avePeakTime = (hit1Peak / hit1SigSq + hit2Peak / hit2SigSq) * oneOverWghts;
967  float totalCharge = hit1->getHit()->Integral() + hit2->getHit()->Integral();
968  float hitChiSquare = std::pow((hit1Peak - avePeakTime), 2) / hit1SigSq +
969  std::pow((hit2Peak - avePeakTime), 2) / hit2SigSq;
970 
971  float xPositionHit1(hit1->getXPosition());
972  float xPositionHit2(hit2->getXPosition());
973  float xPosition = (xPositionHit1 / hit1SigSq + xPositionHit2 / hit2SigSq) * hit1SigSq *
974  hit2SigSq / (hit1SigSq + hit2SigSq);
975 
976  Eigen::Vector3f position(
977  xPosition, float(widIntersect.y), float(widIntersect.z) - m_zPosOffset);
978 
979  // If to here then we need to sort out the hit pair code telling what views are used
980  unsigned statusBits = 1 << hit1->WireID().Plane | 1 << hit2->WireID().Plane;
981 
982  // handle status bits for the 2D hits
987 
990 
991  reco::ClusterHit2DVec hitVector;
992 
993  hitVector.resize(3, NULL);
994 
995  hitVector[hit1->WireID().Plane] = hit1;
996  hitVector[hit2->WireID().Plane] = hit2;
997 
998  unsigned int cryostatIdx = hit1->WireID().Cryostat;
999  unsigned int tpcIdx = hit1->WireID().TPC;
1000 
1001  // Initialize the wireIdVec
1002  std::vector<geo::WireID> wireIDVec = {geo::WireID(cryostatIdx, tpcIdx, 0, 0),
1003  geo::WireID(cryostatIdx, tpcIdx, 1, 0),
1004  geo::WireID(cryostatIdx, tpcIdx, 2, 0)};
1005 
1006  wireIDVec[hit1->WireID().Plane] = hit1->WireID();
1007  wireIDVec[hit2->WireID().Plane] = hit2->WireID();
1008 
1009  // For compiling at the moment
1010  std::vector<float> hitDelTSigVec = {0., 0., 0.};
1011 
1012  hitDelTSigVec[hit1->WireID().Plane] = deltaPeakTime / sigmaPeakTime;
1013  hitDelTSigVec[hit2->WireID().Plane] = deltaPeakTime / sigmaPeakTime;
1014 
1015  // Create the 3D cluster hit
1016  hitPair.initialize(hitPairCntr,
1017  statusBits,
1018  position,
1019  totalCharge,
1020  avePeakTime,
1021  deltaPeakTime,
1022  sigmaPeakTime,
1023  hitChiSquare,
1024  0.,
1025  0.,
1026  0.,
1027  0.,
1028  hitVector,
1029  hitDelTSigVec,
1030  wireIDVec);
1031 
1032  result = true;
1033  }
1034  }
1035  }
1036 
1037  // Send it back
1038  return result;
1039  }
1040 
1041  bool
1043  const reco::ClusterHit3D& pair,
1044  const reco::ClusterHit2D* hit) const
1045  {
1046  // Assume failure
1047  bool result(false);
1048 
1049  // We are going to force the wire pitch here, some time in the future we need to fix
1050  static const double wirePitch =
1051  0.5 * m_wirePitchScaleFactor * *std::max_element(m_wirePitch, m_wirePitch + 3);
1052 
1053  // Recover hit info
1054  float hitTimeTicks = hit->getTimeTicks();
1055  float hitSigma = hit->getHit()->RMS();
1056 
1057  // Special case check
1058  if (hitSigma > 2. * hit->getHit()->PeakAmplitude())
1059  hitSigma = 2. * hit->getHit()->PeakAmplitude();
1060 
1061  // Let's do a quick consistency check on the input hits to make sure we are in range...
1062  // Require the W hit to be "in range" with the UV Pair
1063  if (fabs(hitTimeTicks - pair.getAvePeakTime()) <
1064  m_hitWidthSclFctr * (pair.getSigmaPeakTime() + hitSigma)) {
1065  // Check the distance from the point to the wire the hit is on
1066  float hitWireDist = DistanceFromPointToHitWire(pair.getPosition(), hit->WireID());
1067 
1068  if (m_outputHistograms) m_maxSideVecVec.push_back(hitWireDist);
1069 
1070  // Reject hits that are not within range
1071  if (hitWireDist < wirePitch) {
1072  if (m_outputHistograms) m_pairWireDistVec.push_back(hitWireDist);
1073 
1074  // Use the existing code to see the U and W hits are willing to pair with the V hit
1075  reco::ClusterHit3D pair0h;
1076  reco::ClusterHit3D pair1h;
1077 
1078  // Recover all the hits involved
1079  const reco::ClusterHit2DVec& pairHitVec = pair.getHits();
1080  const reco::ClusterHit2D* hit0 = pairHitVec[0];
1081  const reco::ClusterHit2D* hit1 = pairHitVec[1];
1082 
1083  if (!hit0)
1084  hit0 = pairHitVec[2];
1085  else if (!hit1)
1086  hit1 = pairHitVec[2];
1087 
1088  // If good pairs made here then we can try to make a triplet
1089  if (makeHitPair(pair0h, hit0, hit, m_hitWidthSclFctr) &&
1090  makeHitPair(pair1h, hit1, hit, m_hitWidthSclFctr)) {
1091  // Get a copy of the input hit vector (note the order is by plane - by definition)
1092  reco::ClusterHit2DVec hitVector = pair.getHits();
1093 
1094  // include the new hit
1095  hitVector[hit->WireID().Plane] = hit;
1096 
1097  // Set up to get average peak time, hitChiSquare, etc.
1098  unsigned int statusBits(0x7);
1099  float avePeakTime(0.);
1100  float weightSum(0.);
1101  float xPosition(0.);
1102 
1103  // And get the wire IDs
1104  std::vector<geo::WireID> wireIDVec = {geo::WireID(), geo::WireID(), geo::WireID()};
1105 
1106  // First loop through the hits to get WireIDs and calculate the averages
1107  for (size_t planeIdx = 0; planeIdx < 3; planeIdx++) {
1108  const reco::ClusterHit2D* hit2D = hitVector[planeIdx];
1109 
1110  wireIDVec[planeIdx] = hit2D->WireID();
1111 
1114 
1116 
1117  float hitRMS = hit2D->getHit()->RMS();
1118  float peakTime = hit2D->getTimeTicks();
1119 
1120  // Basically, allow the range to extend to the nearest end of the snippet
1121  if (hit2D->getHit()->DegreesOfFreedom() < 2)
1122  hitRMS = std::min(hit2D->getTimeTicks() - float(hit2D->getHit()->StartTick()),
1123  float(hit2D->getHit()->EndTick()) - hit2D->getTimeTicks());
1124 
1125  float weight = 1. / (hitRMS * hitRMS);
1126 
1127  avePeakTime += peakTime * weight;
1128  xPosition += hit2D->getXPosition() * weight;
1129  weightSum += weight;
1130  }
1131 
1132  avePeakTime /= weightSum;
1133  xPosition /= weightSum;
1134 
1135  Eigen::Vector2f pair0hYZVec(pair0h.getPosition()[1], pair0h.getPosition()[2]);
1136  Eigen::Vector2f pair1hYZVec(pair1h.getPosition()[1], pair1h.getPosition()[2]);
1137  Eigen::Vector2f pairYZVec(pair.getPosition()[1], pair.getPosition()[2]);
1138  Eigen::Vector3f position(xPosition,
1139  float((pairYZVec[0] + pair0hYZVec[0] + pair1hYZVec[0]) / 3.),
1140  float((pairYZVec[1] + pair0hYZVec[1] + pair1hYZVec[1]) / 3.));
1141 
1142  // Armed with the average peak time, now get hitChiSquare and the sig vec
1143  float hitChiSquare(0.);
1144  float sigmaPeakTime(std::sqrt(1. / weightSum));
1145  std::vector<float> hitDelTSigVec;
1146 
1147  for (const auto& hit2D : hitVector) {
1148  float hitRMS = hit2D->getHit()->RMS();
1149 
1150  // Basically, allow the range to extend to the nearest end of the snippet
1151  if (hit2D->getHit()->DegreesOfFreedom() < 2)
1152  hitRMS = std::min(hit2D->getTimeTicks() - float(hit2D->getHit()->StartTick()),
1153  float(hit2D->getHit()->EndTick()) - hit2D->getTimeTicks());
1154 
1155  float combRMS = std::sqrt(hitRMS * hitRMS - sigmaPeakTime * sigmaPeakTime);
1156  float peakTime = hit2D->getTimeTicks();
1157  float deltaTime = peakTime - avePeakTime;
1158  float hitSig = deltaTime / combRMS;
1159 
1160  hitChiSquare += hitSig * hitSig;
1161 
1162  hitDelTSigVec.emplace_back(std::fabs(hitSig));
1163  }
1164 
1165  if (m_outputHistograms) m_chiSquare3DVec.push_back(hitChiSquare);
1166 
1167  int lowMinIndex(std::numeric_limits<int>::max());
1168  int lowMaxIndex(std::numeric_limits<int>::min());
1169  int hiMinIndex(std::numeric_limits<int>::max());
1170  int hiMaxIndex(std::numeric_limits<int>::min());
1171 
1172  // First task is to get the min/max values for the common overlap region
1173  for (const auto& hit2D : hitVector) {
1174  float range = 2. * hit2D->getHit()->RMS();
1175 
1176  // Basically, allow the range to extend to the nearest end of the snippet
1177  if (hit2D->getHit()->DegreesOfFreedom() < 2)
1178  range = std::min(hit2D->getTimeTicks() - float(hit2D->getHit()->StartTick()),
1179  float(hit2D->getHit()->EndTick()) - hit2D->getTimeTicks());
1180 
1181  int hitStart = hit2D->getHit()->PeakTime() - range - 0.5;
1182  int hitStop = hit2D->getHit()->PeakTime() + range + 0.5;
1183 
1184  lowMinIndex = std::min(hitStart, lowMinIndex);
1185  lowMaxIndex = std::max(hitStart, lowMaxIndex);
1186  hiMinIndex = std::min(hitStop + 1, hiMinIndex);
1187  hiMaxIndex = std::max(hitStop + 1, hiMaxIndex);
1188  }
1189 
1190  // Keep only "good" hits...
1191  if (hitChiSquare < m_maxHit3DChiSquare && hiMinIndex > lowMaxIndex) {
1192  // One more pass through hits to get charge
1193  std::vector<float> chargeVec;
1194 
1195  for (const auto& hit2D : hitVector)
1196  chargeVec.push_back(chargeIntegral(hit2D->getHit()->PeakTime(),
1197  hit2D->getHit()->PeakAmplitude(),
1198  hit2D->getHit()->RMS(),
1199  1.,
1200  lowMaxIndex,
1201  hiMinIndex));
1202 
1203  float totalCharge =
1204  std::accumulate(chargeVec.begin(), chargeVec.end(), 0.) / float(chargeVec.size());
1205  float overlapRange = float(hiMinIndex - lowMaxIndex);
1206  float overlapFraction = overlapRange / float(hiMaxIndex - lowMinIndex);
1207 
1208  // Set up to compute the charge asymmetry
1209  std::vector<float> smallestChargeDiffVec;
1210  std::vector<float> chargeAveVec;
1211  float smallestDiff(std::numeric_limits<float>::max());
1212  float maxDeltaPeak(0.);
1213  size_t chargeIndex(0);
1214 
1215  for (size_t idx = 0; idx < 3; idx++) {
1216  size_t leftIdx = (idx + 2) % 3;
1217  size_t rightIdx = (idx + 1) % 3;
1218 
1219  smallestChargeDiffVec.push_back(std::abs(chargeVec[leftIdx] - chargeVec[rightIdx]));
1220  chargeAveVec.push_back(float(0.5 * (chargeVec[leftIdx] + chargeVec[rightIdx])));
1221 
1222  if (smallestChargeDiffVec.back() < smallestDiff) {
1223  smallestDiff = smallestChargeDiffVec.back();
1224  chargeIndex = idx;
1225  }
1226 
1227  // Take opportunity to look at peak time diff
1228  float deltaPeakTime =
1229  hitVector[leftIdx]->getTimeTicks() - hitVector[rightIdx]->getTimeTicks();
1230 
1231  if (std::abs(deltaPeakTime) > maxDeltaPeak) maxDeltaPeak = std::abs(deltaPeakTime);
1232 
1233  if (m_outputHistograms) m_deltaTimeVec.push_back(deltaPeakTime);
1234  }
1235 
1236  float chargeAsymmetry = (chargeAveVec[chargeIndex] - chargeVec[chargeIndex]) /
1237  (chargeAveVec[chargeIndex] + chargeVec[chargeIndex]);
1238 
1239  // If this is true there has to be a negative charge that snuck in somehow
1240  if (chargeAsymmetry < -1. || chargeAsymmetry > 1.) {
1241  const geo::WireID& hitWireID = hitVector[chargeIndex]->WireID();
1242 
1243  std::cout << "============> Charge asymmetry out of range: " << chargeAsymmetry
1244  << " <============" << std::endl;
1245  std::cout << " hit C: " << hitWireID.Cryostat << ", TPC: " << hitWireID.TPC
1246  << ", Plane: " << hitWireID.Plane << ", Wire: " << hitWireID.Wire
1247  << std::endl;
1248  std::cout << " charge: " << chargeVec[0] << ", " << chargeVec[1] << ", "
1249  << chargeVec[2] << std::endl;
1250  std::cout << " index: " << chargeIndex << ", smallest diff: " << smallestDiff
1251  << std::endl;
1252  return result;
1253  }
1254 
1255  // Usurping "deltaPeakTime" to be the maximum pull
1256  float deltaPeakTime = *std::max_element(hitDelTSigVec.begin(), hitDelTSigVec.end());
1257 
1258  if (m_outputHistograms) {
1259  m_smallChargeDiffVec.push_back(smallestDiff);
1260  m_smallIndexVec.push_back(chargeIndex);
1261  m_maxPullVec.push_back(deltaPeakTime);
1262  m_qualityMetricVec.push_back(hitChiSquare);
1263  m_spacePointChargeVec.push_back(totalCharge);
1264  m_overlapFractionVec.push_back(overlapFraction);
1265  m_overlapRangeVec.push_back(overlapRange);
1266  m_maxDeltaPeakVec.push_back(maxDeltaPeak);
1267  m_hitAsymmetryVec.push_back(chargeAsymmetry);
1268  }
1269 
1270  // Try to weed out cases where overlap doesn't match peak separation
1271  if (maxDeltaPeak > overlapRange) return result;
1272 
1273  // Create the 3D cluster hit
1274  hitTriplet.initialize(0,
1275  statusBits,
1276  position,
1277  totalCharge,
1278  avePeakTime,
1279  deltaPeakTime,
1280  sigmaPeakTime,
1281  hitChiSquare,
1282  overlapFraction,
1283  chargeAsymmetry,
1284  0.,
1285  0.,
1286  hitVector,
1287  hitDelTSigVec,
1288  wireIDVec);
1289 
1290  result = true;
1291  }
1292  }
1293  }
1294  }
1295 
1296  // return success/fail
1297  return result;
1298  }
1299 
1300  float
1302  float peakAmp,
1303  float peakSigma,
1304  float areaNorm,
1305  int low,
1306  int hi) const
1307  {
1308  float integral(0);
1309 
1310  for (int sigPos = low; sigPos < hi; sigPos++) {
1311  float arg = (float(sigPos) - peakMean + 0.5) / peakSigma;
1312  integral += peakAmp * std::exp(-0.5 * arg * arg);
1313  }
1314 
1315  return integral;
1316  }
1317 
1318  bool
1320  const reco::ClusterHit3D& pair,
1321  size_t maxChanStatus,
1322  size_t minChanStatus,
1323  float minOverlap) const
1324  {
1325  // Assume failure (most common result)
1326  bool result(false);
1327 
1328  const reco::ClusterHit2D* hit0 = pair.getHits()[0];
1329  const reco::ClusterHit2D* hit1 = pair.getHits()[1];
1330 
1331  size_t missPlane(2);
1332 
1333  // u plane hit is missing
1334  if (!hit0) {
1335  hit0 = pair.getHits()[2];
1336  missPlane = 0;
1337  }
1338  // v plane hit is missing
1339  else if (!hit1) {
1340  hit1 = pair.getHits()[2];
1341  missPlane = 1;
1342  }
1343 
1344  // Which plane is missing?
1345  geo::WireID wireID0 = hit0->WireID();
1346  geo::WireID wireID1 = hit1->WireID();
1347 
1348  // Ok, recover the wireID expected in the third plane...
1349  geo::WireID wireIn(wireID0.Cryostat, wireID0.TPC, missPlane, 0);
1350  geo::WireID wireID = NearestWireID(pair.getPosition(), wireIn);
1351 
1352  // There can be a round off issue so check the next wire as well
1353  bool wireStatus = m_channelStatus[wireID.Plane][wireID.Wire] < maxChanStatus &&
1354  m_channelStatus[wireID.Plane][wireID.Wire] >= minChanStatus;
1355  bool wireOneStatus = m_channelStatus[wireID.Plane][wireID.Wire + 1] < maxChanStatus &&
1356  m_channelStatus[wireID.Plane][wireID.Wire + 1] >= minChanStatus;
1357 
1358  // Make sure they are of at least the minimum status
1359  if (wireStatus || wireOneStatus) {
1360  // Sort out which is the wire we're dealing with
1361  if (!wireStatus) wireID.Wire += 1;
1362 
1363  // Want to refine position since we "know" the missing wire
1364  geo::WireIDIntersection widIntersect0;
1365 
1366  if (m_geometry->WireIDsIntersect(wireID0, wireID, widIntersect0)) {
1367  geo::WireIDIntersection widIntersect1;
1368 
1369  if (m_geometry->WireIDsIntersect(wireID1, wireID, widIntersect1)) {
1370  Eigen::Vector3f newPosition(
1371  pair.getPosition()[0], pair.getPosition()[1], pair.getPosition()[2]);
1372 
1373  newPosition[1] = (newPosition[1] + widIntersect0.y + widIntersect1.y) / 3.;
1374  newPosition[2] =
1375  (newPosition[2] + widIntersect0.z + widIntersect1.z - 2. * m_zPosOffset) / 3.;
1376 
1377  pairOut = pair;
1378  pairOut.setWireID(wireID);
1379  pairOut.setPosition(newPosition);
1380 
1385 
1388 
1389  result = true;
1390  }
1391  }
1392  }
1393 
1394  return result;
1395  }
1396 
1397  const reco::ClusterHit2D*
1399  const reco::ClusterHit3D& pair,
1400  float pairDeltaTimeLimits) const
1401  {
1402  static const float minCharge(0.);
1403 
1404  const reco::ClusterHit2D* bestVHit(0);
1405 
1406  float pairAvePeakTime(pair.getAvePeakTime());
1407 
1408  // Idea is to loop through the input set of hits and look for the best combination
1409  for (const auto& hit2D : hit2DSet) {
1410  if (hit2D->getHit()->Integral() < minCharge) continue;
1411 
1412  float hitVPeakTime(hit2D->getTimeTicks());
1413  float deltaPeakTime(pairAvePeakTime - hitVPeakTime);
1414 
1415  if (deltaPeakTime > pairDeltaTimeLimits) continue;
1416 
1417  if (deltaPeakTime < -pairDeltaTimeLimits) break;
1418 
1419  pairDeltaTimeLimits = fabs(deltaPeakTime);
1420  bestVHit = hit2D;
1421  }
1422 
1423  return bestVHit;
1424  }
1425 
1426  int
1428  const reco::ClusterHit3D& pair,
1429  float range) const
1430  {
1431  static const float minCharge(0.);
1432 
1433  int numberInRange(0);
1434  float pairAvePeakTime(pair.getAvePeakTime());
1435 
1436  // Idea is to loop through the input set of hits and look for the best combination
1437  for (const auto& hit2D : hit2DSet) {
1438  if (hit2D->getHit()->Integral() < minCharge) continue;
1439 
1440  float hitVPeakTime(hit2D->getTimeTicks());
1441  float deltaPeakTime(pairAvePeakTime - hitVPeakTime);
1442 
1443  if (deltaPeakTime > range) continue;
1444 
1445  if (deltaPeakTime < -range) break;
1446 
1447  numberInRange++;
1448  }
1449 
1450  return numberInRange;
1451  }
1452 
1453  geo::WireID
1455  const geo::WireID& wireIDIn) const
1456  {
1457  geo::WireID wireID = wireIDIn;
1458 
1459  // Embed the call to the geometry's services nearest wire id method in a try-catch block
1460  try {
1461  // Switch from NearestWireID to this method to avoid the roundoff error issues...
1462  double distanceToWire = m_geometry->Plane(wireIDIn).WireCoordinate(position.data());
1463 
1464  wireID.Wire = int(distanceToWire);
1465  }
1466  catch (std::exception& exc) {
1467  // This can happen, almost always because the coordinates are **just** out of range
1468  mf::LogWarning("Cluster3D") << "Exception caught finding nearest wire, position - "
1469  << exc.what() << std::endl;
1470 
1471  // Assume extremum for wire number depending on z coordinate
1472  if (position[2] < 0.5 * m_geometry->DetLength())
1473  wireID.Wire = 0;
1474  else
1475  wireID.Wire = m_geometry->Nwires(wireIDIn.Plane) - 1;
1476  }
1477 
1478  return wireID;
1479  }
1480 
1481  float
1483  const geo::WireID& wireIDIn) const
1484  {
1485  float distance;
1486 
1487  // Embed the call to the geometry's services nearest wire id method in a try-catch block
1488  try {
1489  // Get the wire endpoints
1490  Eigen::Vector3d wireStart;
1491  Eigen::Vector3d wireEnd;
1492 
1493  m_geometry->WireEndPoints(wireIDIn, &wireStart[0], &wireEnd[0]);
1494 
1495  // Want the hit position to have same x value as wire coordinates
1496  Eigen::Vector3d hitPosition(wireStart[0], position[1], position[2]);
1497 
1498  // Want the wire direction
1499  Eigen::Vector3d wireDir = wireEnd - wireStart;
1500 
1501  wireDir.normalize();
1502 
1503  // Get arc length to doca
1504  double arcLen = (hitPosition - wireStart).dot(wireDir);
1505 
1506  Eigen::Vector3d docaVec = hitPosition - (wireStart + arcLen * wireDir);
1507 
1508  distance = docaVec.norm();
1509  }
1510  catch (std::exception& exc) {
1511  // This can happen, almost always because the coordinates are **just** out of range
1512  mf::LogWarning("Cluster3D") << "Exception caught finding nearest wire, position - "
1513  << exc.what() << std::endl;
1514 
1515  // Assume extremum for wire number depending on z coordinate
1516  distance = 0.;
1517  }
1518 
1519  return distance;
1520  }
1521 
1522  //------------------------------------------------------------------------------------------------------------------------------------------
1523  bool
1524  SetHitTimeOrder(const reco::ClusterHit2D* left, const reco::ClusterHit2D* right)
1525  {
1526  // Sort by "modified start time" of pulse
1527  return left->getHit()->PeakTime() < right->getHit()->PeakTime();
1528  }
1529 
1530  bool
1531  Hit2DSetCompare::operator()(const reco::ClusterHit2D* left, const reco::ClusterHit2D* right) const
1532  {
1533  return left->getHit()->PeakTime() < right->getHit()->PeakTime();
1534  }
1535 
1536  //------------------------------------------------------------------------------------------------------------------------------------------
1537  void
1539  {
1540  /**
1541  * @brief Recover the 2D hits from art and fill out the local data structures for the 3D clustering
1542  */
1543 
1544  // Start by getting a vector of valid, non empty hit collections to make sure we really have something to do here...
1545  // Here is a container for the hits...
1546  std::vector<const recob::Hit*> recobHitVec;
1547 
1548  auto const clock_data =
1550  auto const det_prop =
1552 
1553  // Loop through the list of input sources
1554  for (const auto& inputTag : m_hitFinderTagVec) {
1555  art::Handle<std::vector<recob::Hit>> recobHitHandle;
1556  evt.getByLabel(inputTag, recobHitHandle);
1557 
1558  if (!recobHitHandle.isValid() || recobHitHandle->size() == 0) continue;
1559 
1560  recobHitVec.reserve(recobHitVec.size() + recobHitHandle->size());
1561 
1562  for (const auto& hit : *recobHitHandle)
1563  recobHitVec.push_back(&hit);
1564  }
1565 
1566  // If the vector is empty there is nothing to do
1567  if (recobHitVec.empty()) return;
1568 
1569  cet::cpu_timer theClockMakeHits;
1570 
1571  if (m_enableMonitoring) theClockMakeHits.start();
1572 
1573  // We'll want to correct the hit times for the plane offsets
1574  // (note this is already taken care of when converting to position)
1575  std::map<geo::PlaneID, double> planeOffsetMap;
1576 
1577  // Try to output a formatted string
1578  std::string debugMessage("");
1579 
1580  // Initialize the plane to hit vector map
1581  for (size_t cryoIdx = 0; cryoIdx < m_geometry->Ncryostats(); cryoIdx++) {
1582  for (size_t tpcIdx = 0; tpcIdx < m_geometry->NTPC(); tpcIdx++) {
1583  m_planeToHitVectorMap[geo::PlaneID(cryoIdx, tpcIdx, 0)] = HitVector();
1584  m_planeToHitVectorMap[geo::PlaneID(cryoIdx, tpcIdx, 1)] = HitVector();
1585  m_planeToHitVectorMap[geo::PlaneID(cryoIdx, tpcIdx, 2)] = HitVector();
1586 
1587  // What we want here are the relative offsets between the planes
1588  // Note that plane 0 is assumed the "first" plane and is the reference
1589  planeOffsetMap[geo::PlaneID(cryoIdx, tpcIdx, 0)] = 0.;
1590  planeOffsetMap[geo::PlaneID(cryoIdx, tpcIdx, 1)] =
1591  det_prop.GetXTicksOffset(geo::PlaneID(cryoIdx, tpcIdx, 1)) -
1592  det_prop.GetXTicksOffset(geo::PlaneID(cryoIdx, tpcIdx, 0));
1593  planeOffsetMap[geo::PlaneID(cryoIdx, tpcIdx, 2)] =
1594  det_prop.GetXTicksOffset(geo::PlaneID(cryoIdx, tpcIdx, 2)) -
1595  det_prop.GetXTicksOffset(geo::PlaneID(cryoIdx, tpcIdx, 0));
1596 
1597  // Should we provide output?
1599  std::ostringstream outputString;
1600 
1601  outputString << "***> plane 0 offset: "
1602  << planeOffsetMap[geo::PlaneID(cryoIdx, tpcIdx, 0)]
1603  << ", plane 1: " << planeOffsetMap[geo::PlaneID(cryoIdx, tpcIdx, 1)]
1604  << ", plane 2: " << planeOffsetMap[geo::PlaneID(cryoIdx, tpcIdx, 2)]
1605  << "\n";
1606  debugMessage += outputString.str();
1607  outputString << " Det prop plane 0: "
1608  << det_prop.GetXTicksOffset(geo::PlaneID(cryoIdx, tpcIdx, 0))
1609  << ", plane 1: "
1610  << det_prop.GetXTicksOffset(geo::PlaneID(cryoIdx, tpcIdx, 1))
1611  << ", plane 2: "
1612  << det_prop.GetXTicksOffset(geo::PlaneID(cryoIdx, tpcIdx, 2))
1613  << ", Trig: " << trigger_offset(clock_data) << "\n";
1614  debugMessage += outputString.str();
1615  }
1616  }
1617  }
1618 
1620  mf::LogDebug("Cluster3D") << debugMessage << std::endl;
1621 
1623  }
1624 
1625  // Cycle through the recob hits to build ClusterHit2D objects and insert
1626  // them into the map
1627  for (const auto& recobHit : recobHitVec) {
1628  // Reject hits with negative charge, these are misreconstructed
1629  if (recobHit->Integral() < 0.) continue;
1630 
1631  // For some detectors we can have multiple wire ID's associated to a given channel.
1632  // So we recover the list of these wire IDs
1633  const std::vector<geo::WireID>& wireIDs = m_geometry->ChannelToWire(recobHit->Channel());
1634 
1635  // And then loop over all possible to build out our maps
1636  for (const auto& wireID : wireIDs) {
1637  // Check if this is an invalid TPC
1638  // (for example, in protoDUNE there are logical TPC's which see no signal)
1639  if (std::find(m_invalidTPCVec.begin(), m_invalidTPCVec.end(), wireID.TPC) !=
1640  m_invalidTPCVec.end())
1641  continue;
1642 
1643  // Note that a plane ID will define cryostat, TPC and plane
1644  const geo::PlaneID& planeID = wireID.planeID();
1645 
1646  double hitPeakTime(recobHit->PeakTime() - planeOffsetMap[planeID]);
1647  double xPosition(det_prop.ConvertTicksToX(
1648  recobHit->PeakTime(), planeID.Plane, planeID.TPC, planeID.Cryostat));
1649 
1650  m_clusterHit2DMasterList.emplace_back(0, 0., 0., xPosition, hitPeakTime, wireID, recobHit);
1651 
1652  m_planeToHitVectorMap[planeID].push_back(&m_clusterHit2DMasterList.back());
1653  m_planeToWireToHitSetMap[planeID][wireID.Wire].insert(&m_clusterHit2DMasterList.back());
1654  }
1655  }
1656 
1657  // Make a loop through to sort the recover hits in time order
1658  for (auto& hitVectorMap : m_planeToHitVectorMap)
1659  std::sort(hitVectorMap.second.begin(), hitVectorMap.second.end(), SetHitTimeOrder);
1660 
1661  if (m_enableMonitoring) {
1662  theClockMakeHits.stop();
1663 
1664  m_timeVector[COLLECTARTHITS] = theClockMakeHits.accumulated_real_time();
1665  }
1666 
1667  mf::LogDebug("Cluster3D") << ">>>>> Number of ART hits: " << m_clusterHit2DMasterList.size()
1668  << std::endl;
1669  }
1670 
1671  //------------------------------------------------------------------------------------------------------------------------------------------
1672 
1673  void
1675  reco::HitPairList& hitPairList,
1676  std::vector<recob::Hit>& hitPtrVec,
1677  RecobHitToPtrMap& recobHitToPtrMap)
1678  {
1679  // Set up the timing
1680  cet::cpu_timer theClockBuildNewHits;
1681 
1682  if (m_enableMonitoring) theClockBuildNewHits.start();
1683 
1684  // We want to build a unique list of hits from the input 3D points which, we know, reuse 2D points frequently
1685  // At the same time we need to create a new 2D hit with the "correct" WireID and replace the old 2D hit in the
1686  // ClusterHit2D object with this new one... while keeping track of the use of the old ones. My head is spinning...
1687  // Declare a set which will allow us to keep track of those CusterHit2D objects we have seen already
1688  std::set<const reco::ClusterHit2D*> visitedHit2DSet;
1689 
1690  // Use this handy art utility to make art::Ptr objects to the new recob::Hits for use in the output phase
1691  art::PtrMaker<recob::Hit> ptrMaker(event);
1692 
1693  // Reserve enough memory to replace every recob::Hit we have considered (this is upper limit)
1694  hitPtrVec.reserve(m_clusterHit2DMasterList.size());
1695 
1696  // Scheme is to loop through all 3D hits, then through each associated ClusterHit2D object
1697  for (reco::ClusterHit3D& hit3D : hitPairList) {
1698  reco::ClusterHit2DVec& hit2DVec = hit3D.getHits();
1699 
1700  // The loop is over the index so we can recover the correct WireID to associate to the new hit when made
1701  for (size_t idx = 0; idx < hit3D.getHits().size(); idx++) {
1702  const reco::ClusterHit2D* hit2D = hit2DVec[idx];
1703 
1704  // Have we seen this 2D hit already?
1705  if (visitedHit2DSet.find(hit2D) == visitedHit2DSet.end()) {
1706  visitedHit2DSet.insert(hit2D);
1707 
1708  // Create and save the new recob::Hit with the correct WireID
1709  hitPtrVec.emplace_back(
1710  recob::HitCreator(*hit2D->getHit(), hit3D.getWireIDs()[idx]).copy());
1711 
1712  // Recover a pointer to it...
1713  recob::Hit* newHit = &hitPtrVec.back();
1714 
1715  // Create a mapping from this hit to an art Ptr representing it
1716  recobHitToPtrMap[newHit] = ptrMaker(hitPtrVec.size() - 1);
1717 
1718  // And set the pointer to this hit in the ClusterHit2D object
1719  const_cast<reco::ClusterHit2D*>(hit2D)->setHit(newHit);
1720  }
1721  }
1722  }
1723 
1724  size_t numNewHits = hitPtrVec.size();
1725 
1726  if (m_enableMonitoring) {
1727  theClockBuildNewHits.stop();
1728 
1729  m_timeVector[BUILDNEWHITS] = theClockBuildNewHits.accumulated_real_time();
1730  }
1731 
1732  mf::LogDebug("Cluster3D") << ">>>>> New output recob::Hit size: " << numNewHits << " (vs "
1733  << m_clusterHit2DMasterList.size() << " input)" << std::endl;
1734 
1735  return;
1736  }
1737 
1738  void
1741  RecobHitToPtrMap& recobHitPtrMap) const
1742  {
1743  // Let's make sure the input associations container is empty
1745 
1746  // First task is to recover all of the previous wire <--> hit associations and map them by channel number
1747  // Create the temporary container
1748  std::unordered_map<raw::ChannelID_t, art::Ptr<recob::Wire>> channelToWireMap;
1749 
1750  // Go through the list of input sources and fill out the map
1751  for (const auto& inputTag : m_hitFinderTagVec) {
1753  evt.getValidHandle<std::vector<recob::Hit>>(inputTag);
1754 
1755  art::FindOneP<recob::Wire> hitToWireAssns(hitHandle, evt, inputTag);
1756 
1757  if (hitToWireAssns.isValid()) {
1758  for (size_t wireIdx = 0; wireIdx < hitToWireAssns.size(); wireIdx++) {
1759  art::Ptr<recob::Wire> wire = hitToWireAssns.at(wireIdx);
1760 
1761  channelToWireMap[wire->Channel()] = wire;
1762  }
1763  }
1764  }
1765 
1766  // Now fill the container
1767  for (const auto& hitPtrPair : recobHitPtrMap) {
1768  raw::ChannelID_t channel = hitPtrPair.first->Channel();
1769 
1770  std::unordered_map<raw::ChannelID_t, art::Ptr<recob::Wire>>::iterator chanWireItr =
1771  channelToWireMap.find(channel);
1772 
1773  if (!(chanWireItr != channelToWireMap.end())) {
1774  mf::LogDebug("Cluster3D") << "** Did not find channel to wire match! Skipping..."
1775  << std::endl;
1776  continue;
1777  }
1778 
1779  wireAssns.addSingle(chanWireItr->second, hitPtrPair.second);
1780  }
1781 
1782  return;
1783  }
1784 
1785  void
1788  RecobHitToPtrMap& recobHitPtrMap) const
1789  {
1790  // Let's make sure the input associations container is empty
1791  rawDigitAssns = art::Assns<raw::RawDigit, recob::Hit>();
1792 
1793  // First task is to recover all of the previous wire <--> hit associations and map them by channel number
1794  // Create the temporary container
1795  std::unordered_map<raw::ChannelID_t, art::Ptr<raw::RawDigit>> channelToRawDigitMap;
1796 
1797  // Go through the list of input sources and fill out the map
1798  for (const auto& inputTag : m_hitFinderTagVec) {
1800  evt.getValidHandle<std::vector<recob::Hit>>(inputTag);
1801 
1802  art::FindOneP<raw::RawDigit> hitToRawDigitAssns(hitHandle, evt, inputTag);
1803 
1804  if (hitToRawDigitAssns.isValid()) {
1805  for (size_t rawDigitIdx = 0; rawDigitIdx < hitToRawDigitAssns.size(); rawDigitIdx++) {
1806  art::Ptr<raw::RawDigit> rawDigit = hitToRawDigitAssns.at(rawDigitIdx);
1807 
1808  channelToRawDigitMap[rawDigit->Channel()] = rawDigit;
1809  }
1810  }
1811  }
1812 
1813  // Now fill the container
1814  for (const auto& hitPtrPair : recobHitPtrMap) {
1815  raw::ChannelID_t channel = hitPtrPair.first->Channel();
1816 
1817  std::unordered_map<raw::ChannelID_t, art::Ptr<raw::RawDigit>>::iterator chanRawDigitItr =
1818  channelToRawDigitMap.find(channel);
1819 
1820  if (!(chanRawDigitItr != channelToRawDigitMap.end())) {
1821  mf::LogDebug("Cluster3D") << "** Did not find channel to wire match! Skipping..."
1822  << std::endl;
1823  continue;
1824  }
1825 
1826  rawDigitAssns.addSingle(chanRawDigitItr->second, hitPtrPair.second);
1827  }
1828 
1829  return;
1830  }
1831 
1832  //------------------------------------------------------------------------------------------------------------------------------------------
1833 
1835 } // namespace lar_cluster3d
void initialize(size_t id, unsigned int statusBits, const Eigen::Vector3f &position, float totalCharge, float avePeakTime, float deltaPeakTime, float sigmaPeakTime, float hitChiSquare, float overlapFraction, float chargeAsymmetry, float docaToAxis, float arclenToPoca, const ClusterHit2DVec &hitVec, const std::vector< float > &hitDelTSigVec, const std::vector< geo::WireID > &wireIDVec)
Definition: Cluster3D.cxx:137
intermediate_table::iterator iterator
bool makeHitTriplet(reco::ClusterHit3D &pairOut, const reco::ClusterHit3D &pairIn, const reco::ClusterHit2D *hit2) const
Make a 3D HitPair object by checking two hits.
float m_wirePitchScaleFactor
Scaling factor to determine max distance allowed between candidate pairs.
void setWireID(const geo::WireID &wid) const
Definition: Cluster3D.cxx:172
double z
z position of intersection
Definition: geo_types.h:805
std::list< reco::ClusterHit3D > HitPairList
Definition: Cluster3D.h:339
#define DEFINE_ART_CLASS_TOOL(tool)
Definition: ToolMacros.h:42
PlaneGeo const & Plane(unsigned int const p, unsigned int const tpc=0, unsigned int const cstat=0) const
Returns the specified wire.
void CreateNewRecobHitCollection(art::Event &, reco::HitPairList &, std::vector< recob::Hit > &, RecobHitToPtrMap &)
Create a new 2D hit collection from hits associated to 3D space points.
float getTimeTicks() const
Definition: Cluster3D.h:76
static QCString result
double rms(sqlite3 *db, std::string const &table_name, std::string const &column_name)
Definition: statistics.cc:40
T * get() const
Definition: ServiceHandle.h:63
std::vector< size_t > ChannelStatusVec
define data structure for keeping track of channel status
std::string string
Definition: nybbler.cc:12
const Eigen::Vector3f getPosition() const
Definition: Cluster3D.h:158
bool SetHitTimeOrder(const reco::ClusterHit2D *left, const reco::ClusterHit2D *right)
float RMS() const
RMS of the hit shape, in tick units.
Definition: Hit.h:220
size_t BuildHitPairMapByTPC(PlaneHitVectorItrPairVec &planeHitVectorItrPairVec, reco::HitPairList &hitPairList) const
constexpr T pow(T x)
Definition: pow.h:72
The data type to uniquely identify a Plane.
Definition: geo_types.h:472
const geo::WireID & WireID() const
Definition: Cluster3D.h:77
ChannelID_t Channel() const
DAQ channel this raw data was read from.
Definition: RawDigit.h:212
int findGoodHitPairs(const reco::ClusterHit2D *, HitVector::iterator &, HitVector::iterator &, HitMatchPairVecMap &) const
bool operator()(const reco::ClusterHit2D *, const reco::ClusterHit2D *) const
int DegreesOfFreedom() const
Definition: Hit.h:229
std::vector< geo::WireID > ChannelToWire(raw::ChannelID_t const channel) const
Returns a list of wires connected to the specified TPC channel.
CryostatID_t Cryostat
Index of cryostat.
Definition: geo_types.h:212
void BuildHit3D(reco::HitPairList &hitPairList) const
Given the ClusterHit2D objects, build the HitPairMap.
std::map< geo::WireID, HitMatchPairVec > HitMatchPairVecMap
bool makeHitPair(reco::ClusterHit3D &pairOut, const reco::ClusterHit2D *hit1, const reco::ClusterHit2D *hit2, float hitWidthSclFctr=1., size_t hitPairCntr=0) const
Make a HitPair object by checking two hits.
void produces(art::ProducesCollector &) override
Each algorithm may have different objects it wants "produced" so use this to let the top level produc...
float Integral() const
Integral under the calibrated signal waveform of the hit, in tick x ADC units.
Definition: Hit.h:224
WireID_t Wire
Index of the wire within its plane.
Definition: geo_types.h:580
float getSigmaPeakTime() const
Definition: Cluster3D.h:165
uint8_t channel
Definition: CRTFragment.hh:201
void Hit3DBuilder(art::Event &, reco::HitPairList &, RecobHitToPtrMap &) override
Given a set of recob hits, run DBscan to form 3D clusters.
std::map< geo::PlaneID, WireToHitSetMap > PlaneToWireToHitSetMap
unsigned int Ncryostats() const
Returns the number of cryostats in the detector.
bool SetPeakHitPairIteratorOrder(const HitPairList::iterator &left, const HitPairList::iterator &right)
weight
Definition: test.py:257
unsigned int Nwires(unsigned int p, unsigned int tpc=0, unsigned int cstat=0) const
Returns the total number of wires in the specified plane.
const reco::ClusterHit2D * FindBestMatchingHit(const Hit2DSet &hit2DSet, const reco::ClusterHit3D &pair, float pairDeltaTimeLimits) const
A utility routine for finding a 2D hit closest in time to the given pair.
art framework interface to geometry description
const recob::Hit * getHit() const
Definition: Cluster3D.h:78
unsigned int Nchannels() const
Returns the number of TPC readout channels in the detector.
float PeakAmplitude() const
The estimated amplitude of the hit at its peak, in ADC units.
Definition: Hit.h:221
float m_maxHit3DChiSquare
Provide ability to select hits based on "chi square".
bool isValid() const noexcept
Definition: Handle.h:191
unsigned short Status_t
type representing channel status
float getAvePeakTime() const
Definition: Cluster3D.h:163
std::map< geo::View_t, HitVector > HitVectorMap
Class managing the creation of a new recob::Hit object.
Definition: HitCreator.h:83
void makeWireAssns(const art::Event &, art::Assns< recob::Wire, recob::Hit > &, RecobHitToPtrMap &) const
Create recob::Wire to recob::Hit associations.
geo::WireID NearestWireID(const Eigen::Vector3f &position, const geo::WireID &wireID) const
Jacket the calls to finding the nearest wire in order to intercept the exceptions if out of range...
T abs(T value)
Helper functions to create a hit.
std::vector< art::InputTag > m_hitFinderTagVec
Data members to follow.
bool getByLabel(std::string const &label, std::string const &instance, Handle< PROD > &result) const
Definition: DataViewImpl.h:633
unsigned int Nplanes(unsigned int tpc=0, unsigned int cstat=0) const
Returns the total number of wire planes in the specified TPC.
void produces(std::string const &instanceName={}, Persistable const persistable=Persistable::Yes)
std::list< reco::ClusterHit2D > Hit2DList
IDparameter< geo::WireID > WireID
Member type of validated geo::WireID parameter.
bool m_outputHistograms
Take the time to create and fill some histograms for diagnostics.
std::map< geo::PlaneID, HitVector > PlaneToHitVectorMap
raw::ChannelID_t Channel() const
Returns the ID of the channel (or InvalidChannelID)
Definition: Wire.h:231
def move(depos, offset)
Definition: depos.py:107
T get(std::string const &key) const
Definition: ParameterSet.h:271
StandardHit3DBuilder class definiton.
int FindNumberInRange(const Hit2DSet &hit2DSet, const reco::ClusterHit3D &pair, float range) const
A utility routine for returning the number of 2D hits from the list in a given range.
ValidHandle< PROD > getValidHandle(InputTag const &tag) const
Definition: DataViewImpl.h:441
virtual Status_t Status(raw::ChannelID_t channel) const
Returns a status integer with arbitrary meaning.
geo::Length_t DetLength(geo::TPCID const &tpcid) const
Returns the length of the active volume of the specified TPC.
virtual bool IsGood(raw::ChannelID_t channel) const
Returns whether the specified channel is physical and good.
ProductID put(std::unique_ptr< PROD > &&edp, std::string const &instance={})
Definition: DataViewImpl.h:686
void CollectArtHits(const art::Event &evt) const
Extract the ART hits and the ART hit-particle relationships.
float getXPosition() const
Definition: Cluster3D.h:75
TimeValues
enumerate the possible values for time checking if monitoring timing
Definition: IHit3DBuilder.h:69
double distance(double x1, double y1, double z1, double x2, double y2, double z2)
Class providing information about the quality of channels.
static int max(int a, int b)
The geometry of one entire detector, as served by art.
Definition: Geometry.h:196
PlaneID_t Plane
Index of the plane within its TPC.
Definition: geo_types.h:493
raw::TDCtick_t StartTick() const
Initial tdc tick for hit.
Definition: Hit.h:216
float chargeIntegral(float, float, float, float, int, int) const
Perform charge integration between limits.
IHit3DBuilder interface class definiton.
Definition: IHit3DBuilder.h:32
Detector simulation of raw signals on wires.
float DistanceFromPointToHitWire(const Eigen::Vector3f &position, const geo::WireID &wireID) const
Jacket the calls to finding the nearest wire in order to intercept the exceptions if out of range...
void makeRawDigitAssns(const art::Event &, art::Assns< raw::RawDigit, recob::Hit > &, RecobHitToPtrMap &) const
Create raw::RawDigit to recob::Hit associations.
const_iterator end() const
Definition: Assns.h:511
This provides an art tool interface definition for tools which construct 3D hits used in 3D clusterin...
std::vector< const reco::ClusterHit2D * > ClusterHit2DVec
Definition: Cluster3D.h:92
void findGoodTriplets(HitMatchPairVecMap &, HitMatchPairVecMap &, reco::HitPairList &, bool=false) const
This algorithm takes lists of hit pairs and finds good triplets.
std::set< const reco::ClusterHit2D *, Hit2DSetCompare > Hit2DSet
unsigned int NTPC(unsigned int cstat=0) const
Returns the total number of TPCs in the specified cryostat.
raw::TDCtick_t EndTick() const
Final tdc tick for hit.
Definition: Hit.h:217
const lariov::ChannelStatusProvider * m_channelFilter
std::map< geo::TPCID, PlaneToWireToHitSetMap > TPCToPlaneToWireToHitSetMap
std::vector< reco::ClusterHit2D * > HitVector
What follows are several highly useful typedefs which we want to expose to the outside world...
bool WireIDsIntersect(WireID const &wid1, WireID const &wid2, geo::Point_t &intersection) const
Computes the intersection between two wires.
bool operator()(const reco::HitPairClusterMap::iterator &left, const reco::HitPairClusterMap::iterator &right)
std::map< geo::TPCID, PlaneToHitVectorMap > TPCToPlaneToHitVectorMap
float PeakTime() const
Time of the signal peak, in tick units.
Definition: Hit.h:218
Declaration of signal hit object.
std::pair< const reco::ClusterHit2D *, reco::ClusterHit3D > HitMatchPair
This builds a list of candidate hit pairs from lists of hits on two planes.
T min(sqlite3 *const db, std::string const &table_name, std::string const &column_name)
Definition: statistics.h:55
void setID(const size_t &id) const
Definition: Cluster3D.h:179
std::vector< ChannelStatusVec > ChannelStatusByPlaneVec
double y
y position of intersection
Definition: geo_types.h:804
std::vector< std::pair< HitVector::iterator, HitVector::iterator >> PlaneHitVectorItrPairVec
Given the ClusterHit2D objects, build the HitPairMap.
MaybeLogger_< ELseverityLevel::ELsev_success, false > LogDebug
void addSingle(Ptr< left_t > const &left, Ptr< right_t > const &right, data_t const &data)
Definition: Assns.h:546
size_t BuildHitPairMap(PlaneToHitVectorMap &planeToHitVectorMap, reco::HitPairList &hitPairList) const
Given the ClusterHit2D objects, build the HitPairMap.
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
Interface for experiment-specific channel quality info provider.
T copy(T const &v)
int trigger_offset(DetectorClocksData const &data)
void WireEndPoints(geo::WireID const &wireid, double *xyzStart, double *xyzEnd) const
Fills two arrays with the coordinates of the wire end points.
StandardHit3DBuilder(fhicl::ParameterSet const &pset)
Constructor.
constexpr WireID()=default
Default constructor: an invalid TPC ID.
detail::Node< FrameID, bool > PlaneID
Definition: CRTID.h:125
vector< vector< double > > clear
2D representation of charge deposited in the TDC/wire plane
Definition: Hit.h:48
bool makeDeadChannelPair(reco::ClusterHit3D &pairOut, const reco::ClusterHit3D &pair, size_t maxStatus=4, size_t minStatus=0, float minOverlap=0.2) const
Make a 3D HitPair object from a valid pair and a dead channel in the missing plane.
double WireCoordinate(Point const &point) const
Returns the coordinate of the point on the plane, in wire units.
Definition: PlaneGeo.h:882
double accumulated_real_time() const
Definition: cpu_timer.cc:66
TCEvent evt
Definition: DataStructs.cxx:7
unsigned int ChannelID_t
Type representing the ID of a readout channel.
Definition: RawTypes.h:28
TPCID_t TPC
Index of the TPC within its cryostat.
Definition: geo_types.h:406
Interface for experiment-specific service for channel quality info.
second_as<> second
Type of time stored in seconds, in double precision.
Definition: spacetime.h:85
LArSoft geometry interface.
Definition: ChannelGeo.h:16
float getTimeToExecute(IHit3DBuilder::TimeValues index) const override
If monitoring, recover the time to execute a particular function.
const ClusterHit2DVec & getHits() const
Definition: Cluster3D.h:171
void start()
Definition: cpu_timer.cc:83
void clear()
clear the tuple vectors before processing next event
std::unordered_map< const recob::Hit *, art::Ptr< recob::Hit >> RecobHitToPtrMap
Defines a structure mapping art representation to internal.
Definition: IHit3DBuilder.h:56
void setPosition(const Eigen::Vector3f &pos) const
Definition: Cluster3D.h:186
bool SetPairStartTimeOrder(const reco::ClusterHit3D &left, const reco::ClusterHit3D &right)
void configure(const fhicl::ParameterSet &) override
Interface for configuring the particular algorithm tool.
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
QTextStream & endl(QTextStream &s)
Event finding and building.
void BuildChannelStatusVec(PlaneToWireToHitSetMap &planeToWiretoHitSetMap) const
Create the internal channel status vector (assume will eventually be event-by-event) ...
std::map< unsigned int, Hit2DSet > WireToHitSetMap
unsigned getStatusBits() const
Definition: Cluster3D.h:72
void setStatusBit(unsigned bits) const
Definition: Cluster3D.h:80