Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
HoughTransformSeeder.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file HoughTransformSeeder.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2020 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
10 
27 
28 #include <algorithm>
29 #include <cmath>
30 #include <iterator>
31 #include <ostream>
32 #include <stdexcept>
33 #include <variant>
34 
35 static inline int quant(double min, double max, unsigned nSteps, double val);
36 static inline double unquant(double min, double max, unsigned nSteps, int step);
37 template <typename T>
38 static inline std::string to_string(std::vector<T> v);
39 
42  : ActsExamples::IAlgorithm("HoughTransformSeeder", lvl),
43  m_cfg(std::move(cfg)),
44  m_logger(Acts::getDefaultLogger("HoughTransformSeeder", lvl)) {
45  // require spacepoints or input measurements (or both), but at least one kind
46  // of input
47  bool foundInput = false;
48  for (const auto& spName : m_cfg.inputSpacePoints) {
49  if (!(spName.empty())) {
50  foundInput = true;
51  }
52 
53  auto& handle = m_inputSpacePoints.emplace_back(
55  this,
56  "InputSpacePoints#" + std::to_string(m_inputSpacePoints.size())));
57  handle->initialize(spName);
58  }
59  if (!(m_cfg.inputMeasurements.empty())) {
60  foundInput = true;
61  }
62 
63  if (!foundInput) {
64  throw std::invalid_argument(
65  "HoughTransformSeeder: Missing some kind of input (measurements of "
66  "spacepoints)");
67  }
68 
69  if (m_cfg.outputProtoTracks.empty()) {
70  throw std::invalid_argument(
71  "HoughTransformSeeder: Missing hough tracks output collection");
72  }
73  if (m_cfg.outputSeeds.empty()) {
74  throw std::invalid_argument(
75  "HoughTransformSeeder: Missing hough track seeds output collection");
76  }
77 
78  if (m_cfg.inputSourceLinks.empty()) {
79  throw std::invalid_argument(
80  "HoughTransformSeeder: Missing source link input collection");
81  }
82 
86 
87  if (not m_cfg.trackingGeometry) {
88  throw std::invalid_argument(
89  "HoughTransformSeeder: Missing tracking geometry");
90  }
91 
92  if (m_cfg.geometrySelection.empty()) {
93  throw std::invalid_argument(
94  "HoughTransformSeeder: Missing geometry selection");
95  }
96  // ensure geometry selection contains only valid inputs
97  for (const auto& geoId : m_cfg.geometrySelection) {
98  if ((geoId.approach() != 0u) or (geoId.boundary() != 0u) or
99  (geoId.sensitive() != 0u)) {
100  throw std::invalid_argument(
101  "HoughTransformSeeder: Invalid geometry selection: only volume and "
102  "layer are allowed to be set");
103  }
104  }
105  // remove geometry selection duplicates
106  //
107  // the geometry selections must be mutually exclusive, i.e. if we have a
108  // selection that contains both a volume and a layer within that same volume,
109  // we would create the space points for the layer twice.
110  auto isDuplicate = [](Acts::GeometryIdentifier ref,
112  // code assumes ref < cmp and that only volume and layer can be non-zero
113  // root node always contains everything
114  if (ref.volume() == 0) {
115  return true;
116  }
117  // unequal volumes always means separate hierarchies
118  if (ref.volume() != cmp.volume()) {
119  return false;
120  }
121  // within the same volume hierarchy only consider layers
122  return (ref.layer() == cmp.layer());
123  };
124  auto geoSelBeg = m_cfg.geometrySelection.begin();
125  auto geoSelEnd = m_cfg.geometrySelection.end();
126  // sort geometry selection so the unique filtering works
127  std::sort(geoSelBeg, geoSelEnd);
128  auto geoSelLastUnique = std::unique(geoSelBeg, geoSelEnd, isDuplicate);
129  if (geoSelLastUnique != geoSelEnd) {
130  ACTS_WARNING("Removed " << std::distance(geoSelLastUnique, geoSelEnd)
131  << " geometry selection duplicates");
132  m_cfg.geometrySelection.erase(geoSelLastUnique, geoSelEnd);
133  }
134  ACTS_INFO("Hough geometry selection:");
135  for (const auto& geoId : m_cfg.geometrySelection) {
136  ACTS_INFO(" " << geoId);
137  }
138 
139  // Fill convenience variables
142  for (unsigned i = 0; i <= m_cfg.houghHistSize_x; i++) {
143  m_bins_x.push_back(
145  }
146  for (unsigned i = 0; i <= m_cfg.houghHistSize_y; i++) {
147  m_bins_y.push_back(
149  }
150 
157 }
158 
160  const AlgorithmContext& ctx) const {
161  // clear our Hough measurements out from the previous iteration, if at all
162  houghMeasurementStructs.clear();
163 
164  // add SPs to the inputs
165  addSpacePoints(ctx);
166 
167  // add ACTS measurements
168  addMeasurements(ctx);
169 
170  static thread_local ProtoTrackContainer protoTracks;
171  protoTracks.clear();
172 
173  // loop over our subregions and run the Hough Transform on each
174  for (int subregion : m_cfg.subRegions) {
175  ACTS_DEBUG("Processing subregion " << subregion);
176  ActsExamples::HoughHist m_houghHist = createHoughHist(subregion);
177 
178  for (unsigned y = 0; y < m_cfg.houghHistSize_y; y++) {
179  for (unsigned x = 0; x < m_cfg.houghHistSize_x; x++) {
180  if (passThreshold(m_houghHist, x, y)) {
181  /* now we need to unpack the hits; there should be multiple track
182  candidates if we have multiple hits in a given layer So the first
183  thing is to unpack the indices (which is what we need) by layer */
184 
185  std::vector<std::vector<std::vector<Index>>> hitIndicesAll(
186  m_cfg.nLayers); // [layer,vector<Index]
187  std::vector<size_t> nHitsPerLayer(m_cfg.nLayers);
188  for (auto measurementIndex : m_houghHist(y, x).second) {
189  HoughMeasurementStruct* meas =
190  houghMeasurementStructs[measurementIndex].get();
191  hitIndicesAll[meas->layer].push_back(meas->indices);
192  nHitsPerLayer[meas->layer]++;
193  }
194  std::vector<std::vector<int>> combs = getComboIndices(nHitsPerLayer);
195 
196  for (auto [icomb, hit_indices] :
197  Acts::enumerate(combs)) { // loop over all the combination
198 
199  ProtoTrack protoTrack;
200  for (unsigned layer = 0; layer < m_cfg.nLayers; layer++) {
201  if (hit_indices[layer] >= 0) {
202  for (auto index : hitIndicesAll[layer][hit_indices[layer]]) {
203  protoTrack.push_back(index);
204  }
205  }
206  }
207  protoTracks.push_back(protoTrack);
208  }
209  }
210  }
211  }
212  }
213  ACTS_DEBUG("Created " << protoTracks.size() << " proto track");
214 
215  m_outputProtoTracks(ctx, ProtoTrackContainer{protoTracks});
216  // clear the vector
217  houghMeasurementStructs.clear();
219 }
220 
223  int subregion) const {
224  ActsExamples::HoughHist houghHist(m_cfg.houghHistSize_y,
225  m_cfg.houghHistSize_x);
226 
227  for (unsigned index = 0; index < houghMeasurementStructs.size(); index++) {
229  if (meas->layer != layer) {
230  continue;
231  }
232  if (!(m_cfg.sliceTester(meas->z, meas->layer, subregion)).value()) {
233  continue;
234  }
235 
236  // This scans over y (pT) because that is more efficient in memory
237  for (unsigned y_ = 0; y_ < m_cfg.houghHistSize_y; y_++) {
238  unsigned y_bin_min = y_;
239  unsigned y_bin_max = (y_ + 1);
240 
241  // Find the min/max x bins
242  auto xBins =
243  yToXBins(y_bin_min, y_bin_max, meas->radius, meas->phi, meas->layer);
244  // Update the houghHist
245  for (unsigned y = y_bin_min; y < y_bin_max; y++) {
246  for (unsigned x = xBins.first; x < xBins.second; x++) {
247  houghHist(y, x).first++;
248  houghHist(y, x).second.insert(index);
249  }
250  }
251  }
252  }
253 
254  return houghHist;
255 }
256 
258  int subregion) const {
259  ActsExamples::HoughHist houghHist(m_cfg.houghHistSize_y,
260  m_cfg.houghHistSize_x);
261 
262  for (unsigned i = 0; i < m_cfg.nLayers; i++) {
263  HoughHist layerHoughHist = createLayerHoughHist(i, subregion);
264  for (unsigned x = 0; x < m_cfg.houghHistSize_x; ++x) {
265  for (unsigned y = 0; y < m_cfg.houghHistSize_y; ++y) {
266  if (layerHoughHist(y, x).first > 0) {
267  houghHist(y, x).first++;
268  houghHist(y, x).second.insert(layerHoughHist(y, x).second.begin(),
269  layerHoughHist(y, x).second.end());
270  }
271  }
272  }
273  }
274 
275  return houghHist;
276 }
277 
279  HoughHist const& houghHist, unsigned x, unsigned y) const {
280  // Pass window threshold
281  unsigned width = m_cfg.threshold.size() / 2;
282  if (x < width || (houghHist.size(1) - x) < width) {
283  return false;
284  }
285  for (unsigned i = 0; i < m_cfg.threshold.size(); i++) {
286  if (houghHist(y, x - width + i).first < m_cfg.threshold[i]) {
287  return false;
288  }
289  }
290 
291  // Pass local-maximum check, if used
292  if (m_cfg.localMaxWindowSize != 0) {
293  for (int j = -m_cfg.localMaxWindowSize; j <= m_cfg.localMaxWindowSize;
294  j++) {
295  for (int i = -m_cfg.localMaxWindowSize; i <= m_cfg.localMaxWindowSize;
296  i++) {
297  if (i == 0 && j == 0) {
298  continue;
299  }
300  if (y + j < houghHist.size(0) && x + i < houghHist.size(1)) {
301  if (houghHist(y + j, x + i).first > houghHist(y, x).first) {
302  return false;
303  }
304  if (houghHist(y + j, x + i).first == houghHist(y, x).first) {
305  if (houghHist(y + j, x + i).second.size() >
306  houghHist(y, x).second.size()) {
307  return false;
308  }
309  if (houghHist(y + j, x + i).second.size() ==
310  houghHist(y, x).second.size() &&
311  j <= 0 && i <= 0) {
312  return false; // favor bottom-left (low phi, low neg q/pt)
313  }
314  }
315  }
316  }
317  }
318  }
319 
320  return true;
321 }
322 
324 // Helpers
325 
326 // Quantizes val, given a range [min, max) split into nSteps. Returns the bin
327 // below.
328 static inline int quant(double min, double max, unsigned nSteps, double val) {
329  return static_cast<int>((val - min) / (max - min) * nSteps);
330 }
331 
332 // Returns the lower bound of the bin specified by step
333 static inline double unquant(double min, double max, unsigned nSteps,
334  int step) {
335  return min + (max - min) * step / nSteps;
336 }
337 
338 template <typename T>
339 static inline std::string to_string(std::vector<T> v) {
340  std::ostringstream oss;
341  oss << "[";
342  if (!v.empty()) {
343  std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(oss, ", "));
344  oss << v.back();
345  }
346  oss << "]";
347  return oss.str();
348 }
349 
351  double phi) const {
352  double d0 = 0; // d0 correction TO DO allow for this
353  double x =
354  asin(r * ActsExamples::HoughTransformSeeder::m_cfg.kA * y - d0 / r) + phi;
355 
356  if (m_cfg.fieldCorrector.connected()) {
357  x += (m_cfg.fieldCorrector(0, y, r)).value();
358  }
359 
360  return x;
361 }
362 
363 // Find the min/max x bins of the hit's line, in each y bin. Max is exclusive.
364 // Note this assumes yToX is monotonic. Returns {0, 0} if hit lies out of
365 // bounds.
366 std::pair<unsigned, unsigned> ActsExamples::HoughTransformSeeder::yToXBins(
367  size_t yBin_min, size_t yBin_max, double r, double phi,
368  unsigned layer) const {
369  double x_min = yToX(m_bins_y[yBin_min], r, phi);
370  double x_max = yToX(m_bins_y[yBin_max], r, phi);
371  if (x_min > x_max) {
372  std::swap(x_min, x_max);
373  }
374  if (x_max < m_cfg.xMin || x_min > m_cfg.xMax) {
375  return {0, 0}; // out of bounds
376  }
377 
378  // Get bins
379  int x_bin_min = quant(m_cfg.xMin, m_cfg.xMax, m_cfg.houghHistSize_x, x_min);
380  int x_bin_max = quant(m_cfg.xMin, m_cfg.xMax, m_cfg.houghHistSize_x, x_max) +
381  1; // exclusive
382 
383  // Extend bins
384  unsigned extend = getExtension(yBin_min, layer);
385  x_bin_min -= extend;
386  x_bin_max += extend;
387 
388  // Clamp bins
389  if (x_bin_min < 0) {
390  x_bin_min = 0;
391  }
392  if (x_bin_max > static_cast<int>(m_cfg.houghHistSize_x)) {
393  x_bin_max = m_cfg.houghHistSize_x;
394  }
395 
396  return {x_bin_min, x_bin_max};
397 }
398 
399 // We allow variable extension based on the size of m_hitExtend_x. See comments
400 // below.
402  unsigned y, unsigned layer) const {
403  if (m_cfg.hitExtend_x.size() == m_cfg.nLayers) {
404  return m_cfg.hitExtend_x[layer];
405  }
406 
407  if (m_cfg.hitExtend_x.size() == m_cfg.nLayers * 2) {
408  // different extension for low pt vs high pt, split in half but irrespective
409  // of sign first nLayers entries of m_hitExtend_x is for low pt half, rest
410  // are for high pt half
411  if (y < m_cfg.houghHistSize_y / 4 || y > 3 * m_cfg.houghHistSize_y / 4) {
412  return m_cfg.hitExtend_x[layer];
413  }
414 
415  return m_cfg.hitExtend_x[m_cfg.nLayers + layer];
416  }
417  return 0;
418 }
419 
435 std::vector<std::vector<int>>
437  std::vector<size_t>& sizes) const {
438  size_t nCombs = 1;
439  std::vector<size_t> nCombs_prior(sizes.size());
440  std::vector<int> temp(sizes.size(), 0);
441 
442  for (size_t i = 0; i < sizes.size(); i++) {
443  if (sizes[i] > 0) {
444  nCombs_prior[i] = nCombs;
445  nCombs *= sizes[i];
446  } else {
447  temp[i] = -1;
448  }
449  }
450 
451  std::vector<std::vector<int>> combos(nCombs, temp);
452 
453  for (size_t icomb = 0; icomb < nCombs; icomb++) {
454  size_t index = icomb;
455  for (size_t isize = sizes.size() - 1; isize < sizes.size(); isize--) {
456  if (sizes[isize] == 0) {
457  continue;
458  }
459  combos[icomb][isize] = static_cast<int>(index / nCombs_prior[isize]);
460  index = index % nCombs_prior[isize];
461  }
462  }
463 
464  return combos;
465 }
466 
468  const AlgorithmContext& ctx) const {
469  // construct the combined input container of space point pointers from all
470  // configured input sources.
471  for (const auto& isp : m_inputSpacePoints) {
472  const auto& spContainer = (*isp)(ctx);
473  ACTS_DEBUG("Inserting " << spContainer.size() << " space points from "
474  << isp->key());
475  for (auto& sp : spContainer) {
476  double r = std::hypot(sp.x(), sp.y());
477  double z = sp.z();
478  float phi = std::atan2(sp.y(), sp.x());
479  ResultUnsigned hitlayer = m_cfg.layerIDFinder(r).value();
480  if (!(hitlayer.ok())) {
481  continue;
482  }
483  std::vector<Index> indices;
484  for (const auto& slink : sp.sourceLinks()) {
485  const auto& islink = slink.get<IndexSourceLink>();
486  indices.push_back(islink.index());
487  }
488 
489  auto meas =
490  std::shared_ptr<HoughMeasurementStruct>(new HoughMeasurementStruct(
491  hitlayer.value(), phi, r, z, indices, HoughHitType::SP));
492  houghMeasurementStructs.push_back(meas);
493  }
494  }
495 }
496 
498  const AlgorithmContext& ctx) const {
499  const auto& measurements = m_inputMeasurements(ctx);
500  const auto& sourceLinks = m_inputSourceLinks(ctx);
501 
502  ACTS_DEBUG("Inserting " << measurements.size() << " space points from "
503  << m_cfg.inputMeasurements);
504 
505  for (Acts::GeometryIdentifier geoId : m_cfg.geometrySelection) {
506  // select volume/layer depending on what is set in the geometry id
507  auto range = selectLowestNonZeroGeometryObject(sourceLinks, geoId);
508  // groupByModule only works with geometry containers, not with an
509  // arbitrary range. do the equivalent grouping manually
510  auto groupedByModule = makeGroupBy(range, detail::GeometryIdGetter());
511 
512  for (auto [moduleGeoId, moduleSourceLinks] : groupedByModule) {
513  // find corresponding surface
514  const Acts::Surface* surface =
515  m_cfg.trackingGeometry->findSurface(moduleGeoId);
516  if (surface == nullptr) {
517  ACTS_ERROR("Could not find surface " << moduleGeoId);
518  return;
519  }
520 
521  for (auto& sourceLink : moduleSourceLinks) {
522  // extract a local position/covariance independent of the concrete
523  // measurement content. since we do not know if and where the local
524  // parameters are contained in the measurement parameters vector, they
525  // are transformed to the bound space where we do know their location.
526  // if the local parameters are not measured, this results in a
527  // zero location, which is a reasonable default fall-back.
528  auto [localPos, localCov] = std::visit(
529  [](const auto& meas) {
530  auto expander = meas.expander();
531  Acts::BoundVector par = expander * meas.parameters();
533  expander * meas.covariance() * expander.transpose();
534  // extract local position
536  // extract local position covariance.
537  Acts::SquareMatrix2 lcov =
538  cov.block<2, 2>(Acts::eBoundLoc0, Acts::eBoundLoc0);
539  return std::make_pair(lpar, lcov);
540  },
541  measurements[sourceLink.index()]);
542 
543  // transform local position to global coordinates
544  Acts::Vector3 globalFakeMom(1, 1, 1);
545  Acts::Vector3 globalPos =
546  surface->localToGlobal(ctx.geoContext, localPos, globalFakeMom);
547  double r = std::hypot(globalPos[Acts::ePos0], globalPos[Acts::ePos1]);
548  double phi = std::atan2(globalPos[Acts::ePos1], globalPos[Acts::ePos0]);
549  double z = globalPos[Acts::ePos2];
550  ResultUnsigned hitlayer = m_cfg.layerIDFinder(r);
551  if (hitlayer.ok()) {
552  std::vector<Index> index;
553  index.push_back(sourceLink.index());
554  auto meas = std::shared_ptr<HoughMeasurementStruct>(
555  new HoughMeasurementStruct(hitlayer.value(), phi, r, z, index,
557  houghMeasurementStructs.push_back(meas);
558  }
559  }
560  }
561  }
562 }