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
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
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
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
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
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
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
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
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
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
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
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
0187 return lhs[radial_peak_r] > rhs[radial_peak_r];
0188 }
0189
0190 double effective_gap_mask_padding(double gapWidth)
0191 {
0192
0193 return std::min(radialGapMaskPaddingCm, radialGapMaskMaxPaddingFraction * gapWidth);
0194 }
0195
0196 std::pair<TH2 *, std::vector<double>> clean_laminations(TH2 *histogram)
0197 {
0198
0199
0200
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
0222
0223
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
0233
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
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
0313
0314 std::vector<std::array<double, 4>> radialGaps;
0315
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
0371
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
0429
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
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 }