All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
QuadVtx_module.cc
Go to the documentation of this file.
1 // Chris Backhouse - c.backhouse@ucl.ac.uk - Oct 2019
2 
4 
5 // C/C++ standard libraries
6 #include <iostream>
7 #include <random>
8 #include <string>
9 
10 // framework libraries
11 #include "art/Framework/Core/EDProducer.h"
12 #include "art/Framework/Core/ModuleMacros.h"
13 #include "art/Framework/Principal/Event.h"
14 #include "art/Framework/Principal/Handle.h"
15 #include "art/Framework/Services/Registry/ServiceHandle.h"
16 #include "art_root_io/TFileService.h"
17 #include "canvas/Persistency/Common/Ptr.h"
18 #include "cetlib/pow.h"
19 #include "fhiclcpp/ParameterSet.h"
20 #include "messagefacility/MessageLogger/MessageLogger.h"
21 
22 // LArSoft libraries
27 
28 #include "TGraph.h"
29 #include "TH2F.h"
30 #include "TMatrixD.h"
31 #include "TVectorD.h"
32 
33 namespace quad {
34 
35  // ---------------------------------------------------------------------------
36  struct Pt2D {
37  Pt2D(double _x, double _z, int _view, double _energy)
38  : x(_x), z(_z), view(_view), energy(_energy)
39  {}
40 
41  bool
42  operator<(const Pt2D& p) const
43  {
44  return z < p.z;
45  }
46 
47  double x, z;
48  int view;
49  double energy;
50  };
51 
52  // ---------------------------------------------------------------------------
53  struct Line2D {
54  Line2D(const Pt2D& a, const Pt2D& b)
55  : m((b.x - a.x) / (b.z - a.z))
56  , c(b.x - m * b.z)
57  , // w(a.energy * b.energy),
58  minz(std::min(a.z, b.z))
59  , maxz(std::max(a.z, b.z))
60  {
61  assert(a.z != b.z); // no vertical lines
62  }
63 
64  bool
65  operator<(const Line2D& l) const
66  {
67  return m < l.m;
68  }
69 
70  float m, c, /*w,*/ minz, maxz;
71  };
72 
73  // ---------------------------------------------------------------------------
74  class QuadVtx : public art::EDProducer {
75  public:
76  explicit QuadVtx(const fhicl::ParameterSet& pset);
77 
78  void beginJob() override;
79  void produce(art::Event& evt) override;
80 
81  private:
83  const std::vector<recob::Hit>& hits,
84  TVector3& vtx,
85  int evt) const;
86 
87  std::string fHitLabel;
88 
89  bool fSavePlots;
90 
92  };
93 
94  DEFINE_ART_MODULE(QuadVtx)
95 
96  // ---------------------------------------------------------------------------
97  QuadVtx::QuadVtx(const fhicl::ParameterSet& pset)
98  : EDProducer(pset)
99  , fHitLabel(pset.get<std::string>("HitLabel"))
100  , fSavePlots(pset.get<bool>("SavePlots"))
101  {
102  produces<std::vector<recob::Vertex>>();
103  }
104 
105  // ---------------------------------------------------------------------------
106  void
108  {
109  geom = art::ServiceHandle<const geo::Geometry>()->provider();
110  }
111 
112  // ---------------------------------------------------------------------------
113  // x = m*z+c. z1 and z2 are the two intercepts in case of returning true
114  bool
115  IntersectsCircle(float m, float c, float z0, float x0, float R, float& z1, float& z2)
116  {
117  // Change to the frame where (z0, x0) = (0, 0)
118  c += m * z0 - x0;
119 
120  // z^2 + (m*z+c)^2 = R^2
121  const float A = 1 + cet::square(m);
122  const float B = 2 * m * c;
123  const float C = cet::square(c) - cet::square(R);
124 
125  double desc = cet::square(B) - 4 * A * C;
126 
127  if (desc < 0) return false;
128 
129  desc = sqrt(desc);
130 
131  z1 = (-B - desc) / (2 * A);
132  z2 = (-B + desc) / (2 * A);
133 
134  // Back to the original frame
135  z1 += z0;
136  z2 += z0;
137 
138  return true;
139  }
140 
141  // ---------------------------------------------------------------------------
142  void
143  LinesFromPoints(const std::vector<Pt2D>& pts,
144  std::vector<Line2D>& lines,
145  float z0 = 0,
146  float x0 = 0,
147  float R = -1)
148  {
149  constexpr size_t kMaxLines = 10 * 1000 * 1000; // This is 150MB of lines...
150 
151  const size_t product = (pts.size() * (pts.size() - 1)) / 2;
152  const int stride = product / kMaxLines + 1;
153 
154  lines.reserve(std::min(product, kMaxLines));
155 
156  for (int offset = 0; offset < stride; ++offset) {
157  for (unsigned int i = 0; i < pts.size(); ++i) {
158  for (unsigned int j = i + offset + 1; j < pts.size(); j += stride) {
159  const Line2D l(pts[i], pts[j]);
160 
161  if (isinf(l.m) || isnan(l.m) || isinf(l.c) || isnan(l.c)) continue;
162 
163  if (R > 0) {
164  float z1, z2;
165  if (!IntersectsCircle(l.m, l.c, z0, x0, 2.5, z1, z2)) continue;
166  if (l.minz < z1 && l.minz < z2 && l.maxz > z1 && l.maxz > z2) continue;
167  }
168 
169  lines.push_back(std::move(l));
170  if (lines.size() == kMaxLines) goto end; // break out of 3 loops
171  }
172  }
173  }
174 
175  end:
176 
177  lines.shrink_to_fit();
178 
179  mf::LogInfo() << "Made " << lines.size() << " lines using stride " << stride
180  << " to fit under cap of " << kMaxLines << std::endl;
181 
182  // Lines are required to be sorted by gradient for a later optimization
183  std::sort(lines.begin(), lines.end());
184  }
185 
186  // ---------------------------------------------------------------------------
187  inline bool
188  CloseAngles(float ma, float mb)
189  {
190  const float cosCrit = cos(10 * M_PI / 180.);
191  const float dot = 1 + ma * mb; // (1, ma)*(1, mb)
192  return cet::square(dot) > (1 + cet::square(ma)) * (1 + cet::square(mb)) * cet::square(cosCrit);
193  }
194 
195  // ---------------------------------------------------------------------------
196  void
197  MapFromLines(const std::vector<Line2D>& lines, HeatMap& hm)
198  {
199  // This maximum is driven by runtime
200  constexpr size_t kMaxPts = 10 * 1000 * 1000;
201 
202  unsigned int j0 = 0;
203  unsigned int jmax = 0;
204 
205  long npts = 0;
206  for (unsigned int i = 0; i + 1 < lines.size(); ++i) {
207  const Line2D a = lines[i];
208 
209  j0 = std::max(j0, i + 1);
210  while (j0 < lines.size() && CloseAngles(a.m, lines[j0].m))
211  ++j0;
212  jmax = std::max(jmax, j0);
213  while (jmax < lines.size() && !CloseAngles(a.m, lines[jmax].m))
214  ++jmax;
215 
216  npts += jmax - j0;
217  }
218 
219  const size_t product = (lines.size() * (lines.size() - 1)) / 2;
220  const int stride = npts / kMaxPts + 1;
221 
222  mf::LogInfo() << "Combining lines to points with stride " << stride << std::endl;
223 
224  mf::LogInfo() << npts << " cf " << product << " ie " << double(npts) / product << std::endl;
225 
226  j0 = 0;
227  jmax = 0;
228 
229  for (unsigned int i = 0; i + 1 < lines.size(); ++i) {
230  const Line2D a = lines[i];
231 
232  j0 = std::max(j0, i + 1);
233  while (j0 < lines.size() && CloseAngles(a.m, lines[j0].m))
234  ++j0;
235  jmax = std::max(jmax, j0);
236  while (jmax < lines.size() && !CloseAngles(a.m, lines[jmax].m))
237  ++jmax;
238 
239  for (unsigned int j = j0; j < jmax; j += stride) {
240  const Line2D& b = lines[j];
241 
242  // x = mA * z + cA = mB * z + cB
243  const float z = (b.c - a.c) / (a.m - b.m);
244  const float x = a.m * z + a.c;
245 
246  // No solutions within a line
247  if ((z < a.minz || z > a.maxz) && (z < b.minz || z > b.maxz)) {
248  const int iz = hm.ZToBin(z);
249  const int ix = hm.XToBin(x);
250  if (iz >= 0 && iz < hm.Nz && ix >= 0 && ix < hm.Nx) { hm.map[iz * hm.Nx + ix] += stride; }
251  }
252  } // end for i
253  }
254  }
255 
256  // ---------------------------------------------------------------------------
257  // Assumes that all three maps have the same vertical stride
258  TVector3
259  FindPeak3D(const std::vector<HeatMap>& hs, const std::vector<TVector3>& dirs) noexcept
260  {
261  assert(hs.size() == 3);
262  assert(dirs.size() == 3);
263 
264  const int Nx = hs[0].Nx;
265 
266  TMatrixD M(2, 2);
267  M(0, 0) = dirs[0].Y();
268  M(0, 1) = dirs[0].Z();
269  M(1, 0) = dirs[1].Y();
270  M(1, 1) = dirs[1].Z();
271 
272  // Singular, and stupid setup of exceptions means we can't test any other way
273  if (M(0, 0) * M(1, 1) - M(1, 0) * M(0, 1) == 0) return TVector3(0, 0, 0);
274 
275  M.Invert();
276 
277  float bestscore = -1;
278  TVector3 bestr;
279 
280  // Accumulate some statistics up front that will enable us to optimize
281  std::vector<float> colMax[3];
282  for (int view = 0; view < 3; ++view) {
283  colMax[view].resize(hs[view].Nz);
284  for (int iz = 0; iz < hs[view].Nz; ++iz) {
285  colMax[view][iz] = *std::max_element(&hs[view].map[Nx * iz], &hs[view].map[Nx * (iz + 1)]);
286  }
287  }
288 
289  for (int iz = 0; iz < hs[0].Nz; ++iz) {
290  const float z = hs[0].ZBinCenter(iz);
291  const float bonus = 1; // works badly... exp((hs[0].maxz-z)/1000.);
292 
293  for (int iu = 0; iu < hs[1].Nz; ++iu) {
294  const float u = hs[1].ZBinCenter(iu);
295  // r.Dot(d0) = z && r.Dot(d1) = u
296  TVectorD p(2);
297  p(0) = z;
298  p(1) = u;
299  const TVectorD r = M * p;
300  const float v = r[0] * dirs[2].Y() + r[1] * dirs[2].Z();
301  const int iv = hs[2].ZToBin(v);
302  if (iv < 0 || iv >= hs[2].Nz) continue;
303  const double y = r(0);
304 
305  // Even if the maxes were all at the same x we couldn't beat the record
306  if (colMax[0][iz] + colMax[1][iu] + colMax[2][iv] < bestscore) continue;
307 
308  // Attempt to micro-optimize the dx loop below
309  const float* __restrict__ h0 = &hs[0].map[Nx * iz];
310  const float* __restrict__ h1 = &hs[1].map[Nx * iu];
311  const float* __restrict__ h2 = &hs[2].map[Nx * iv];
312 
313  int bestix = -1;
314  for (int ix = 1; ix < Nx - 1; ++ix) {
315  const float score = bonus * (h0[ix] + h1[ix] + h2[ix]);
316 
317  if (score > bestscore) {
318  bestscore = score;
319  bestix = ix;
320  }
321  } // end for dx
322 
323  if (bestix != -1) { bestr = TVector3(hs[0].XBinCenter(bestix), y, z); }
324  } // end for u
325  } // end for z
326 
327  return bestr;
328  }
329 
330  // ---------------------------------------------------------------------------
331  void
333  const std::vector<recob::Hit>& hits,
334  std::vector<std::vector<Pt2D>>& pts,
335  std::vector<TVector3>& dirs,
336  const geo::GeometryCore* geom)
337  {
338  pts.resize(3); // 3 views
339 
340  TVector3 dirZ(0, 0, 1);
341  TVector3 dirU, dirV;
342 
343  for (const recob::Hit& hit : hits) {
344  const geo::WireID wire = hit.WireID();
345 
346  const double xpos = detProp.ConvertTicksToX(hit.PeakTime(), wire);
347 
348  const TVector3 r0 = geom->WireEndPoints(wire).start();
349  const TVector3 r1 = geom->WireEndPoints(wire).end();
350 
351  const double energy = hit.Integral();
352 
353  if (geom->View(hit.Channel()) == geo::kZ) {
354  pts[0].emplace_back(xpos, r0.z(), 0, energy);
355  continue;
356  }
357 
358  // Compute the direction perpendicular to the wires
359  TVector3 perp = (r1 - r0).Unit();
360  perp = TVector3(0, -perp.z(), perp.y());
361  // We want to ultimately have a positive z component in "perp"
362  if (perp.z() < 0) perp *= -1;
363 
364  // TODO check we never get a 4th view a-la the bug we had in the 3D version
365 
366  // The "U" direction is the first one we see
367  if (dirU.Mag2() == 0) { dirU = perp; }
368  else if (dirV.Mag2() == 0 && fabs(dirU.Dot(perp)) < 0.99) {
369  // If we still need a "V" and this direction differs from "U"
370  dirV = perp;
371  }
372 
373  // Hits belong to whichever view their perpendicular vector aligns with
374  if (fabs(dirU.Dot(perp)) > 0.99) { pts[1].emplace_back(xpos, r0.Dot(dirU), 1, energy); }
375  else {
376  pts[2].emplace_back(xpos, r0.Dot(dirV), 2, energy);
377  }
378  } // end for hits
379 
380  dirs = {dirZ, dirU, dirV};
381 
382  std::default_random_engine gen;
383 
384  // In case we need to sub-sample they should be shuffled
385  for (int view = 0; view < 3; ++view) {
386  std::shuffle(pts[view].begin(), pts[view].end(), gen);
387  }
388  }
389 
390  // ---------------------------------------------------------------------------
391  bool
393  const std::vector<recob::Hit>& hits,
394  TVector3& vtx,
395  int evt) const
396  {
397  if (hits.empty()) return false;
398 
399  std::vector<std::vector<Pt2D>> pts;
400  std::vector<TVector3> dirs;
401 
402  GetPts2D(detProp, hits, pts, dirs, geom);
403 
404  double minx = +1e9;
405  double maxx = -1e9;
406  double minz[3] = {+1e9, +1e9, +1e9};
407  double maxz[3] = {-1e9, -1e9, -1e9};
408  for (int view = 0; view < 3; ++view) {
409  for (const Pt2D& p : pts[view]) {
410  minx = std::min(minx, p.x);
411  maxx = std::max(maxx, p.x);
412  minz[view] = std::min(minz[view], p.z);
413  maxz[view] = std::max(maxz[view], p.z);
414  }
415  }
416 
417  // Add some padding
418  for (int view = 0; view < 3; ++view) {
419  minz[view] -= 100;
420  maxz[view] += 100;
421  }
422  minx -= 20;
423  maxx += 20;
424 
425  // Don't allow the vertex further downstream in z (view 0) than 25% of the
426  // hits.
427  std::vector<float> zs;
428  zs.reserve(pts[0].size());
429  for (const Pt2D& p : pts[0])
430  zs.push_back(p.z);
431  auto mid = zs.begin() + zs.size() / 4;
432  if (mid != zs.end()) {
433  std::nth_element(zs.begin(), mid, zs.end());
434  maxz[0] = *mid;
435  }
436 
437  std::vector<HeatMap> hms;
438  hms.reserve(3);
439  for (int view = 0; view < 3; ++view) {
440  if (pts[view].empty()) return false;
441 
442  std::vector<Line2D> lines;
443  LinesFromPoints(pts[view], lines);
444 
445  if (lines.empty()) return false;
446 
447  // Approximately cm bins
448  hms.emplace_back(maxz[view] - minz[view], minz[view], maxz[view], maxx - minx, minx, maxx);
449  MapFromLines(lines, hms.back());
450  } // end for view
451 
452  vtx = FindPeak3D(hms, dirs);
453 
454  std::vector<HeatMap> hms_zoom;
455  hms_zoom.reserve(3);
456  for (int view = 0; view < 3; ++view) {
457  const double x0 = vtx.X();
458  const double z0 = vtx.Dot(dirs[view]);
459 
460  std::vector<Line2D> lines;
461  LinesFromPoints(pts[view], lines, z0, x0, 2.5);
462 
463  if (lines.empty()) return false; // How does this happen??
464 
465  // mm granularity
466  hms_zoom.emplace_back(50, z0 - 2.5, z0 + 2.5, 50, x0 - 2.5, x0 + 2.5);
467 
468  MapFromLines(lines, hms_zoom.back());
469  }
470 
471  vtx = FindPeak3D(hms_zoom, dirs);
472 
473  if (fSavePlots) {
474  art::TFileDirectory evt_dir =
475  art::ServiceHandle<art::TFileService>()->mkdir(TString::Format("evt%d", evt).Data());
476 
477  for (int view = 0; view < 3; ++view) {
478  art::TFileDirectory view_dir = evt_dir.mkdir(TString::Format("view%d", view).Data());
479 
480  TGraph* gpts = view_dir.makeAndRegister<TGraph>("hits", "");
481  for (const Pt2D& p : pts[view])
482  gpts->SetPoint(gpts->GetN(), p.z, p.x);
483 
484  view_dir.makeAndRegister<TH2F>("hmap", "", *hms[view].AsTH2());
485 
486  view_dir.makeAndRegister<TH2F>("hmap_zoom", "", *hms_zoom[view].AsTH2());
487 
488  const double x = vtx.X();
489  const double z = vtx.Dot(dirs[view]);
490  view_dir.makeAndRegister<TGraph>("vtx3d", "", 1, &z, &x);
491  } // end for view
492  } // end if saving plots
493 
494  return true;
495  }
496 
497  // ---------------------------------------------------------------------------
498  void
499  QuadVtx::produce(art::Event& evt)
500  {
501  auto vtxcol = std::make_unique<std::vector<recob::Vertex>>();
502 
503  art::Handle<std::vector<recob::Hit>> hits;
504  evt.getByLabel(fHitLabel, hits);
505 
506  auto const detProp =
507  art::ServiceHandle<detinfo::DetectorPropertiesService const>()->DataFor(evt);
508  TVector3 vtx;
509  if (FindVtx(detProp, *hits, vtx, evt.event())) {
510  vtxcol->emplace_back(
511  recob::Vertex::Point_t(vtx.X(), vtx.Y(), vtx.Z()), recob::Vertex::SMatrixSym33(), 0, 0);
512  }
513 
514  evt.put(std::move(vtxcol));
515  }
516 
517 } // end namespace quad
process_name opflash particleana ie ie ie z
void GetPts2D(const detinfo::DetectorPropertiesData &detProp, const std::vector< recob::Hit > &hits, std::vector< std::vector< Pt2D >> &pts, std::vector< TVector3 > &dirs, const geo::GeometryCore *geom)
int XToBin(double x) const
Definition: HeatMap.h:22
double std(const std::vector< short > &wf, const double ped_mean, size_t start, size_t nsample)
Definition: UtilFunc.cxx:42
BEGIN_PROLOG TPC Trig offset(g4 rise time) ProjectToHeight
Definition: CORSIKAGen.fcl:7
TVector3 FindPeak3D(const std::vector< HeatMap > &hs, const std::vector< TVector3 > &dirs) noexcept
constexpr auto dot(Vector const &a, Vector const &b)
Return cross product of two vectors.
process_name opflash particleana ie x
Declaration of signal hit object.
pdgs p
Definition: selectors.fcl:22
tracking::SMatrixSym33 SMatrixSym33
Definition: Vertex.h:40
BEGIN_PROLOG or score(default)}sbnd_crttrackmatchingalg_crID
int ZToBin(double z) const
Definition: HeatMap.h:21
std::size_t size(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:561
Planes which measure Z direction.
Definition: geo_types.h:132
process_name hit
Definition: cheaterreco.fcl:51
tuple m
now if test mode generate materials, CRT shell, world, gdml header else just generate CRT shell for u...
process_name gaushit a
auto vector(Vector const &v)
Returns a manipulator which will print the specified array.
Definition: DumpUtils.h:265
tracking::Point_t Point_t
Definition: Vertex.h:39
const int Nx
Definition: HeatMap.h:25
bool FindVtx(const detinfo::DetectorPropertiesData &detProp, const std::vector< recob::Hit > &hits, TVector3 &vtx, int evt) const
process_name opflash particleana ie ie y
Pt2D(double _x, double _z, int _view, double _energy)
void MapFromLines(const std::vector< Line2D > &lines, HeatMap &hm)
QuadVtx(const fhicl::ParameterSet &pset)
std::string fHitLabel
bool IntersectsCircle(float m, float c, float z0, float x0, float R, float &z1, float &z2)
auto end(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:585
bool operator<(const Pt2D &p) const
Description of geometry of one entire detector.
View_t View(geo::PlaneID const &pid) const
Returns the view (wire orientation) on the channels of specified TPC plane.
double ConvertTicksToX(double ticks, int p, int t, int c) const
auto begin(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:573
bool CloseAngles(float ma, float mb)
void beginJob() override
bool operator<(const Line2D &l) const
void LinesFromPoints(const std::vector< Pt2D > &pts, std::vector< Line2D > &lines, float z0=0, float x0=0, float R=-1)
void WireEndPoints(geo::WireID const &wireid, double *xyzStart, double *xyzEnd) const
Fills two arrays with the coordinates of the wire end points.
2D representation of charge deposited in the TDC/wire plane
Definition: Hit.h:48
TCEvent evt
Definition: DataStructs.cxx:8
const geo::GeometryCore * geom
float A
Definition: dedx.py:137
bool empty(FixedBins< T, C > const &) noexcept
Definition: FixedBins.h:555
esac echo uname r
art framework interface to geometry description
auto const detProp
void produce(art::Event &evt) override
Line2D(const Pt2D &a, const Pt2D &b)
std::vector< float > map
Definition: HeatMap.h:27