Back to home page

sPhenix code displayed by LXR

 
 

    


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