Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /include/Acts/Seeding/HoughTransformUtils.ipp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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 void Acts::HoughTransformUtils::HoughCell<identifier_t>::fill(
0043     const identifier_t& identifier, unsigned layer, YieldType weight) {
0044   // add the hit to the list of hits in the cell
0045   if (m_hits.insert(identifier).second) {
0046     // if new, increment the weighted hit counter
0047     m_nHits += weight;
0048   }
0049   // and to the same for layer counts
0050   if (m_layers.insert(layer).second) {
0051     m_nLayers += weight;
0052   }
0053 }
0054 template <class identifier_t>
0055 void Acts::HoughTransformUtils::HoughCell<identifier_t>::reset() {
0056   // avoid expensive clear calls on empty cells
0057   if (m_nLayers > 0) {
0058     m_layers.clear();
0059   }
0060   if (m_nHits > 0) {
0061     m_hits.clear();
0062   }
0063   m_nHits = 0;
0064   m_nLayers = 0;
0065 }
0066 
0067 template <class identifier_t>
0068 Acts::HoughTransformUtils::HoughPlane<identifier_t>::HoughPlane(
0069     const HoughPlaneConfig& cfg)
0070     : m_cfg(cfg) {
0071   // instantiate our histogram.
0072   m_houghHist = HoughHist(m_cfg.nBinsX, m_cfg.nBinsY);
0073 }
0074 template <class identifier_t>
0075 void Acts::HoughTransformUtils::HoughPlane<identifier_t>::fillBin(
0076     std::size_t binX, std::size_t binY, const identifier_t& identifier,
0077     unsigned layer, double w) {
0078   // mark that this bin was filled with non trivial content
0079   m_touchedBins.insert({binX, binY});
0080   // add content to the cell
0081   m_houghHist(binX, binY).fill(identifier, layer, w);
0082   // and update our cached maxima
0083   YieldType nLayers = m_houghHist(binX, binY).nLayers();
0084   YieldType nHits = m_houghHist(binX, binY).nHits();
0085   if (nLayers > m_maxLayers) {
0086     m_maxLayers = nLayers;
0087     m_maxLocLayers = {binX, binY};
0088   }
0089   if (nHits > m_maxHits) {
0090     m_maxHits = nHits;
0091     m_maxLocHits = {binX, binY};
0092   }
0093 }
0094 
0095 template <class identifier_t>
0096 void Acts::HoughTransformUtils::HoughPlane<identifier_t>::reset() {
0097   // reset all bins that were previously filled
0098   // avoid calling this on empty cells to save time
0099   for (auto bin : m_touchedBins) {
0100     m_houghHist(bin).reset();
0101   }
0102   // don't forget to reset our cached maxima
0103   m_maxHits = 0.;
0104   m_maxLayers = 0.;
0105   // and reset the list of nontrivial bins
0106   m_touchedBins.clear();
0107 }
0108 
0109 template <class identifier_t>
0110 Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<identifier_t>::
0111     LayerGuidedCombinatoric(const LayerGuidedCombinatoricConfig& cfg)
0112     : m_cfg(cfg) {}
0113 
0114 template <class identifier_t>
0115 std::vector<typename Acts::HoughTransformUtils::PeakFinders::
0116                 LayerGuidedCombinatoric<identifier_t>::Maximum>
0117 Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<
0118     identifier_t>::findPeaks(const HoughPlane<identifier_t>& plane) const {
0119   // book the vector for the maxima
0120   std::vector<PeakFinders::LayerGuidedCombinatoric<identifier_t>::Maximum>
0121       maxima;
0122   // loop over the non empty bins
0123   for (auto [x, y] : plane.getNonEmptyBins()) {
0124     // and look for the ones that represent a maximum
0125     if (passThreshold(plane, x, y)) {
0126       // write out a maximum
0127       Maximum max;
0128       max.hitIdentifiers = plane.hitIds(x, y);
0129       maxima.push_back(max);
0130     }
0131   }
0132   return maxima;
0133 }
0134 
0135 template <class identifier_t>
0136 bool Acts::HoughTransformUtils::PeakFinders::LayerGuidedCombinatoric<
0137     identifier_t>::passThreshold(const HoughPlane<identifier_t>& plane,
0138                                  std::size_t xBin, std::size_t yBin) const {
0139   // Check if we have sufficient layers for a maximum
0140   if (plane.nLayers(xBin, yBin) < m_cfg.threshold) {
0141     return false;
0142   }
0143   // if no local maximum is required, this is enough to classify as a max
0144   if (m_cfg.localMaxWindowSize == 0) {
0145     return true;
0146   }
0147   // otherwise, check for local maxima by looking at the surrounding cells
0148 
0149   /// loop over neighbours
0150   for (int dXbin = -m_cfg.localMaxWindowSize; dXbin <= m_cfg.localMaxWindowSize;
0151        dXbin++) {
0152     for (int dYbin = -m_cfg.localMaxWindowSize;
0153          dYbin <= m_cfg.localMaxWindowSize; dYbin++) {
0154       // exclude the candidate itself
0155       if (dYbin == 0 && dXbin == 0) {
0156         continue;
0157       }
0158       // out of bounds -> no need to test this bin
0159       if (static_cast<int>(xBin) + dXbin < 0 ||
0160           static_cast<int>(yBin) + dYbin < 0) {
0161         continue;
0162       }
0163       if (xBin + dXbin >= plane.nBinsX() || yBin + dYbin >= plane.nBinsY()) {
0164         continue;
0165       }
0166       // if a neighbour with more layers exist, this is not a minimum
0167       if (plane.nLayers(xBin + dXbin, yBin + dYbin) >
0168           plane.nLayers(xBin, yBin)) {
0169         return false;
0170       }
0171       // if the neighbour has fewer hits, we can move to the next neighbour
0172       if (plane.nLayers(xBin + dXbin, yBin + dYbin) <
0173           plane.nLayers(xBin, yBin)) {
0174         continue;
0175       }
0176 
0177       // we can still be in a plateau. In this case, resolve by counting hits
0178 
0179       // if the other bin with the same hit count has seen more clusters (
0180       // double hits in one layer), keep that one and reject the current
0181       if (plane.nHits(xBin + dXbin, yBin + dYbin) > plane.nHits(xBin, yBin)) {
0182         return false;
0183       }
0184       // and if we have completely identical hit and layer count, prefer bins in
0185       // the bottom (left) direction
0186       if (plane.nHits(xBin + dXbin, yBin + dYbin) == plane.nHits(xBin, yBin) &&
0187           (dYbin < 0 || (dYbin == 0 && dXbin < 0))) {
0188         return false;
0189       }
0190     }
0191   }
0192   return true;
0193 }
0194 template <class identifier_t>
0195 Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0196     identifier_t>::IslandsAroundMax(const IslandsAroundMaxConfig& cfg)
0197     : m_cfg(cfg) {}
0198 
0199 template <class identifier_t>
0200 std::vector<typename Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0201     identifier_t>::Maximum>
0202 Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<
0203     identifier_t>::findPeaks(const HoughPlane<identifier_t>& plane,
0204                              const HoughAxisRanges& ranges) {
0205   // check the global maximum hit count in the plane
0206   YieldType max = plane.maxHits();
0207   // and obtain the fraction of the max that is our cutoff for island formation
0208   YieldType min = std::max(m_cfg.threshold, m_cfg.fractionCutoff * max);
0209   // book a list for the candidates and the maxima
0210   std::vector<std::pair<std::size_t, std::size_t>> candidates;
0211   std::vector<Maximum> maxima;
0212   // keep track of the yields in each non empty cell
0213   std::map<std::pair<std::size_t, std::size_t>, YieldType> yieldMap;
0214 
0215   // now loop over all non empty bins
0216   for (auto [x, y] : plane.getNonEmptyBins()) {
0217     // we only consider cells above threshold from now on.
0218     // each is a potential candidate to seed or join an island
0219     // We also add each to our yield map
0220     if (plane.nHits(x, y) > min) {
0221       candidates.push_back({x, y});
0222       yieldMap[{x, y}] = plane.nHits(x, y);
0223     }
0224   }
0225   // sort the candidate cells descending in content
0226   std::sort(candidates.begin(), candidates.end(),
0227             [&plane](const std::pair<std::size_t, std::size_t>& bin1,
0228                      const std::pair<std::size_t, std::size_t>& bin2) {
0229               return (plane.nHits(bin1.first, bin1.second) >
0230                       plane.nHits(bin2.first, bin2.second));
0231             });
0232 
0233   // now we build islands from the candidate cells, starting with the most
0234   // populated one
0235   std::vector<std::pair<std::size_t, std::size_t>> toExplore;
0236   std::vector<std::pair<std::size_t, std::size_t>> solution;
0237 
0238   // loop over candidate cells
0239   for (auto& cand : candidates) {
0240     // check the content again (if used in a previous island, this will now
0241     // report empty)
0242     if (yieldMap[cand] < min) {
0243       continue;
0244     }
0245     // translate to parameter space for overlap veto
0246     CoordType xCand =
0247         binCenter(ranges.xMin, ranges.xMax, plane.nBinsX(), cand.first);
0248     CoordType yCand =
0249         binCenter(ranges.yMin, ranges.yMax, plane.nBinsY(), cand.second);
0250     // check if we are too close to a previously found maximum
0251     bool goodSpacing = true;
0252     for (auto& found : maxima) {
0253       if (std::abs(xCand - found.x) < m_cfg.minSpacingBetweenPeaks.first ||
0254           std::abs(yCand - found.y) < m_cfg.minSpacingBetweenPeaks.second) {
0255         goodSpacing = false;
0256         break;
0257       }
0258     }
0259     if (!goodSpacing) {
0260       continue;
0261     }
0262     // now we can extend the candidate into an island
0263     toExplore.clear();
0264     solution.clear();
0265     // initially, pass the candidate into the list of "neighbours" to explore
0266     // around an empty island
0267     toExplore.push_back(cand);
0268     // and incrementally add neighbours, filling the solution vector
0269     while (!toExplore.empty()) {
0270       extendMaximum(solution, toExplore, min, yieldMap);
0271     }
0272     // nothing found? Next candidate!
0273     if (solution.empty()) {
0274       continue;
0275     }
0276     // We found an island
0277     Maximum maximum;
0278     CoordType max_x = 0;
0279     CoordType max_y = 0;
0280     CoordType pos_den = 0;
0281     CoordType ymax = -std::numeric_limits<CoordType>::max();
0282     CoordType ymin = std::numeric_limits<CoordType>::max();
0283     CoordType xmax = -std::numeric_limits<CoordType>::max();
0284     CoordType xmin = std::numeric_limits<CoordType>::max();
0285     // loop over cells in the island and get the weighted mean position.
0286     // Also collect all hit identifiers in the island and the maximum
0287     // extent (within the count threshold!) of the island
0288     for (auto& [xBin, yBin] : solution) {
0289       const auto& hidIds = plane.hitIds(xBin, yBin);
0290       maximum.hitIdentifiers.insert(hidIds.cbegin(), hidIds.cend());
0291       CoordType xHit =
0292           binCenter(ranges.xMin, ranges.xMax, plane.nBinsX(), xBin);
0293       CoordType yHit =
0294           binCenter(ranges.yMin, ranges.yMax, plane.nBinsY(), yBin);
0295       YieldType nHits = plane.nHits(xBin, yBin);
0296       max_x += xHit * nHits;
0297       max_y += yHit * nHits;
0298       pos_den += nHits;
0299       if (xHit > xmax) {
0300         xmax = xHit;
0301       }
0302       if (yHit > ymax) {
0303         ymax = yHit;
0304       }
0305       if (xHit < xmin) {
0306         xmin = xHit;
0307       }
0308       if (yHit < ymin) {
0309         ymin = yHit;
0310       }
0311     }
0312     // calculate mean position
0313     maximum.x = max_x / pos_den;
0314     maximum.y = max_y / pos_den;
0315     // calculate width as symmetrised band
0316     maximum.wx = 0.5 * (xmax - xmin);
0317     maximum.wy = 0.5 * (ymax - ymin);
0318     maxima.push_back(maximum);
0319   }
0320   return maxima;
0321 }
0322 
0323 template <class identifier_t>
0324 void Acts::HoughTransformUtils::PeakFinders::IslandsAroundMax<identifier_t>::
0325     extendMaximum(
0326         std::vector<std::pair<std::size_t, std::size_t>>& inMaximum,
0327         std::vector<std::pair<std::size_t, std::size_t>>& toExplore,
0328         YieldType threshold,
0329         std::map<std::pair<std::size_t, std::size_t>, YieldType>& yieldMap) {
0330   // in this call, we explore the last element of the toExplore list.
0331   // Fetch it and pop it from the vector.
0332   auto nextCand = toExplore.back();
0333   toExplore.pop_back();
0334   // check if we are above threshold. Don't add this cell to the island if not
0335   if (yieldMap[nextCand] < threshold) {
0336     return;
0337   }
0338   // This candidate is above threshold and should go on the island!
0339 
0340   // add it to the cell list for the island
0341   inMaximum.push_back(nextCand);
0342   // and "veto" the hit for further use via the yield map
0343   yieldMap[nextCand] = -1.0f;
0344 
0345   // now we have to collect the non empty neighbours of this cell and check them
0346   // as well
0347   for (auto step : m_stepDirections) {
0348     auto newCand = std::make_pair(nextCand.first + step.first,
0349                                   nextCand.second + step.second);
0350     // no need for bounds-checking, as the map structure will default to "empty"
0351     // yields for OOB
0352 
0353     // if the cell is above threshold, add it to our list of neighbours to
0354     // explore
0355     if (yieldMap[newCand] > threshold) {
0356       toExplore.push_back(newCand);
0357     }
0358   }
0359 }