Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-03 07:38:20

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     const LineParametrisation<PointType>& linePar,
0021     const LineParametrisation<PointType>& widthPar,
0022     const identifier_t& identifier, 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   m_hits[m_iHit] = identifier;
0077   m_iHit += 1;
0078   m_nHits += weight;
0079 
0080   // Check for duplicate layers
0081   if (m_iLayer != 0 && m_layers[m_iLayer - 1] == layer) {
0082     return;
0083   }
0084 
0085   if (m_iLayer == m_layers.size()) {
0086     m_layers.resize(m_layers.size() + m_assignBatch);
0087   }
0088 
0089   m_layers[m_iLayer] = layer;
0090   m_iLayer += 1;
0091   m_nLayers += weight;
0092 }
0093 
0094 template <class identifier_t>
0095 void Acts::HoughTransformUtils::HoughCell<identifier_t>::reset() {
0096   m_nHits = 0;
0097   m_nLayers = 0;
0098 
0099   m_iLayer = 0;
0100   m_iHit = 0;
0101 }
0102 
0103 template <class identifier_t>
0104 Acts::HoughTransformUtils::HoughPlane<identifier_t>::HoughPlane(
0105     const HoughPlaneConfig& cfg)
0106     : m_cfg(cfg),
0107       m_houghHist(Axis(0, m_cfg.nBinsX, m_cfg.nBinsX),
0108                   Axis(0, m_cfg.nBinsY, m_cfg.nBinsY)) {
0109   // instantiate our histogram.
0110   // m_houghHist = HoughHist(Axis(0, m_cfg.nBinsX, m_cfg.nBinsX), Axis(0,
0111   // m_cfg.nBinsY, m_cfg.nBinsY));
0112 }
0113 template <class identifier_t>
0114 void Acts::HoughTransformUtils::HoughPlane<identifier_t>::fillBin(
0115     std::size_t binX, std::size_t binY, const identifier_t& identifier,
0116     unsigned layer, double w) {
0117   // mark that this bin was filled with non trivial content
0118 
0119   m_touchedBins.insert(globalBin({binX, binY}));
0120 
0121   // add content to the cell
0122   m_houghHist.atLocalBins({binX, binY}).fill(identifier, layer, w);
0123   // and update our cached maxima
0124   YieldType layers = nLayers(binX, binY);
0125   YieldType hits = nHits(binX, binY);
0126   if (layers > m_maxLayers) {
0127     m_maxLayers = layers;
0128     m_maxLocLayers = {binX, binY};
0129   }
0130   if (hits > m_maxHits) {
0131     m_maxHits = hits;
0132     m_maxLocHits = {binX, binY};
0133   }
0134 }
0135 
0136 template <class identifier_t>
0137 void Acts::HoughTransformUtils::HoughPlane<identifier_t>::reset() {
0138   // reset all bins that were previously filled
0139   // avoid calling this on empty cells to save time
0140   for (auto bin : getNonEmptyBins()) {
0141     m_houghHist.at(bin).reset();
0142   }
0143   // don't forget to reset our cached maxima
0144   m_maxHits = 0.;
0145   m_maxLayers = 0.;
0146   // and reset the list of nontrivial bins
0147   m_iBin = 0;
0148   m_touchedBins.clear();
0149 }
0150 template <class identifier_t>
0151 void Acts::HoughTransformUtils::HoughPlane<identifier_t>::checkIndices(
0152     std::size_t xBin, std::size_t yBin) const {
0153   if (xBin >= nBinsX()) {
0154     throw std::out_of_range("When accessing HoughPlane, X index " +
0155                             std::to_string(xBin) +
0156                             " is >= " + std::to_string(nBinsX()));
0157   }
0158   if (yBin >= nBinsY()) {
0159     throw std::out_of_range("When accessing HoughPlane, Y index " +
0160                             std::to_string(yBin) +
0161                             " is >= " + std::to_string(nBinsY()));
0162   }
0163 }
0164 
0165 template <class identifier_t>
0166 Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<identifier_t>::
0167     LayerGuidedCombinatoric(const LayerGuidedCombinatoricConfig& cfg)
0168     : m_cfg(cfg) {}
0169 
0170 template <class identifier_t>
0171 std::vector<typename Acts::HoughTransformUtils::PeakFinders::
0172                 LayerGuidedCombinatoric<identifier_t>::Maximum>
0173 Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<
0174     identifier_t>::findPeaks(const HoughPlane<identifier_t>& plane) const {
0175   // book the vector for the maxima
0176   std::vector<PeakFinders::LayerGuidedCombinatoric<identifier_t>::Maximum>
0177       maxima;
0178   // loop over the non empty bins
0179   for (auto nbin : plane.getNonEmptyBins()) {
0180     // and look for the ones that represent a maximum
0181 
0182     std::array<std::size_t, 2> xy = plane.axisBins(nbin);
0183 
0184     if (passThreshold(plane, xy[0], xy[1])) {
0185       // write out a maximum
0186       Maximum max;
0187       auto hitIds = plane.hitIds(xy[0], xy[1]);
0188       max.hitIdentifiers.insert(std::make_move_iterator(hitIds.begin()),
0189                                 std::make_move_iterator(hitIds.end()));
0190       maxima.push_back(max);
0191     }
0192   }
0193   return maxima;
0194 }
0195 
0196 template <class identifier_t>
0197 bool Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<
0198     identifier_t>::passThreshold(const HoughPlane<identifier_t>& plane,
0199                                  std::size_t xBin, std::size_t yBin) const {
0200   // Check if we have sufficient layers for a maximum
0201   if (plane.nLayers(xBin, yBin) < m_cfg.threshold) {
0202     return false;
0203   }
0204   // if no local maximum is required, this is enough to classify as a max
0205   if (m_cfg.localMaxWindowSize == 0) {
0206     return true;
0207   }
0208   // otherwise, check for local maxima by looking at the surrounding cells
0209 
0210   /// loop over neighbours
0211   for (int dXbin = -m_cfg.localMaxWindowSize; dXbin <= m_cfg.localMaxWindowSize;
0212        dXbin++) {
0213     for (int dYbin = -m_cfg.localMaxWindowSize;
0214          dYbin <= m_cfg.localMaxWindowSize; dYbin++) {
0215       // exclude the candidate itself
0216       if (dYbin == 0 && dXbin == 0) {
0217         continue;
0218       }
0219       // out of bounds -> no need to test this bin
0220       if (static_cast<int>(xBin) + dXbin < 0 ||
0221           static_cast<int>(yBin) + dYbin < 0) {
0222         continue;
0223       }
0224       if (xBin + dXbin >= plane.nBinsX() || yBin + dYbin >= plane.nBinsY()) {
0225         continue;
0226       }
0227       // if a neighbour with more layers exist, this is not a minimum
0228       if (plane.nLayers(xBin + dXbin, yBin + dYbin) >
0229           plane.nLayers(xBin, yBin)) {
0230         return false;
0231       }
0232       // if the neighbour has fewer hits, we can move to the next neighbour
0233       if (plane.nLayers(xBin + dXbin, yBin + dYbin) <
0234           plane.nLayers(xBin, yBin)) {
0235         continue;
0236       }
0237 
0238       // we can still be in a plateau. In this case, resolve by counting hits
0239 
0240       // if the other bin with the same hit count has seen more clusters (
0241       // double hits in one layer), keep that one and reject the current
0242       if (plane.nHits(xBin + dXbin, yBin + dYbin) > plane.nHits(xBin, yBin)) {
0243         return false;
0244       }
0245       // and if we have completely identical hit and layer count, prefer bins in
0246       // the bottom (left) direction
0247       if (plane.nHits(xBin + dXbin, yBin + dYbin) == plane.nHits(xBin, yBin) &&
0248           (dYbin < 0 || (dYbin == 0 && dXbin < 0))) {
0249         return false;
0250       }
0251     }
0252   }
0253   return true;
0254 }
0255 template <class identifier_t>
0256 Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0257     identifier_t>::IslandsAroundMax(const IslandsAroundMaxConfig& cfg)
0258     : m_cfg(cfg) {}
0259 
0260 template <class identifier_t>
0261 std::vector<typename Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0262     identifier_t>::Maximum>
0263 Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0264     identifier_t>::findPeaks(const HoughPlane<identifier_t>& plane,
0265                              const HoughAxisRanges& ranges) {
0266   // check the global maximum hit count in the plane
0267   YieldType max = plane.maxHits();
0268   // and obtain the fraction of the max that is our cutoff for island formation
0269   YieldType min = std::max(m_cfg.threshold, m_cfg.fractionCutoff * max);
0270   // book a list for the candidates and the maxima
0271   const std::unordered_set<std::size_t>& nonEmptyBins{plane.getNonEmptyBins()};
0272   std::vector<std::size_t> candidates;
0273   candidates.reserve(nonEmptyBins.size());
0274   std::vector<Maximum> maxima;
0275   maxima.reserve(nonEmptyBins.size());
0276   // keep track of the yields in each non empty cell
0277   std::unordered_map<std::size_t, YieldType> yieldMap;
0278 
0279   // now loop over all non empty bins
0280   for (const std::size_t nbin : nonEmptyBins) {
0281     //  we only consider cells above threshold from now on.
0282     //  each is a potential candidate to seed or join an island
0283     //  We also add each to our yield map
0284 
0285     if (plane.nHits(nbin) > min) {
0286       candidates.push_back(nbin);
0287       yieldMap[nbin] = plane.nHits(nbin);
0288     }
0289   }
0290   // sort the candidate cells descending in content
0291   std::ranges::sort(candidates, std::greater{}, [&yieldMap](std::size_t c) {
0292     return std::make_tuple(yieldMap[c], c);
0293   });
0294 
0295   // now we build islands from the candidate cells, starting with the most
0296   // populated one
0297   std::vector<std::size_t> toExplore;
0298   std::vector<std::array<std::size_t, 2>> solution;
0299 
0300   // loop over candidate cells
0301   for (auto& cand : candidates) {
0302     // check the content again (if used in a previous island, this will now
0303     // report empty)
0304     if (yieldMap[cand] < min) {
0305       continue;
0306     }
0307     // CALL AXIS BINS HERE
0308     std::array<std::size_t, 2> xy = plane.axisBins(cand);
0309     // translate to parameter space for overlap veto
0310     CoordType xCand =
0311         binCenter(ranges.xMin, ranges.xMax, plane.nBinsX(), xy[0]);
0312     CoordType yCand =
0313         binCenter(ranges.yMin, ranges.yMax, plane.nBinsY(), xy[1]);
0314     // check if we are too close to a previously found maximum
0315     bool goodSpacing = true;
0316     for (auto& found : maxima) {
0317       if (std::abs(xCand - found.x) < m_cfg.minSpacingBetweenPeaks.first ||
0318           std::abs(yCand - found.y) < m_cfg.minSpacingBetweenPeaks.second) {
0319         goodSpacing = false;
0320         break;
0321       }
0322     }
0323     if (!goodSpacing) {
0324       continue;
0325     }
0326     // now we can extend the candidate into an island
0327     toExplore.clear();
0328     solution.clear();
0329     // initially, pass the candidate into the list of "neighbours" to explore
0330     // around an empty island
0331     toExplore.push_back(cand);
0332     // and incrementally add neighbours, filling the solution vector
0333     while (!toExplore.empty()) {
0334       extendMaximum(plane, solution, toExplore, min, yieldMap);
0335     }
0336     // nothing found? Next candidate!
0337     if (solution.empty()) {
0338       continue;
0339     }
0340     // We found an island
0341     Maximum maximum;
0342     CoordType max_x = 0;
0343     CoordType max_y = 0;
0344     CoordType pos_den = 0;
0345     CoordType ymax = -std::numeric_limits<CoordType>::max();
0346     CoordType ymin = std::numeric_limits<CoordType>::max();
0347     CoordType xmax = -std::numeric_limits<CoordType>::max();
0348     CoordType xmin = std::numeric_limits<CoordType>::max();
0349     // loop over cells in the island and get the weighted mean position.
0350     // Also collect all hit identifiers in the island and the maximum
0351     // extent (within the count threshold!) of the island
0352     std::vector<identifier_t> allHits;
0353     for (auto& [xBin, yBin] : solution) {
0354       auto hidIds = plane.hitIds(xBin, yBin);
0355       allHits.insert(allHits.end(), std::make_move_iterator(hidIds.begin()),
0356                      std::make_move_iterator(hidIds.end()));
0357       CoordType xHit =
0358           binCenter(ranges.xMin, ranges.xMax, plane.nBinsX(), xBin);
0359       CoordType yHit =
0360           binCenter(ranges.yMin, ranges.yMax, plane.nBinsY(), yBin);
0361       YieldType nHits = plane.nHits(xBin, yBin);
0362       max_x += xHit * nHits;
0363       max_y += yHit * nHits;
0364       pos_den += nHits;
0365       if (xHit > xmax) {
0366         xmax = xHit;
0367       }
0368       if (yHit > ymax) {
0369         ymax = yHit;
0370       }
0371       if (xHit < xmin) {
0372         xmin = xHit;
0373       }
0374       if (yHit < ymin) {
0375         ymin = yHit;
0376       }
0377     }
0378     maximum.hitIdentifiers.insert(std::make_move_iterator(allHits.begin()),
0379                                   std::make_move_iterator(allHits.end()));
0380     // calculate mean position
0381     maximum.x = max_x / pos_den;
0382     maximum.y = max_y / pos_den;
0383     // calculate width as symmetrised band
0384     maximum.wx = 0.5 * (xmax - xmin);
0385     maximum.wy = 0.5 * (ymax - ymin);
0386     maxima.push_back(maximum);
0387   }
0388   return maxima;
0389 }
0390 
0391 template <class identifier_t>
0392 void Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<identifier_t>::
0393     extendMaximum(
0394         const Acts::HoughTransformUtils::HoughPlane<identifier_t>& houghPlane,
0395         std::vector<std::array<std::size_t, 2>>& inMaximum,
0396         std::vector<std::size_t>& toExplore, YieldType threshold,
0397         std::unordered_map<std::size_t, YieldType>& yieldMap) {
0398   // in this call, we explore the last element of the toExplore list.
0399   // Fetch it and pop it from the vector.
0400 
0401   const std::size_t candidate = toExplore.back();
0402   YieldType& yield{yieldMap[candidate]};
0403   toExplore.pop_back();
0404   // check if we are above threshold. Don't add this cell to the island if not
0405   if (yield < threshold) {
0406     return;
0407   }
0408   // This candidate is above threshold and should go on the island!
0409   auto nextCand = houghPlane.axisBins(candidate);
0410   // add it to the cell list for the island
0411   inMaximum.push_back(nextCand);
0412   // and "veto" the hit for further use via the yield map
0413   yieldMap[candidate] = -1.0f;
0414 
0415   // now we have to collect the non empty neighbours of this cell and check them
0416   // as well
0417   for (auto step : m_stepDirections) {
0418     std::array<unsigned long, 2> newCandxy = {nextCand[0] + step.first,
0419                                               nextCand[1] + step.second};
0420     // check if we are moving out of the bounds
0421     if (newCandxy[0] >= houghPlane.nBinsX() ||
0422         newCandxy[1] >= houghPlane.nBinsY()) {
0423       continue;
0424     }
0425     std::size_t newCand = houghPlane.globalBin({newCandxy[0], newCandxy[1]});
0426 
0427     // if the cell is above threshold, add it to our list of neighbours to
0428     // explore
0429     if (yieldMap[newCand] > threshold) {
0430       toExplore.push_back(newCand);
0431     }
0432   }
0433 }
0434 
0435 namespace {
0436 
0437 template <typename identifier_t>
0438 bool passWindow(
0439     const typename Acts::HoughTransformUtils::HoughPlane<identifier_t>::Index
0440         index,
0441     const Acts::HoughTransformUtils::HoughPlane<identifier_t>& plane,
0442     const Acts::HoughTransformUtils::PeakFinders::SlidingWindowConfig& config) {
0443   using YieldType = Acts::HoughTransformUtils::YieldType;
0444   auto [xmax, ymax] = index;
0445   const YieldType max = plane.nHits(xmax, ymax);
0446   // window loopdefininf
0447   // this loop needs to be smarter to take care of wrapping
0448   for (std::size_t x = xmax - config.xWindowSize;
0449        x <= xmax + config.xWindowSize; ++x) {
0450     for (std::size_t y = ymax - config.yWindowSize;
0451          y <= ymax + config.yWindowSize; ++y) {
0452       const float xdist = static_cast<float>(x) - static_cast<float>(xmax);
0453       const float ydist = static_cast<float>(y) - static_cast<float>(ymax);
0454       const bool above = ydist + 0.1 > xdist;  // upper right corner
0455       const YieldType numOfHits = plane.nHits(x, y);
0456 
0457       if (above && numOfHits > max) {
0458         return false;
0459       }
0460 
0461       if (!above && numOfHits >= max) {
0462         return false;
0463       }
0464     }
0465   }
0466   return true;
0467 }
0468 
0469 template <typename identifier_t>
0470 typename Acts::HoughTransformUtils::HoughPlane<identifier_t>::Index
0471 slidingWindowRecenter(
0472     const typename Acts::HoughTransformUtils::HoughPlane<identifier_t>::Index
0473         index,
0474     const Acts::HoughTransformUtils::HoughPlane<identifier_t>& plane,
0475     const Acts::HoughTransformUtils::PeakFinders::SlidingWindowConfig& config) {
0476   using YieldType = Acts::HoughTransformUtils::YieldType;
0477   YieldType xw = 0;
0478   YieldType yw = 0;
0479   YieldType tot = 0;
0480   auto [xcenter, ycenter] = index;
0481   YieldType maxValue = plane.nHits(xcenter, ycenter);
0482   for (std::size_t x = xcenter - config.xRecenterSize;
0483        x <= xcenter + config.xRecenterSize; ++x) {
0484     for (std::size_t y = ycenter - config.yRecenterSize;
0485          y <= ycenter + config.yRecenterSize; ++y) {
0486       const YieldType numOfHits = plane.nHits(x, y);
0487       if (numOfHits >= maxValue) {
0488         xw += x * numOfHits;
0489         yw += y * numOfHits;
0490         tot += numOfHits;
0491       }
0492     }
0493   }
0494   return {static_cast<std::size_t>(xw / tot),
0495           static_cast<std::size_t>(yw / tot)};
0496 }
0497 
0498 }  // namespace
0499 
0500 template <typename identifier_t>
0501 std::vector<typename Acts::HoughTransformUtils::HoughPlane<identifier_t>::Index>
0502 Acts::HoughTransformUtils::PeakFinders::slidingWindowPeaks(
0503     const Acts::HoughTransformUtils::HoughPlane<identifier_t>& plane,
0504     const Acts::HoughTransformUtils::PeakFinders::SlidingWindowConfig& config) {
0505   using IndexType = Acts::HoughTransformUtils::HoughPlane<identifier_t>::Index;
0506   std::vector<IndexType> output;
0507 
0508   for (std::size_t x = config.xWindowSize;
0509        x < plane.nBinsX() - config.xWindowSize; ++x) {
0510     for (std::size_t y = config.yWindowSize;
0511          y < plane.nBinsY() - config.yWindowSize; ++y) {
0512       if (plane.nHits(x, y) >= config.threshold) {
0513         const IndexType index({x, y});
0514         if (passWindow(index, plane, config)) {
0515           output.push_back(config.recenter
0516                                ? slidingWindowRecenter(index, plane, config)
0517                                : index);
0518         }
0519       }
0520     }
0521   }
0522   return output;
0523 }
0524 
0525 template <typename identifier_t, typename pixel_value_t>
0526 std::vector<pixel_value_t>
0527 Acts::HoughTransformUtils::PeakFinders::hitsCountImage(
0528     const Acts::HoughTransformUtils::HoughPlane<identifier_t>& plane,
0529     typename Acts::HoughTransformUtils::HoughPlane<identifier_t>::Index index,
0530     std::size_t xSize, std::size_t ySize,
0531     const std::function<pixel_value_t(const HoughPlane<identifier_t>&, int,
0532                                       int)>& summaryFunction) {
0533   std::vector<pixel_value_t> output;
0534   output.reserve((xSize) * (ySize));
0535 
0536   auto [xcenter, ycenter] = index;
0537 
0538   auto isInside = [&plane](int x, int y) -> bool {
0539     return 0 <= x && x < static_cast<int>(plane.nBinsX()) && 0 <= y &&
0540            y < static_cast<int>(plane.nBinsY());
0541   };
0542 
0543   for (std::size_t x = 0; x < xSize; ++x) {
0544     for (std::size_t y = 0; y < ySize; ++y) {
0545       int xPlaneCoord = x + xcenter - xSize / 2;
0546       int yPlaneCoord = y + ycenter - ySize / 2;
0547       output.push_back(isInside(xPlaneCoord, yPlaneCoord)
0548                            ? summaryFunction(plane, xPlaneCoord, yPlaneCoord)
0549                            : pixel_value_t{});
0550     }
0551   }
0552   return output;
0553 }