Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-08-30 08:14:27

0001 #include "StripeDetector.h"
0002 
0003 #include "helpers.h"
0004 #include "parameters.h"
0005 
0006 #include <TH2.h>
0007 
0008 #include <algorithm>
0009 #include <cmath>
0010 #include <iostream>
0011 #include <queue>
0012 #include <string>
0013 #include <array>
0014 #include <vector>
0015 
0016 bool compare_seed_bins(const std::array<double, 5> &lhs, const std::array<double, 5> &rhs)
0017 {
0018   // Process brighter seed bins first. This lets strong bins claim nearby weak
0019   // bins before a lower-content seed can start a duplicate component.
0020   if (lhs[component_content] != rhs[component_content])
0021   {
0022     return lhs[component_content] > rhs[component_content];
0023   }
0024   if (static_cast<int>(lhs[component_bin_y]) != static_cast<int>(rhs[component_bin_y]))
0025   {
0026     return static_cast<int>(lhs[component_bin_y]) < static_cast<int>(rhs[component_bin_y]);
0027   }
0028   return static_cast<int>(lhs[component_bin_x]) < static_cast<int>(rhs[component_bin_x]);
0029 }
0030 
0031 bool compare_stripe_content(const std::array<double, 3> &lhs, const std::array<double, 3> &rhs)
0032 {
0033   // Keep high-integral stripe candidates first during duplicate removal.
0034   return lhs[stripe_content] > rhs[stripe_content];
0035 }
0036 
0037 int StripeDetector::wrapped_phi_bin(int bin, int nBinsX)
0038 {
0039   // Histogram phi bins are periodic. Wrap any neighbor probe back into [1,N].
0040   while (bin < 1)
0041   {
0042     bin += nBinsX;
0043   }
0044   while (bin > nBinsX)
0045   {
0046     bin -= nBinsX;
0047   }
0048   return bin;
0049 }
0050 
0051 void StripeDetector::normalize_phi_to_axis(TH2 *histogram, double &phi)
0052 {
0053   // ROOT histogram axes are finite even when the physics coordinate is
0054   // periodic, so normalize centroids into the stored axis range.
0055   const double phiMin = histogram->GetXaxis()->GetXmin();
0056   const double phiMax = histogram->GetXaxis()->GetXmax();
0057   const double phiWidth = phiMax - phiMin;
0058   while (phi < phiMin)
0059   {
0060     phi += phiWidth;
0061   }
0062   while (phi >= phiMax)
0063   {
0064     phi -= phiWidth;
0065   }
0066 }
0067 
0068 bool StripeDetector::detect(TH2 *histogram, std::vector<std::array<double, 3>> &stripes)
0069 {
0070   // One public call owns the whole stripe finding workflow:
0071   // reset state, clone the histogram, find components, convert to centroids.
0072   if (!initialize(histogram, stripes) || !create_working_histogram(histogram))
0073   {
0074     clear();
0075     return false;
0076   }
0077 
0078   collect_seed_bins(histogram);
0079   build_connected_components();
0080   convert_components_to_raw_stripes(histogram);
0081   stripes = m_rawStripes;
0082   clear();
0083   return true;
0084 }
0085 
0086 bool StripeDetector::initialize(TH2 *histogram, std::vector<std::array<double, 3>> &stripes)
0087 {
0088   // Cache dimensions early. A null histogram leaves dimensions at zero and
0089   // returns false below.
0090   m_nBinsX = histogram ? histogram->GetNbinsX() : 0;
0091   m_nBinsY = histogram ? histogram->GetNbinsY() : 0;
0092 
0093   stripes.clear();
0094   m_seedBinsByPhi.assign(m_nBinsX + 2, {});
0095   m_components.clear();
0096   m_rawStripes.clear();
0097   m_nSeedBins = 0;
0098 
0099   if (!histogram)
0100   {
0101     std::cout << "ERROR: Null histogram passed to StripeDetector" << std::endl;
0102     return false;
0103   }
0104 
0105   return true;
0106 }
0107 
0108 bool StripeDetector::create_working_histogram(TH2 *histogram)
0109 {
0110   // The clone is detached from any ROOT directory so deleting it here is safe.
0111   clear();
0112   if (!histogram)
0113   {
0114     return false;
0115   }
0116 
0117   m_workingHist = dynamic_cast<TH2 *>(histogram->Clone((std::string(histogram->GetName()) + "_floodFillInput").c_str()));
0118   if (!m_workingHist)
0119   {
0120     std::cout << "ERROR: Could not clone input histogram for stripe detection" << std::endl;
0121     return false;
0122   }
0123 
0124   m_workingHist->SetDirectory(nullptr);
0125   return true;
0126 }
0127 
0128 void StripeDetector::clear()
0129 {
0130   // clear() is safe to call repeatedly; delete handles nullptr.
0131   delete m_workingHist;
0132   m_workingHist = nullptr;
0133 }
0134 
0135 void StripeDetector::collect_seed_bins(TH2 *histogram)
0136 {
0137   // Store every above-threshold bin as {binX, binY, phi, R, content}.
0138   // Grouping by phi bin makes later seed flattening straightforward.
0139   if (!histogram || !m_workingHist)
0140   {
0141     return;
0142   }
0143 
0144   m_seedBinsByPhi.assign(m_nBinsX + 2, {});
0145   m_nSeedBins = 0;
0146   for (int i = 1; i <= m_nBinsX; i++)
0147   {
0148     for (int j = 1; j <= m_nBinsY; j++)
0149     {
0150       const double content = m_workingHist->GetBinContent(i, j);
0151       if (content <= 0.0 || content < stripeFloodSeedMinContent)
0152       {
0153         continue;
0154       }
0155 
0156       const double r = histogram->GetYaxis()->GetBinCenter(j);
0157       const double phi = histogram->GetXaxis()->GetBinCenter(i);
0158       std::array<double, 5> seed{};
0159       seed[component_bin_x] = i;
0160       seed[component_bin_y] = j;
0161       seed[component_phi] = phi;
0162       seed[component_r] = r;
0163       seed[component_content] = content;
0164       m_seedBinsByPhi[i].push_back(seed);
0165     }
0166   }
0167 
0168   for (int i = 1; i <= m_nBinsX; i++)
0169   {
0170     m_nSeedBins += m_seedBinsByPhi[i].size();
0171   }
0172 }
0173 
0174 void StripeDetector::build_connected_components()
0175 {
0176   
0177 
0178   // Flatten the phi-binned seed list so it can be sorted by content.
0179   m_components.clear();
0180   if (!m_workingHist)
0181   {
0182     return;
0183   }
0184 
0185   std::vector<std::array<double, 5>> seeds;
0186   seeds.reserve(m_nSeedBins);
0187   for (int i = 1; i <= m_nBinsX; i++)
0188   {
0189     for (const auto &point : m_seedBinsByPhi[i])
0190     {
0191       seeds.push_back(point);
0192     }
0193   }
0194 
0195   std::sort(seeds.begin(), seeds.end(), compare_seed_bins);
0196 
0197   std::vector<std::vector<bool>> used(m_nBinsX + 1, std::vector<bool>(m_nBinsY + 1, false));
0198 
0199   // Start a flood fill from each unused seed. Neighbor bins are allowed to be
0200   // close in phi/R but components are rejected if they grow too wide.
0201   for (const auto &seed : seeds)
0202   {
0203     if (used[static_cast<int>(seed[component_bin_x])][static_cast<int>(seed[component_bin_y])])
0204     {
0205       continue;
0206     }
0207 
0208     std::vector<std::array<double, 5>> component;
0209     std::queue<std::array<double, 5>> queue;
0210     used[static_cast<int>(seed[component_bin_x])][static_cast<int>(seed[component_bin_y])] = true;
0211     queue.push(seed);
0212 
0213     int minBinPhi = static_cast<int>(seed[component_bin_x]);
0214     int maxBinPhi = static_cast<int>(seed[component_bin_x]);
0215     int minBinR = static_cast<int>(seed[component_bin_y]);
0216     int maxBinR = static_cast<int>(seed[component_bin_y]);
0217 
0218     while (!queue.empty())
0219     {
0220       const std::array<double, 5> current = queue.front();
0221       queue.pop();
0222       component.push_back(current);
0223 
0224       for (int dx = -stripeFloodMaxPhiGapBins - 1; dx <= stripeFloodMaxPhiGapBins + 1; dx++)
0225       {
0226         for (int dy = -stripeFloodMaxRGapBins - 1; dy <= stripeFloodMaxRGapBins + 1; dy++)
0227         {
0228           if (dx == 0 && dy == 0)
0229           {
0230             continue;
0231           }
0232 
0233           const int binX = wrapped_phi_bin(static_cast<int>(current[component_bin_x]) + dx, m_nBinsX);
0234           const int binY = static_cast<int>(current[component_bin_y]) + dy;
0235           if (binY < 1 || binY > m_nBinsY || used[binX][binY])
0236           {
0237             continue;
0238           }
0239 
0240           const double content = m_workingHist->GetBinContent(binX, binY);
0241           if (content < stripeFloodGrowMinContent)
0242           {
0243             continue;
0244           }
0245 
0246           // Limit the bounding box so one flood fill does not merge nearby
0247           // stripes into a long blob.
0248           const int candidateMinPhi = std::min(minBinPhi, binX);
0249           const int candidateMaxPhi = std::max(maxBinPhi, binX);
0250           const int candidateMinR = std::min(minBinR, binY);
0251           const int candidateMaxR = std::max(maxBinR, binY);
0252           if (candidateMaxPhi - candidateMinPhi + 1 > stripeFloodMaxPhiSpanBins || candidateMaxR - candidateMinR + 1 > stripeFloodMaxRSpanBins)
0253           {
0254             continue;
0255           }
0256 
0257           const double r = m_workingHist->GetYaxis()->GetBinCenter(binY);
0258           const double phi = m_workingHist->GetXaxis()->GetBinCenter(binX);
0259           used[binX][binY] = true;
0260           minBinPhi = candidateMinPhi;
0261           maxBinPhi = candidateMaxPhi;
0262           minBinR = candidateMinR;
0263           maxBinR = candidateMaxR;
0264           std::array<double, 5> neighbor{};
0265           neighbor[component_bin_x] = binX;
0266           neighbor[component_bin_y] = binY;
0267           neighbor[component_phi] = phi;
0268           neighbor[component_r] = r;
0269           neighbor[component_content] = content;
0270           queue.push(neighbor);
0271         }
0272       }
0273     }
0274 
0275     if (!component.empty())
0276     {
0277       m_components.push_back(component);
0278     }
0279   }
0280 }
0281 
0282 void StripeDetector::convert_components_to_raw_stripes(TH2 *histogram)
0283 {
0284   
0285 
0286   // Convert each connected component into one weighted centroid stripe.
0287   if (!histogram)
0288   {
0289     return;
0290   }
0291 
0292   m_rawStripes.clear();
0293   for (const auto &component : m_components)
0294   {
0295     if (static_cast<int>(component.size()) < stripeFloodMinBins)
0296     {
0297       continue;
0298     }
0299 
0300     double sumW = 0.0;
0301     double sumPhi = 0.0;
0302     double sumR = 0.0;
0303     double totalContent = 0.0;
0304     const double seedPhi = component.front()[component_phi];
0305     for (const auto &point : component)
0306     {
0307       // Average phi relative to the seed so components crossing the periodic
0308       // axis boundary do not get pulled to the wrong side.
0309       const double dphi = wrap_delta_phi(point[component_phi] - seedPhi);
0310       sumW += point[component_content];
0311       sumPhi += point[component_content] * dphi;
0312       sumR += point[component_content] * point[component_r];
0313       totalContent += point[component_content];
0314     }
0315 
0316     if (sumW <= 0.0 || totalContent < stripeFloodMinTotalContent)
0317     {
0318       continue;
0319     }
0320 
0321     std::array<double, 3> cluster{};
0322     cluster[stripe_phi] = seedPhi + sumPhi / sumW;
0323     normalize_phi_to_axis(histogram, cluster[stripe_phi]);
0324     cluster[stripe_r] = sumR / sumW;
0325     cluster[stripe_content] = totalContent;
0326     m_rawStripes.push_back(cluster);
0327   }
0328 
0329   std::sort(m_rawStripes.begin(), m_rawStripes.end(), compare_stripe_content);
0330 
0331   std::vector<std::array<double, 3>> uniqueStripes;
0332   uniqueStripes.reserve(m_rawStripes.size());
0333   // Remove duplicate centroids caused by overlapping seed regions.
0334   for (const auto &candidate : m_rawStripes)
0335   {
0336     bool duplicate = false;
0337     for (const auto &kept : uniqueStripes)
0338     {
0339       const double deltaR = std::abs(candidate[stripe_r] - kept[stripe_r]);
0340       const double deltaPhi = std::abs(wrap_delta_phi(candidate[stripe_phi] - kept[stripe_phi]));
0341       if (deltaR <= duplicateStripeMaxDeltaRCm && deltaPhi <= duplicateStripeMaxDeltaPhiRad)
0342       {
0343         duplicate = true;
0344         break;
0345       }
0346     }
0347     if (!duplicate)
0348     {
0349       uniqueStripes.push_back(candidate);
0350     }
0351   }
0352   m_rawStripes.swap(uniqueStripes);
0353 }