Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-16 08:13:01

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Seeding/HoughTransformUtils.hpp"
0012 
0013 #include <algorithm>
0014 #include <tuple>
0015 
0016 template <class identifier_t>
0017 template <class PointType>
0018 void Acts::HoughTransformUtils::HoughPlane<identifier_t>::fill(
0019     const PointType& measurement, const HoughAxisRanges& axisRanges,
0020     LineParametrisation<PointType> linePar,
0021     LineParametrisation<PointType> widthPar, const identifier_t& identifier,
0022     unsigned layer, YieldType weight) {
0023   // loop over all bins in the first coordinate to populate the line
0024   for (std::size_t xBin = 0; xBin < m_cfg.nBinsX; xBin++) {
0025     // get the x-coordinate for the given bin
0026     auto x = binCenter(axisRanges.xMin, axisRanges.xMax, m_cfg.nBinsX, xBin);
0027     // now evaluate the line equation provided by the user
0028     CoordType y = linePar(x, measurement);
0029     CoordType dy = widthPar(x, measurement);
0030     // translate the y-coordinate range to a bin range
0031     int yBinDown =
0032         binIndex(axisRanges.yMin, axisRanges.yMax, m_cfg.nBinsY, y - dy);
0033     int yBinUp =
0034         binIndex(axisRanges.yMin, axisRanges.yMax, m_cfg.nBinsY, y + dy);
0035     // now we can loop over the bin range to fill the corresponding cells
0036     for (int yBin = yBinDown; yBin <= yBinUp; ++yBin) {
0037       // skip 'out of bounds' cases
0038       if (yBin >= static_cast<int>(m_cfg.nBinsY) || yBin < 0) {
0039         continue;
0040       }
0041       fillBin(xBin, yBin, identifier, layer, weight);
0042     }
0043   }
0044 }
0045 
0046 template <class identifier_t>
0047 std::span<const identifier_t, std::dynamic_extent>
0048 Acts::HoughTransformUtils::HoughCell<identifier_t>::getHits() const {
0049   std::span<const identifier_t, std::dynamic_extent> hits(m_hits.begin(),
0050                                                           m_iHit);
0051   return hits;
0052 }
0053 
0054 template <class identifier_t>
0055 std::span<const unsigned, std::dynamic_extent>
0056 Acts::HoughTransformUtils::HoughCell<identifier_t>::getLayers() const {
0057   std::span<const unsigned, std::dynamic_extent> layers(m_layers.begin(),
0058                                                         m_iLayer);
0059 
0060   return layers;
0061 }
0062 
0063 template <class identifier_t>
0064 void Acts::HoughTransformUtils::HoughCell<identifier_t>::fill(
0065     const identifier_t& identifier, unsigned layer, YieldType weight) {
0066   // add the hit to the list of hits in the cell
0067 
0068   if (m_iHit != 0 && m_hits[m_iHit - 1] == identifier) {
0069     return;
0070   }
0071 
0072   if (m_iHit == m_hits.size()) {
0073     m_hits.resize(m_hits.size() + m_assignBatch);
0074   }
0075 
0076   if (m_iLayer == m_layers.size()) {
0077     m_layers.resize(m_layers.size() + m_assignBatch);
0078   }
0079 
0080   m_hits[m_iHit] = identifier;
0081   m_layers[m_iLayer] = layer;
0082 
0083   m_iHit += 1;
0084   m_iLayer += 1;
0085 
0086   m_nHits += weight;
0087   m_nLayers += weight;
0088 }
0089 
0090 template <class identifier_t>
0091 void Acts::HoughTransformUtils::HoughCell<identifier_t>::reset() {
0092   m_nHits = 0;
0093   m_nLayers = 0;
0094 
0095   m_iLayer = 0;
0096   m_iHit = 0;
0097 }
0098 
0099 template <class identifier_t>
0100 Acts::HoughTransformUtils::HoughPlane<identifier_t>::HoughPlane(
0101     const HoughPlaneConfig& cfg)
0102     : m_cfg(cfg),
0103       m_houghHist(Axis(0, m_cfg.nBinsX, m_cfg.nBinsX),
0104                   Axis(0, m_cfg.nBinsY, m_cfg.nBinsY)) {
0105   // instantiate our histogram.
0106   // m_houghHist = HoughHist(Axis(0, m_cfg.nBinsX, m_cfg.nBinsX), Axis(0,
0107   // m_cfg.nBinsY, m_cfg.nBinsY));
0108 }
0109 template <class identifier_t>
0110 void Acts::HoughTransformUtils::HoughPlane<identifier_t>::fillBin(
0111     std::size_t binX, std::size_t binY, const identifier_t& identifier,
0112     unsigned layer, double w) {
0113   // mark that this bin was filled with non trivial content
0114 
0115   m_touchedBins.insert(globalBin({binX, binY}));
0116 
0117   // add content to the cell
0118   m_houghHist.atLocalBins({binX, binY}).fill(identifier, layer, w);
0119   // and update our cached maxima
0120   YieldType layers = nLayers(binX, binY);
0121   YieldType hits = nHits(binX, binY);
0122   if (layers > m_maxLayers) {
0123     m_maxLayers = layers;
0124     m_maxLocLayers = {binX, binY};
0125   }
0126   if (hits > m_maxHits) {
0127     m_maxHits = hits;
0128     m_maxLocHits = {binX, binY};
0129   }
0130 }
0131 
0132 template <class identifier_t>
0133 void Acts::HoughTransformUtils::HoughPlane<identifier_t>::reset() {
0134   // reset all bins that were previously filled
0135   // avoid calling this on empty cells to save time
0136   for (auto bin : getNonEmptyBins()) {
0137     m_houghHist.at(bin).reset();
0138   }
0139   // don't forget to reset our cached maxima
0140   m_maxHits = 0.;
0141   m_maxLayers = 0.;
0142   // and reset the list of nontrivial bins
0143   m_iBin = 0;
0144   m_touchedBins.clear();
0145 }
0146 
0147 template <class identifier_t>
0148 Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<identifier_t>::
0149     LayerGuidedCombinatoric(const LayerGuidedCombinatoricConfig& cfg)
0150     : m_cfg(cfg) {}
0151 
0152 template <class identifier_t>
0153 std::vector<typename Acts::HoughTransformUtils::PeakFinders::
0154                 LayerGuidedCombinatoric<identifier_t>::Maximum>
0155 Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<
0156     identifier_t>::findPeaks(const HoughPlane<identifier_t>& plane) const {
0157   // book the vector for the maxima
0158   std::vector<PeakFinders::LayerGuidedCombinatoric<identifier_t>::Maximum>
0159       maxima;
0160   // loop over the non empty bins
0161   for (auto nbin : plane.getNonEmptyBins()) {
0162     // and look for the ones that represent a maximum
0163 
0164     std::array<std::size_t, 2> xy = plane.axisBins(nbin);
0165 
0166     if (passThreshold(plane, xy[0], xy[1])) {
0167       // write out a maximum
0168       Maximum max;
0169       auto hitIds = plane.hitIds(xy[0], xy[1]);
0170       max.hitIdentifiers.insert(std::make_move_iterator(hitIds.begin()),
0171                                 std::make_move_iterator(hitIds.end()));
0172       maxima.push_back(max);
0173     }
0174   }
0175   return maxima;
0176 }
0177 
0178 template <class identifier_t>
0179 bool Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<
0180     identifier_t>::passThreshold(const HoughPlane<identifier_t>& plane,
0181                                  std::size_t xBin, std::size_t yBin) const {
0182   // Check if we have sufficient layers for a maximum
0183   if (plane.nLayers(xBin, yBin) < m_cfg.threshold) {
0184     return false;
0185   }
0186   // if no local maximum is required, this is enough to classify as a max
0187   if (m_cfg.localMaxWindowSize == 0) {
0188     return true;
0189   }
0190   // otherwise, check for local maxima by looking at the surrounding cells
0191 
0192   /// loop over neighbours
0193   for (int dXbin = -m_cfg.localMaxWindowSize; dXbin <= m_cfg.localMaxWindowSize;
0194        dXbin++) {
0195     for (int dYbin = -m_cfg.localMaxWindowSize;
0196          dYbin <= m_cfg.localMaxWindowSize; dYbin++) {
0197       // exclude the candidate itself
0198       if (dYbin == 0 && dXbin == 0) {
0199         continue;
0200       }
0201       // out of bounds -> no need to test this bin
0202       if (static_cast<int>(xBin) + dXbin < 0 ||
0203           static_cast<int>(yBin) + dYbin < 0) {
0204         continue;
0205       }
0206       if (xBin + dXbin >= plane.nBinsX() || yBin + dYbin >= plane.nBinsY()) {
0207         continue;
0208       }
0209       // if a neighbour with more layers exist, this is not a minimum
0210       if (plane.nLayers(xBin + dXbin, yBin + dYbin) >
0211           plane.nLayers(xBin, yBin)) {
0212         return false;
0213       }
0214       // if the neighbour has fewer hits, we can move to the next neighbour
0215       if (plane.nLayers(xBin + dXbin, yBin + dYbin) <
0216           plane.nLayers(xBin, yBin)) {
0217         continue;
0218       }
0219 
0220       // we can still be in a plateau. In this case, resolve by counting hits
0221 
0222       // if the other bin with the same hit count has seen more clusters (
0223       // double hits in one layer), keep that one and reject the current
0224       if (plane.nHits(xBin + dXbin, yBin + dYbin) > plane.nHits(xBin, yBin)) {
0225         return false;
0226       }
0227       // and if we have completely identical hit and layer count, prefer bins in
0228       // the bottom (left) direction
0229       if (plane.nHits(xBin + dXbin, yBin + dYbin) == plane.nHits(xBin, yBin) &&
0230           (dYbin < 0 || (dYbin == 0 && dXbin < 0))) {
0231         return false;
0232       }
0233     }
0234   }
0235   return true;
0236 }
0237 template <class identifier_t>
0238 Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0239     identifier_t>::IslandsAroundMax(const IslandsAroundMaxConfig& cfg)
0240     : m_cfg(cfg) {}
0241 
0242 template <class identifier_t>
0243 std::vector<typename Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0244     identifier_t>::Maximum>
0245 Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0246     identifier_t>::findPeaks(const HoughPlane<identifier_t>& plane,
0247                              const HoughAxisRanges& ranges) {
0248   // check the global maximum hit count in the plane
0249   YieldType max = plane.maxHits();
0250   // and obtain the fraction of the max that is our cutoff for island formation
0251   YieldType min = std::max(m_cfg.threshold, m_cfg.fractionCutoff * max);
0252   // book a list for the candidates and the maxima
0253   const std::unordered_set<std::size_t>& nonEmptyBins{plane.getNonEmptyBins()};
0254   std::vector<std::size_t> candidates;
0255   candidates.reserve(nonEmptyBins.size());
0256   std::vector<Maximum> maxima;
0257   maxima.reserve(nonEmptyBins.size());
0258   // keep track of the yields in each non empty cell
0259   std::unordered_map<std::size_t, YieldType> yieldMap;
0260 
0261   // now loop over all non empty bins
0262   for (const std::size_t nbin : nonEmptyBins) {
0263     //  we only consider cells above threshold from now on.
0264     //  each is a potential candidate to seed or join an island
0265     //  We also add each to our yield map
0266 
0267     if (plane.nHits(nbin) > min) {
0268       candidates.push_back(nbin);
0269       yieldMap[nbin] = plane.nHits(nbin);
0270     }
0271   }
0272   // sort the candidate cells descending in content
0273   std::ranges::sort(candidates, std::greater{}, [&yieldMap](std::size_t c) {
0274     return std::make_tuple(yieldMap[c], c);
0275   });
0276 
0277   // now we build islands from the candidate cells, starting with the most
0278   // populated one
0279   std::vector<std::size_t> toExplore;
0280   std::vector<std::array<std::size_t, 2>> solution;
0281 
0282   // loop over candidate cells
0283   for (auto& cand : candidates) {
0284     // check the content again (if used in a previous island, this will now
0285     // report empty)
0286     if (yieldMap[cand] < min) {
0287       continue;
0288     }
0289     // CALL AXIS BINS HERE
0290     std::array<std::size_t, 2> xy = plane.axisBins(cand);
0291     // translate to parameter space for overlap veto
0292     CoordType xCand =
0293         binCenter(ranges.xMin, ranges.xMax, plane.nBinsX(), xy[0]);
0294     CoordType yCand =
0295         binCenter(ranges.yMin, ranges.yMax, plane.nBinsY(), xy[1]);
0296     // check if we are too close to a previously found maximum
0297     bool goodSpacing = true;
0298     for (auto& found : maxima) {
0299       if (std::abs(xCand - found.x) < m_cfg.minSpacingBetweenPeaks.first ||
0300           std::abs(yCand - found.y) < m_cfg.minSpacingBetweenPeaks.second) {
0301         goodSpacing = false;
0302         break;
0303       }
0304     }
0305     if (!goodSpacing) {
0306       continue;
0307     }
0308     // now we can extend the candidate into an island
0309     toExplore.clear();
0310     solution.clear();
0311     // initially, pass the candidate into the list of "neighbours" to explore
0312     // around an empty island
0313     toExplore.push_back(cand);
0314     // and incrementally add neighbours, filling the solution vector
0315     while (!toExplore.empty()) {
0316       extendMaximum(plane, solution, toExplore, min, yieldMap);
0317     }
0318     // nothing found? Next candidate!
0319     if (solution.empty()) {
0320       continue;
0321     }
0322     // We found an island
0323     Maximum maximum;
0324     CoordType max_x = 0;
0325     CoordType max_y = 0;
0326     CoordType pos_den = 0;
0327     CoordType ymax = -std::numeric_limits<CoordType>::max();
0328     CoordType ymin = std::numeric_limits<CoordType>::max();
0329     CoordType xmax = -std::numeric_limits<CoordType>::max();
0330     CoordType xmin = std::numeric_limits<CoordType>::max();
0331     // loop over cells in the island and get the weighted mean position.
0332     // Also collect all hit identifiers in the island and the maximum
0333     // extent (within the count threshold!) of the island
0334     std::vector<identifier_t> allHits;
0335     for (auto& [xBin, yBin] : solution) {
0336       auto hidIds = plane.hitIds(xBin, yBin);
0337       allHits.insert(allHits.end(), std::make_move_iterator(hidIds.begin()),
0338                      std::make_move_iterator(hidIds.end()));
0339       CoordType xHit =
0340           binCenter(ranges.xMin, ranges.xMax, plane.nBinsX(), xBin);
0341       CoordType yHit =
0342           binCenter(ranges.yMin, ranges.yMax, plane.nBinsY(), yBin);
0343       YieldType nHits = plane.nHits(xBin, yBin);
0344       max_x += xHit * nHits;
0345       max_y += yHit * nHits;
0346       pos_den += nHits;
0347       if (xHit > xmax) {
0348         xmax = xHit;
0349       }
0350       if (yHit > ymax) {
0351         ymax = yHit;
0352       }
0353       if (xHit < xmin) {
0354         xmin = xHit;
0355       }
0356       if (yHit < ymin) {
0357         ymin = yHit;
0358       }
0359     }
0360     maximum.hitIdentifiers.insert(std::make_move_iterator(allHits.begin()),
0361                                   std::make_move_iterator(allHits.end()));
0362     // calculate mean position
0363     maximum.x = max_x / pos_den;
0364     maximum.y = max_y / pos_den;
0365     // calculate width as symmetrised band
0366     maximum.wx = 0.5 * (xmax - xmin);
0367     maximum.wy = 0.5 * (ymax - ymin);
0368     maxima.push_back(maximum);
0369   }
0370   return maxima;
0371 }
0372 
0373 template <class identifier_t>
0374 void Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<identifier_t>::
0375     extendMaximum(
0376         const Acts::HoughTransformUtils::HoughPlane<identifier_t>& houghPlane,
0377         std::vector<std::array<std::size_t, 2>>& inMaximum,
0378         std::vector<std::size_t>& toExplore, YieldType threshold,
0379         std::unordered_map<std::size_t, YieldType>& yieldMap) {
0380   // in this call, we explore the last element of the toExplore list.
0381   // Fetch it and pop it from the vector.
0382 
0383   const std::size_t candidate = toExplore.back();
0384   YieldType& yield{yieldMap[candidate]};
0385   toExplore.pop_back();
0386   // check if we are above threshold. Don't add this cell to the island if not
0387   if (yield < threshold) {
0388     return;
0389   }
0390   // This candidate is above threshold and should go on the island!
0391   auto nextCand = houghPlane.axisBins(candidate);
0392   // add it to the cell list for the island
0393   inMaximum.push_back(nextCand);
0394   // and "veto" the hit for further use via the yield map
0395   yieldMap[candidate] = -1.0f;
0396 
0397   // now we have to collect the non empty neighbours of this cell and check them
0398   // as well
0399   for (auto step : m_stepDirections) {
0400     std::array<unsigned long, 2> newCandxy = {nextCand[0] + step.first,
0401                                               nextCand[1] + step.second};
0402     // check if we are moving out of the bounds
0403     if (newCandxy[0] >= houghPlane.nBinsX() ||
0404         newCandxy[1] >= houghPlane.nBinsY()) {
0405       continue;
0406     }
0407     std::size_t newCand = houghPlane.globalBin({newCandxy[0], newCandxy[1]});
0408 
0409     // if the cell is above threshold, add it to our list of neighbours to
0410     // explore
0411     if (yieldMap[newCand] > threshold) {
0412       toExplore.push_back(newCand);
0413     }
0414   }
0415 }