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