Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-12 08:07:09

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