Back to home page

sPhenix code displayed by LXR

 
 

    


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

0001 #include "helpers.h"
0002 
0003 #include "parameters.h"
0004 
0005 #include <TDirectory.h>
0006 #include <TFile.h>
0007 #include <TH2.h>
0008 #include <TObject.h>
0009 
0010 #include <algorithm>
0011 #include <cmath>
0012 #include <iostream>
0013 #include <string>
0014 #include <utility>
0015 
0016 double wrap_delta_phi(double dphi)
0017 {
0018   // Adjust by full turns until the difference is the shortest angular path.
0019   while (dphi > M_PI)
0020   {
0021     dphi -= 2.0 * M_PI;
0022   }
0023   while (dphi < -M_PI)
0024   {
0025     dphi += 2.0 * M_PI;
0026   }
0027 
0028   return dphi;
0029 }
0030 
0031 double distance_Rdphi(double phi1, double r1, double phi2, double r2)
0032 {
0033   // Convert phi separation into a length using the mean radius.
0034   const double dphi = wrap_delta_phi(phi1 - phi2);
0035   const double dr = r1 - r2;
0036   const double rmean = 0.5 * (r1 + r2);
0037   const double rdphi = rmean * dphi;
0038   return std::sqrt(dr * dr + rdphi * rdphi);
0039 }
0040 
0041 double median_value(std::vector<double> values)
0042 {
0043   // Work on a copy so callers keep their original ordering.
0044   if (values.empty())
0045   {
0046     return 0.0;
0047   }
0048 
0049   std::sort(values.begin(), values.end());
0050 
0051   const size_t n = values.size();
0052   if (n % 2 == 1)
0053   {
0054     return values[n / 2];
0055   }
0056 
0057   return 0.5 * (values[n / 2 - 1] + values[n / 2]);
0058 }
0059 
0060 double robust_mad_sigma(const std::vector<double> &values, double fallback)
0061 {
0062   // Fewer than three points cannot define a useful robust width.
0063   if (values.size() < 3)
0064   {
0065     return fallback;
0066   }
0067 
0068   const double med = median_value(values);
0069 
0070   std::vector<double> abs_dev;
0071   abs_dev.reserve(values.size());
0072   for (double value : values)
0073   {
0074     abs_dev.push_back(std::abs(value - med));
0075   }
0076 
0077   const double mad = median_value(abs_dev);
0078   const double sigma = 1.4826 * mad;
0079 
0080   if (sigma <= 1e-6)
0081   {
0082     return fallback;
0083   }
0084 
0085   return sigma;
0086 }
0087 
0088 void safe_write_object(TObject *obj)
0089 {
0090   // Only write when a writable ROOT file is currently active.
0091   TFile *file = gDirectory ? gDirectory->GetFile() : nullptr;
0092   if (!obj || !file || !file->IsOpen() || !file->IsWritable())
0093   {
0094     return;
0095   }
0096 
0097   obj->Write("", TObject::kOverwrite);
0098 }
0099 
0100 TH2 *load_detached_histogram(TFile *file, const char *histogram_name)
0101 {
0102   // Detach the histogram from the input file so it survives after file close.
0103   auto *hist = file ? dynamic_cast<TH2 *>(file->Get(histogram_name)) : nullptr;
0104   if (hist)
0105   {
0106     hist->SetDirectory(nullptr);
0107   }
0108   return hist;
0109 }
0110 
0111 void filter_isolated_stripes(const std::vector<std::array<double, 3>> &input, std::vector<std::array<double, 3>> &output)
0112 {
0113   // Count neighbors in the local R/Rdphi metric and keep dense stripe groups.
0114   output.clear();
0115   for (size_t i = 0; i < input.size(); ++i)
0116   {
0117     int neighbor_count = 0;
0118     for (size_t j = 0; j < input.size(); ++j)
0119     {
0120       if (i == j)
0121       {
0122         continue;
0123       }
0124       if (distance_Rdphi(input[i][stripe_phi], input[i][stripe_r], input[j][stripe_phi], input[j][stripe_r]) <= isolation_radius_cm)
0125       {
0126         ++neighbor_count;
0127       }
0128     }
0129     if (neighbor_count >= min_isolation_neighbors)
0130     {
0131       output.push_back(input[i]);
0132     }
0133   }
0134 }
0135 
0136 inline constexpr int radial_peak_r = 3;
0137 inline constexpr int radial_peak_low_r = 4;
0138 inline constexpr int radial_peak_high_r = 5;
0139 inline constexpr int radial_gap_low_bin_y = 0;
0140 inline constexpr int radial_gap_high_bin_y = 1;
0141 inline constexpr int radial_gap_low_r = 2;
0142 inline constexpr int radial_gap_high_r = 3;
0143 
0144 size_t lamination_mask_index(int nBinsY, int phiBin, int rBin)
0145 {
0146   // Store a 2D mask in one vector. ROOT bins start at one; vectors start at zero.
0147   return static_cast<size_t>(phiBin - 1) * static_cast<size_t>(nBinsY) + static_cast<size_t>(rBin - 1);
0148 }
0149 
0150 void mask_lamination_bin(TH2 *histogram, std::vector<char> &masked, int nBinsY, int i, int j, long long &counter)
0151 {
0152   // Mask one bin and count it only once even if multiple rules hit it.
0153   const size_t index = lamination_mask_index(nBinsY, i, j);
0154   if (!masked[index])
0155   {
0156     masked[index] = true;
0157     counter++;
0158   }
0159   histogram->SetBinContent(i, j, 0.0);
0160   histogram->SetBinError(i, j, 0.0);
0161 }
0162 
0163 void mask_lamination_radial_range(TH2 *histogram, std::vector<char> &masked, int nBinsX, int nBinsY, int lowBinY, int highBinY, long long &counter)
0164 {
0165   // Mask all phi columns over a radial interval.
0166   for (int i = 1; i <= nBinsX; i++)
0167   {
0168     for (int j = lowBinY; j <= highBinY; j++)
0169     {
0170       mask_lamination_bin(histogram, masked, nBinsY, i, j, counter);
0171     }
0172   }
0173 }
0174 
0175 void mask_lamination_phi_column(TH2 *histogram, std::vector<char> &masked, int nBinsY, int i, long long &counter)
0176 {
0177   // Mask all radii for one phi column.
0178   for (int j = 1; j <= nBinsY; j++)
0179   {
0180     mask_lamination_bin(histogram, masked, nBinsY, i, j, counter);
0181   }
0182 }
0183 
0184 bool compare_radial_peak_r(const std::array<double, 7> &lhs, const std::array<double, 7> &rhs)
0185 {
0186   // Sort outer-to-inner so gaps are built between neighboring rows.
0187   return lhs[radial_peak_r] > rhs[radial_peak_r];
0188 }
0189 
0190 double effective_gap_mask_padding(double gapWidth)
0191 {
0192   // Do not let fixed padding consume too much of a narrow gap.
0193   return std::min(radialGapMaskPaddingCm, radialGapMaskMaxPaddingFraction * gapWidth);
0194 }
0195 
0196 std::pair<TH2 *, std::vector<double>> clean_laminations(TH2 *histogram)
0197 {
0198   
0199 
0200   // first: cleaned histogram. second: radial gap centers used as field controls.
0201   std::pair<TH2 *, std::vector<double>> result{nullptr, {}};
0202 
0203   if (!histogram)
0204   {
0205     return result;
0206   }
0207 
0208   result.first = dynamic_cast<TH2 *>(histogram->Clone((std::string(histogram->GetName()) + "_cleaned").c_str()));
0209   if (!result.first)
0210   {
0211     std::cout << "ERROR: Could not clone histogram for lamination cleaning: " << histogram->GetName() << std::endl;
0212     return result;
0213   }
0214   result.first->SetDirectory(nullptr);
0215 
0216   const int nBinsX = histogram->GetNbinsX();
0217   const int nBinsY = histogram->GetNbinsY();
0218   auto *xAxis = histogram->GetXaxis();
0219   auto *yAxis = histogram->GetYaxis();
0220 
0221   // Internal mask bookkeeping is intentionally kept in plain vectors rather
0222   // than ROOT histograms. This function is on the hot path before stripe
0223   // detection, and the diagnostic mask histograms are no longer written.
0224   std::vector<char> masked(static_cast<size_t>(nBinsX) * static_cast<size_t>(nBinsY), false);
0225   std::vector<double> radialProjection(static_cast<size_t>(nBinsY) + 1, 0.0);
0226   std::vector<double> phiCenters(static_cast<size_t>(nBinsX) + 1, 0.0);
0227   for (int i = 1; i <= nBinsX; i++)
0228   {
0229     phiCenters[static_cast<size_t>(i)] = xAxis->GetBinCenter(i);
0230   }
0231 
0232   // 1. Find stripe-row peaks in the radial projection. The gaps between these
0233   // rows are where laminations live and where masking is allowed.
0234   for (int j = 1; j <= nBinsY; j++)
0235   {
0236     double sum = 0.0;
0237     for (int i = 1; i <= nBinsX; i++)
0238     {
0239       const double content = histogram->GetBinContent(i, j);
0240       if (content < radialRowProjectionMinContent)
0241       {
0242         continue;
0243       }
0244 
0245       sum += content;
0246     }
0247     radialProjection[static_cast<size_t>(j)] = sum;
0248   }
0249 
0250   const double radialProjectionMax = *std::max_element(radialProjection.begin() + 1, radialProjection.end());
0251   const double rowPeakThreshold = radialRowPeakThresholdFrac * radialProjectionMax;
0252 
0253   std::vector<std::array<double, 7>> rowPeaks;
0254   // A row peak stores {bin, low bin, high bin, R, low R, high R, projection}.
0255   for (int j = 2; j <= nBinsY - 1; j++)
0256   {
0257     const double r = yAxis->GetBinCenter(j);
0258     const double value = radialProjection[static_cast<size_t>(j)];
0259 
0260     if (r < radialRowPeakMinRCm || value < rowPeakThreshold)
0261     {
0262       continue;
0263     }
0264     if (value <= radialProjection[static_cast<size_t>(j - 1)] || value <= radialProjection[static_cast<size_t>(j + 1)])
0265     {
0266       continue;
0267     }
0268     bool separated = true;
0269     for (const auto &peak : rowPeaks)
0270     {
0271       if (std::abs(r - peak[radial_peak_r]) < radialRowPeakMinSeparationCm)
0272       {
0273         separated = false;
0274         break;
0275       }
0276     }
0277 
0278     if (separated)
0279     {
0280       const double rowBoundaryThreshold = radialRowBoundaryThresholdFrac * value;
0281       int lowBinY = j;
0282       int highBinY = j;
0283 
0284       while (lowBinY > 1 && yAxis->GetBinCenter(lowBinY - 1) >= r - radialRowBoundaryMaxHalfWidthCm && radialProjection[static_cast<size_t>(lowBinY - 1)] >= rowBoundaryThreshold)
0285       {
0286         lowBinY--;
0287       }
0288 
0289       while (highBinY < nBinsY && yAxis->GetBinCenter(highBinY + 1) <= r + radialRowBoundaryMaxHalfWidthCm && radialProjection[static_cast<size_t>(highBinY + 1)] >= rowBoundaryThreshold)
0290       {
0291         highBinY++;
0292       }
0293 
0294       std::array<double, 7> rowPeak{};
0295       rowPeak[0] = j;
0296       rowPeak[1] = lowBinY;
0297       rowPeak[2] = highBinY;
0298       rowPeak[3] = r;
0299       rowPeak[4] = yAxis->GetBinCenter(lowBinY);
0300       rowPeak[5] = yAxis->GetBinCenter(highBinY);
0301       rowPeak[6] = value;
0302       rowPeaks.push_back(rowPeak);
0303     }
0304   }
0305 
0306   std::sort(rowPeaks.begin(), rowPeaks.end(), compare_radial_peak_r);
0307 
0308   long long largeGapMaskBins = 0;
0309   long long laminationPhiMaskBins = 0;
0310   long long radialGapMaskBins = 0;
0311 
0312   // 2. Convert neighboring row peaks into radial gaps. Very large gaps are
0313   // masked immediately because they do not contain usable stripe information.
0314   std::vector<std::array<double, 4>> radialGaps;
0315   // A radial gap stores {low bin, high bin, low R, high R}.
0316   for (size_t k = 0; k + 1 < rowPeaks.size(); k++)
0317   {
0318     const double highR = rowPeaks[k][radial_peak_low_r];
0319     const double lowR = rowPeaks[k + 1][radial_peak_high_r];
0320     if (lowR < laminationOuterGapMinRCm)
0321     {
0322       break;
0323     }
0324 
0325     const double gapHighR = highR - radialGapBoundaryPaddingCm;
0326     const double gapLowR = lowR + radialGapBoundaryPaddingCm;
0327 
0328     if (gapHighR <= gapLowR)
0329     {
0330       continue;
0331     }
0332 
0333     const int lowBinY = yAxis->FindBin(gapLowR);
0334     const int highBinY = yAxis->FindBin(gapHighR);
0335     if (highBinY < lowBinY)
0336     {
0337       continue;
0338     }
0339 
0340     const double gapWidth = gapHighR - gapLowR;
0341     if (gapWidth > radialGapMaxLaminationWidthCm)
0342     {
0343       const double gapMaskPadding = effective_gap_mask_padding(gapWidth);
0344       const int paddedLowBinY = std::max(1, yAxis->FindBin(gapLowR + gapMaskPadding));
0345       const int paddedHighBinY = std::min(nBinsY, yAxis->FindBin(gapHighR - gapMaskPadding));
0346       if (paddedHighBinY < paddedLowBinY)
0347       {
0348         continue;
0349       }
0350       mask_lamination_radial_range(result.first, masked, nBinsX, nBinsY, paddedLowBinY, paddedHighBinY, largeGapMaskBins);
0351       continue;
0352     }
0353 
0354     const int clampedLowBinY = std::max(1, lowBinY);
0355     const int clampedHighBinY = std::min(nBinsY, highBinY);
0356 
0357     std::array<double, 4> radialGap{};
0358     radialGap[radial_gap_low_bin_y] = clampedLowBinY;
0359     radialGap[radial_gap_high_bin_y] = clampedHighBinY;
0360     radialGap[radial_gap_low_r] = yAxis->GetBinCenter(clampedLowBinY);
0361     radialGap[radial_gap_high_r] = yAxis->GetBinCenter(clampedHighBinY);
0362     radialGaps.push_back(radialGap);
0363 
0364     if (static_cast<int>(radialGaps.size()) >= maxLaminationGapCount)
0365     {
0366       break;
0367     }
0368   }
0369 
0370   // 3. Within each acceptable radial gap, project onto phi and count which phi
0371   // bins repeatedly carry lamination-like content across gaps.
0372   std::vector<double> globalPhiSupport(nBinsX + 1, 0.0);
0373   for (size_t igap = 0; igap < radialGaps.size(); igap++)
0374   {
0375     const auto &gap = radialGaps[igap];
0376     std::vector<double> phiProjection(nBinsX + 1, 0.0);
0377     std::vector<double> radialWeights(static_cast<size_t>(static_cast<int>(gap[radial_gap_high_bin_y])) + 1, 1.0);
0378     if (weightLaminationGapByDistanceFromRows)
0379     {
0380       const double gapWidth = gap[radial_gap_high_r] - gap[radial_gap_low_r];
0381       const double halfGapWidth = 0.5 * gapWidth;
0382       if (halfGapWidth > 0.0)
0383       {
0384         for (int j = static_cast<int>(gap[radial_gap_low_bin_y]); j <= static_cast<int>(gap[radial_gap_high_bin_y]); j++)
0385         {
0386           const double r = yAxis->GetBinCenter(j);
0387           const double distanceFromNearestRow = std::min(r - gap[radial_gap_low_r], gap[radial_gap_high_r] - r);
0388           double weight = std::clamp(distanceFromNearestRow / halfGapWidth, 0.0, 1.0);
0389           radialWeights[static_cast<size_t>(j)] = std::pow(weight, laminationGapRadialWeightPower);
0390         }
0391       }
0392     }
0393 
0394     for (int i = 1; i <= nBinsX; i++)
0395     {
0396       double sum = 0.0;
0397       for (int j = static_cast<int>(gap[radial_gap_low_bin_y]); j <= static_cast<int>(gap[radial_gap_high_bin_y]); j++)
0398       {
0399         sum += radialWeights[static_cast<size_t>(j)] * histogram->GetBinContent(i, j);
0400       }
0401 
0402       phiProjection[i] = sum;
0403     }
0404 
0405     for (int i = 1; i <= nBinsX; i++)
0406     {
0407       const double value = phiProjection[i];
0408       if (value < laminationGapPhiMinContent)
0409       {
0410         continue;
0411       }
0412       for (int di = -laminationGlobalPhiIntegralHalfWindowBins; di <= laminationGlobalPhiIntegralHalfWindowBins; di++)
0413       {
0414         int supportBin = i + di;
0415         while (supportBin < 1)
0416         {
0417           supportBin += nBinsX;
0418         }
0419         while (supportBin > nBinsX)
0420         {
0421           supportBin -= nBinsX;
0422         }
0423         globalPhiSupport[supportBin]++;
0424       }
0425     }
0426   }
0427 
0428   // 4. Suppress duplicate nearby phi candidates so each lamination contributes
0429   // one narrow masked phi band.
0430   while (true)
0431   {
0432     int closestBinA = 0;
0433     int closestBinB = 0;
0434     double closestDistance = laminationGlobalPhiSuppressWindowRad;
0435 
0436     for (int i = 1; i <= nBinsX; i++)
0437     {
0438       if (globalPhiSupport[i] <= 0.0)
0439       {
0440         continue;
0441       }
0442 
0443       const double phi = phiCenters[static_cast<size_t>(i)];
0444       for (int ii = i + 1; ii <= nBinsX; ii++)
0445       {
0446         if (globalPhiSupport[ii] <= 0.0)
0447         {
0448           continue;
0449         }
0450         const double otherPhi = phiCenters[static_cast<size_t>(ii)];
0451         const double dphi = std::abs(wrap_delta_phi(phi - otherPhi));
0452         if (dphi > laminationGlobalPhiSuppressWindowRad)
0453         {
0454           continue;
0455         }
0456         if (dphi < closestDistance)
0457         {
0458           closestDistance = dphi;
0459           closestBinA = i;
0460           closestBinB = ii;
0461         }
0462       }
0463     }
0464 
0465     if (closestBinA == 0 || closestBinB == 0)
0466     {
0467       break;
0468     }
0469 
0470     if (globalPhiSupport[closestBinB] > globalPhiSupport[closestBinA])
0471     {
0472       globalPhiSupport[closestBinA] = 0.0;
0473     }
0474     else
0475     {
0476       globalPhiSupport[closestBinB] = 0.0;
0477     }
0478   }
0479 
0480   // 5. Apply the phi-band masks and then mask all accepted radial gaps.
0481   std::vector<double> laminationPhis;
0482   for (int i = 1; i <= nBinsX; i++)
0483   {
0484     if (globalPhiSupport[i] <= laminationGlobalPhiSupportThreshold)
0485     {
0486       continue;
0487     }
0488 
0489     const double phi = phiCenters[static_cast<size_t>(i)];
0490     laminationPhis.push_back(phi);
0491   }
0492 
0493   for (double lamPhi : laminationPhis)
0494   {
0495     const int firstBin = wrapPhiForPreLaminationMask ? 1 : std::max(1, xAxis->FindBin(lamPhi - laminationPhiMaskHalfWidthRad) - 1);
0496     const int lastBin = wrapPhiForPreLaminationMask ? nBinsX : std::min(nBinsX, xAxis->FindBin(lamPhi + laminationPhiMaskHalfWidthRad) + 1);
0497     for (int i = firstBin; i <= lastBin; i++)
0498     {
0499       const double phi = phiCenters[static_cast<size_t>(i)];
0500       const double dphi = wrapPhiForPreLaminationMask ? std::abs(wrap_delta_phi(phi - lamPhi)) : std::abs(phi - lamPhi);
0501 
0502       if (dphi > laminationPhiMaskHalfWidthRad)
0503       {
0504         continue;
0505       }
0506 
0507       mask_lamination_phi_column(result.first, masked, nBinsY, i, laminationPhiMaskBins);
0508     }
0509   }
0510 
0511   for (const auto &gap : radialGaps)
0512   {
0513     const double gapWidth = gap[radial_gap_high_r] - gap[radial_gap_low_r];
0514     const double gapMaskPadding = effective_gap_mask_padding(gapWidth);
0515     const int paddedLowBinY = std::max(1, yAxis->FindBin(gap[radial_gap_low_r] + gapMaskPadding));
0516     const int paddedHighBinY = std::min(nBinsY, yAxis->FindBin(gap[radial_gap_high_r] - gapMaskPadding));
0517     if (paddedHighBinY < paddedLowBinY)
0518     {
0519       continue;
0520     }
0521     mask_lamination_radial_range(result.first, masked, nBinsX, nBinsY, paddedLowBinY, paddedHighBinY, radialGapMaskBins);
0522   }
0523   result.second.reserve(radialGaps.size());
0524   for (const auto &gap : radialGaps)
0525   {
0526     result.second.push_back(0.5 * (gap[radial_gap_low_r] + gap[radial_gap_high_r]));
0527   }
0528 
0529   const long long totalBins = static_cast<long long>(nBinsX) * static_cast<long long>(nBinsY);
0530   const long long totalMaskedBins = largeGapMaskBins + laminationPhiMaskBins + radialGapMaskBins;
0531   std::cout << "Lamination cleaning for " << histogram->GetName() << ": radialGaps=" << radialGaps.size() << " laminationPhis=" << laminationPhis.size() << " maskedBins=" << totalMaskedBins << "/" << totalBins << " largeGap=" << largeGapMaskBins << " laminationPhi=" << laminationPhiMaskBins << " radialGap=" << radialGapMaskBins << std::endl;
0532 
0533   return result;
0534 }