Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-05 08:11:11

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       max.hitIdentifiers = plane.hitIds(xy[0], xy[1]);
0170       maxima.push_back(max);
0171     }
0172   }
0173   return maxima;
0174 }
0175 
0176 template <class identifier_t>
0177 bool Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<
0178     identifier_t>::passThreshold(const HoughPlane<identifier_t>& plane,
0179                                  std::size_t xBin, std::size_t yBin) const {
0180   // Check if we have sufficient layers for a maximum
0181   if (plane.nLayers(xBin, yBin) < m_cfg.threshold) {
0182     return false;
0183   }
0184   // if no local maximum is required, this is enough to classify as a max
0185   if (m_cfg.localMaxWindowSize == 0) {
0186     return true;
0187   }
0188   // otherwise, check for local maxima by looking at the surrounding cells
0189 
0190   /// loop over neighbours
0191   for (int dXbin = -m_cfg.localMaxWindowSize; dXbin <= m_cfg.localMaxWindowSize;
0192        dXbin++) {
0193     for (int dYbin = -m_cfg.localMaxWindowSize;
0194          dYbin <= m_cfg.localMaxWindowSize; dYbin++) {
0195       // exclude the candidate itself
0196       if (dYbin == 0 && dXbin == 0) {
0197         continue;
0198       }
0199       // out of bounds -> no need to test this bin
0200       if (static_cast<int>(xBin) + dXbin < 0 ||
0201           static_cast<int>(yBin) + dYbin < 0) {
0202         continue;
0203       }
0204       if (xBin + dXbin >= plane.nBinsX() || yBin + dYbin >= plane.nBinsY()) {
0205         continue;
0206       }
0207       // if a neighbour with more layers exist, this is not a minimum
0208       if (plane.nLayers(xBin + dXbin, yBin + dYbin) >
0209           plane.nLayers(xBin, yBin)) {
0210         return false;
0211       }
0212       // if the neighbour has fewer hits, we can move to the next neighbour
0213       if (plane.nLayers(xBin + dXbin, yBin + dYbin) <
0214           plane.nLayers(xBin, yBin)) {
0215         continue;
0216       }
0217 
0218       // we can still be in a plateau. In this case, resolve by counting hits
0219 
0220       // if the other bin with the same hit count has seen more clusters (
0221       // double hits in one layer), keep that one and reject the current
0222       if (plane.nHits(xBin + dXbin, yBin + dYbin) > plane.nHits(xBin, yBin)) {
0223         return false;
0224       }
0225       // and if we have completely identical hit and layer count, prefer bins in
0226       // the bottom (left) direction
0227       if (plane.nHits(xBin + dXbin, yBin + dYbin) == plane.nHits(xBin, yBin) &&
0228           (dYbin < 0 || (dYbin == 0 && dXbin < 0))) {
0229         return false;
0230       }
0231     }
0232   }
0233   return true;
0234 }
0235 template <class identifier_t>
0236 Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0237     identifier_t>::IslandsAroundMax(const IslandsAroundMaxConfig& cfg)
0238     : m_cfg(cfg) {}
0239 
0240 template <class identifier_t>
0241 std::vector<typename Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0242     identifier_t>::Maximum>
0243 Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0244     identifier_t>::findPeaks(const HoughPlane<identifier_t>& plane,
0245                              const HoughAxisRanges& ranges) {
0246   // check the global maximum hit count in the plane
0247   YieldType max = plane.maxHits();
0248   // and obtain the fraction of the max that is our cutoff for island formation
0249   YieldType min = std::max(m_cfg.threshold, m_cfg.fractionCutoff * max);
0250   // book a list for the candidates and the maxima
0251   const std::unordered_set<std::size_t>& nonEmptyBins{plane.getNonEmptyBins()};
0252   std::vector<std::size_t> candidates;
0253   candidates.reserve(nonEmptyBins.size());
0254   std::vector<Maximum> maxima;
0255   maxima.reserve(nonEmptyBins.size());
0256   // keep track of the yields in each non empty cell
0257   std::unordered_map<std::size_t, YieldType> yieldMap;
0258 
0259   // now loop over all non empty bins
0260   for (const std::size_t nbin : nonEmptyBins) {
0261     //  we only consider cells above threshold from now on.
0262     //  each is a potential candidate to seed or join an island
0263     //  We also add each to our yield map
0264 
0265     if (plane.nHits(nbin) > min) {
0266       candidates.push_back(nbin);
0267       yieldMap[nbin] = plane.nHits(nbin);
0268     }
0269   }
0270   // sort the candidate cells descending in content
0271   std::ranges::sort(candidates, std::greater{}, [&yieldMap](std::size_t c) {
0272     return std::make_tuple(yieldMap[c], c);
0273   });
0274 
0275   // now we build islands from the candidate cells, starting with the most
0276   // populated one
0277   std::vector<std::size_t> toExplore;
0278   std::vector<std::array<std::size_t, 2>> solution;
0279 
0280   // loop over candidate cells
0281   for (auto& cand : candidates) {
0282     // check the content again (if used in a previous island, this will now
0283     // report empty)
0284     if (yieldMap[cand] < min) {
0285       continue;
0286     }
0287     // CALL AXIS BINS HERE
0288     std::array<std::size_t, 2> xy = plane.axisBins(cand);
0289     // translate to parameter space for overlap veto
0290     CoordType xCand =
0291         binCenter(ranges.xMin, ranges.xMax, plane.nBinsX(), xy[0]);
0292     CoordType yCand =
0293         binCenter(ranges.yMin, ranges.yMax, plane.nBinsY(), xy[1]);
0294     // check if we are too close to a previously found maximum
0295     bool goodSpacing = true;
0296     for (auto& found : maxima) {
0297       if (std::abs(xCand - found.x) < m_cfg.minSpacingBetweenPeaks.first ||
0298           std::abs(yCand - found.y) < m_cfg.minSpacingBetweenPeaks.second) {
0299         goodSpacing = false;
0300         break;
0301       }
0302     }
0303     if (!goodSpacing) {
0304       continue;
0305     }
0306     // now we can extend the candidate into an island
0307     toExplore.clear();
0308     solution.clear();
0309     // initially, pass the candidate into the list of "neighbours" to explore
0310     // around an empty island
0311     toExplore.push_back(cand);
0312     // and incrementally add neighbours, filling the solution vector
0313     while (!toExplore.empty()) {
0314       extendMaximum(plane, solution, toExplore, min, yieldMap);
0315     }
0316     // nothing found? Next candidate!
0317     if (solution.empty()) {
0318       continue;
0319     }
0320     // We found an island
0321     Maximum maximum;
0322     CoordType max_x = 0;
0323     CoordType max_y = 0;
0324     CoordType pos_den = 0;
0325     CoordType ymax = -std::numeric_limits<CoordType>::max();
0326     CoordType ymin = std::numeric_limits<CoordType>::max();
0327     CoordType xmax = -std::numeric_limits<CoordType>::max();
0328     CoordType xmin = std::numeric_limits<CoordType>::max();
0329     // loop over cells in the island and get the weighted mean position.
0330     // Also collect all hit identifiers in the island and the maximum
0331     // extent (within the count threshold!) of the island
0332     for (auto& [xBin, yBin] : solution) {
0333       auto hidIds = plane.hitIds(xBin, yBin);
0334       maximum.hitIdentifiers.insert(std::make_move_iterator(hidIds.begin()),
0335                                     std::make_move_iterator(hidIds.end()));
0336       CoordType xHit =
0337           binCenter(ranges.xMin, ranges.xMax, plane.nBinsX(), xBin);
0338       CoordType yHit =
0339           binCenter(ranges.yMin, ranges.yMax, plane.nBinsY(), yBin);
0340       YieldType nHits = plane.nHits(xBin, yBin);
0341       max_x += xHit * nHits;
0342       max_y += yHit * nHits;
0343       pos_den += nHits;
0344       if (xHit > xmax) {
0345         xmax = xHit;
0346       }
0347       if (yHit > ymax) {
0348         ymax = yHit;
0349       }
0350       if (xHit < xmin) {
0351         xmin = xHit;
0352       }
0353       if (yHit < ymin) {
0354         ymin = yHit;
0355       }
0356     }
0357     // calculate mean position
0358     maximum.x = max_x / pos_den;
0359     maximum.y = max_y / pos_den;
0360     // calculate width as symmetrised band
0361     maximum.wx = 0.5 * (xmax - xmin);
0362     maximum.wy = 0.5 * (ymax - ymin);
0363     maxima.push_back(maximum);
0364   }
0365   return maxima;
0366 }
0367 
0368 template <class identifier_t>
0369 void Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<identifier_t>::
0370     extendMaximum(
0371         const Acts::HoughTransformUtils::HoughPlane<identifier_t>& houghPlane,
0372         std::vector<std::array<std::size_t, 2>>& inMaximum,
0373         std::vector<std::size_t>& toExplore, YieldType threshold,
0374         std::unordered_map<std::size_t, YieldType>& yieldMap) {
0375   // in this call, we explore the last element of the toExplore list.
0376   // Fetch it and pop it from the vector.
0377 
0378   const std::size_t candidate = toExplore.back();
0379   YieldType& yield{yieldMap[candidate]};
0380   toExplore.pop_back();
0381   // check if we are above threshold. Don't add this cell to the island if not
0382   if (yield < threshold) {
0383     return;
0384   }
0385   // This candidate is above threshold and should go on the island!
0386   auto nextCand = houghPlane.axisBins(candidate);
0387   // add it to the cell list for the island
0388   inMaximum.push_back(nextCand);
0389   // and "veto" the hit for further use via the yield map
0390   yieldMap[candidate] = -1.0f;
0391 
0392   // now we have to collect the non empty neighbours of this cell and check them
0393   // as well
0394   for (auto step : m_stepDirections) {
0395     std::array<unsigned long, 2> newCandxy = {nextCand[0] + step.first,
0396                                               nextCand[1] + step.second};
0397     // check if we are moving out of the bounds
0398     if (newCandxy[0] >= houghPlane.nBinsX() ||
0399         newCandxy[1] >= houghPlane.nBinsY()) {
0400       continue;
0401     }
0402     std::size_t newCand = houghPlane.globalBin({newCandxy[0], newCandxy[1]});
0403 
0404     // if the cell is above threshold, add it to our list of neighbours to
0405     // explore
0406     if (yieldMap[newCand] > threshold) {
0407       toExplore.push_back(newCand);
0408     }
0409   }
0410 }