File indexing completed on 2026-10-16 08:28:08
0001 #include "LaserClusterTruthMatcher.h"
0002
0003 #include <trackbase/LaserClusterContainer.h>
0004 #include <trackbase/LaserClusterContainerv1.h>
0005 #include <trackbase/LaserCluster.h>
0006 #include <trackbase/LaserClusterv5.h>
0007 #include <trackbase/TpcDefs.h>
0008 #include <trackbase/TrkrDefs.h>
0009
0010 #include <cdbobjects/CDBTTree.h>
0011
0012 #include <fun4all/Fun4AllReturnCodes.h>
0013 #include <phool/PHCompositeNode.h>
0014 #include <phool/getClass.h>
0015
0016
0017 #include <TCanvas.h>
0018 #include <TColor.h>
0019 #include <TFile.h>
0020 #include <TGraph.h>
0021 #include <TGraph2D.h>
0022 #include <TH1D.h>
0023 #include <TH2D.h>
0024 #include <TLine.h>
0025 #include <TMath.h>
0026 #include <TStyle.h>
0027 #include <TTree.h>
0028
0029 #include <algorithm>
0030 #include <cmath>
0031 #include <format>
0032 #include <iomanip>
0033 #include <iostream>
0034 #include <numeric>
0035 #include <set>
0036
0037
0038
0039
0040
0041
0042
0043
0044 namespace
0045 {
0046 constexpr double kPetalDefault = TMath::Pi() / 9.0;
0047
0048 struct Cluster
0049 {
0050 double R;
0051 double phi;
0052 double adc;
0053 TrkrDefs::cluskey key;
0054 };
0055
0056 struct Result
0057 {
0058 std::vector<double> grid;
0059 std::vector<double> density;
0060 std::vector<double> peakR;
0061 std::vector<double> bound;
0062 };
0063
0064 double layerPitch(double R)
0065 {
0066 if (R < 40.0){ return 0.625; }
0067 if (R < 60.0){ return 1.25; }
0068 return 1.125;
0069 }
0070
0071 double clusSigma(const Cluster &c, double sigma_ref, double adc_ref)
0072 {
0073 double s = sigma_ref * sqrt(adc_ref / std::max(c.adc, 1.0));
0074 s = std::max(s, 0.5 * layerPitch(c.R));
0075 s = std::min(s, 1.0);
0076 return s;
0077 }
0078
0079 Result findRows(const std::vector<Cluster> &cl,
0080 double sigma_ref = 0.15, double promfrac = 0.05,
0081 double rlo = 28.0, double rhi = 78.0, int ngrid = 4000)
0082 {
0083 Result out;
0084 if (cl.empty()){ return out; }
0085
0086 std::vector<double> adcs;
0087 adcs.reserve(cl.size());
0088 for (const auto &c : cl){ adcs.push_back(c.adc); }
0089 std::nth_element(adcs.begin(), adcs.begin() + adcs.size() / 2, adcs.end());
0090 const double adc_ref = std::max(adcs[adcs.size() / 2], 1.0);
0091
0092 const double dr = (rhi - rlo) / ngrid;
0093 out.grid.resize(ngrid);
0094 out.density.assign(ngrid, 0.0);
0095 for (int b = 0; b < ngrid; ++b){ out.grid[b] = rlo + (b + 0.5) * dr; }
0096
0097 for (const auto &c : cl)
0098 {
0099 if (c.R < rlo || c.R > rhi){ continue; }
0100 const double s = clusSigma(c, sigma_ref, adc_ref);
0101 const int lo = std::max(0, (int)((c.R - 4 * s - rlo) / dr));
0102 const int hi = std::min(ngrid - 1, (int)((c.R + 4 * s - rlo) / dr));
0103 for (int b = lo; b <= hi; ++b)
0104 {
0105 const double d = out.grid[b] - c.R;
0106 out.density[b] += exp(-0.5 * d * d / (s * s)) / s;
0107 }
0108 }
0109
0110 std::vector<int> pk;
0111 for (int i = 1; i < ngrid - 1; ++i)
0112 {
0113 if (out.density[i] <= out.density[i - 1] || out.density[i] < out.density[i + 1]){ continue; }
0114 double minL = out.density[i];
0115 for (int j = i - 1; j >= 0; --j)
0116 {
0117 if (out.density[j] > out.density[i]){ break; }
0118 minL = std::min(minL, out.density[j]);
0119 }
0120 double minR = out.density[i];
0121 for (int j = i + 1; j < ngrid; ++j)
0122 {
0123 if (out.density[j] > out.density[i]){ break; }
0124 minR = std::min(minR, out.density[j]);
0125 }
0126 if (out.density[i] - std::max(minL, minR) > promfrac * out.density[i]){ pk.push_back(i); }
0127 }
0128
0129 for (int i : pk){ out.peakR.push_back(out.grid[i]); }
0130
0131 for (size_t ir = 0; ir + 1 < pk.size(); ++ir)
0132 {
0133 int imin = pk[ir];
0134 for (int b = pk[ir]; b <= pk[ir + 1]; ++b)
0135 {
0136 if (out.density[b] < out.density[imin]){ imin = b; }
0137 }
0138 out.bound.push_back(out.grid[imin]);
0139 }
0140 return out;
0141 }
0142
0143 int rowOf(double R, const std::vector<double> &bound)
0144 {
0145 return std::upper_bound(bound.begin(), bound.end(), R) - bound.begin();
0146 }
0147
0148 std::vector<double> refine(const std::vector<Cluster> &cl, const Result &res)
0149 {
0150 const size_t n = res.peakR.size();
0151 std::vector<double> sw(n, 0.0);
0152 std::vector<double> swr(n, 0.0);
0153 for (const auto &c : cl)
0154 {
0155 if (c.R < res.grid.front() || c.R > res.grid.back()){ continue; }
0156 const int ir = rowOf(c.R, res.bound);
0157 sw[ir] += c.adc;
0158 swr[ir] += c.adc * c.R;
0159 }
0160 std::vector<double> R(n, 0.0);
0161 for (size_t i = 0; i < n; ++i){ R[i] = (sw[i] > 0.0) ? swr[i] / sw[i] : res.peakR[i]; }
0162 return R;
0163 }
0164
0165
0166 double foldPetal(double phi, double lo, double petal)
0167 {
0168 double p = fmod(phi - lo, petal);
0169 if (p < 0.0){ p += petal; }
0170 return lo + p;
0171 }
0172
0173 double circDiff(double a, double b, double petal)
0174 {
0175 double d = a - b;
0176 while (d > 0.5 * petal){ d -= petal; }
0177 while (d < -0.5 * petal){ d += petal; }
0178 return d;
0179 }
0180
0181 struct Stripes
0182 {
0183 std::vector<double> grid;
0184 std::vector<double> density;
0185 std::vector<double> peakPhi;
0186 };
0187
0188 std::map<int, TruthRowPattern> buildTruthRowPattern(CDBTTree &cdbttree, int side)
0189 {
0190 std::map<int, TruthRowPattern> truthRows;
0191 for(int row=0; row<32; row++)
0192 {
0193 std::vector<std::pair<int,double>> entries;
0194 for (int iphi = 0; iphi < 12; iphi++)
0195 {
0196 unsigned int truthIndex = (side ? 18 : 0)*10000 + (row*100) + iphi;
0197 double phiVal = cdbttree.GetDoubleValue(truthIndex, "truthPhi");
0198 if (std::isnan(phiVal)){ continue; }
0199 entries.emplace_back(iphi, phiVal);
0200 }
0201 std::sort(entries.begin(), entries.end(), [](const auto &a, const auto &b){ return a.second < b.second; });
0202
0203 TruthRowPattern truthRow;
0204 for (auto &[iphi, phiVal] : entries)
0205 {
0206 truthRow.stripePhi.push_back(phiVal);
0207 truthRow.stripeIPhi.push_back(iphi);
0208 }
0209 truthRows[row] = truthRow;
0210 }
0211 return truthRows;
0212 }
0213
0214
0215 constexpr double sigR = 0.10;
0216 constexpr double sigGap = 0.15;
0217 constexpr double stripeEdgeFloorFrac = 0.25;
0218 constexpr double mergeFrac = 0.3;
0219 constexpr double lamSearchGate = 0.04;
0220 constexpr int lamRowHalfWindow = 3;
0221 constexpr double lamMatchTol = 0.010;
0222 constexpr double lamMatchFrac = 0.7;
0223 constexpr int lamRecoverySearchRows = 8;
0224 constexpr double lamRecoveryMatchTol = 0.005;
0225 constexpr double lamRecoveryFloorFrac = 0.10;
0226 const double phiPetalLo[2] = {-TMath::Pi() / 18.0, 0.0};
0227
0228
0229
0230
0231
0232
0233 Stripes findStripes(const std::vector<Cluster> &clusters, double lo,
0234 double sigma_ref = 0.004,
0235 double sigma_min = 0.0015,
0236 int ngrid = 720, double petal = kPetalDefault,
0237 double recoDphi = kPetalDefault / 11,
0238 double edgeFloorFrac = 0.5)
0239 {
0240 Stripes out;
0241 if(clusters.empty()){ return out; }
0242
0243 std::vector<double> adcs;
0244 adcs.reserve(clusters.size());
0245 for(const auto &c : clusters) { adcs.push_back(c.adc); }
0246 std::nth_element(adcs.begin(), adcs.begin() + adcs.size() / 2, adcs.end());
0247 double adc_ref = std::max(adcs[adcs.size() / 2], 1.0);
0248
0249
0250 double dp = petal / ngrid;
0251 out.grid.resize(ngrid);
0252 out.density.assign(ngrid, 0.0);
0253 for(int b=0; b<ngrid; b++)
0254 {
0255 out.grid[b] = lo + (b+0.5) * dp;
0256 }
0257
0258 for(const auto &c : clusters)
0259 {
0260 const double p = foldPetal(c.phi, lo, petal);
0261
0262
0263
0264 double s = sigma_ref * sqrt(adc_ref / std::max(c.adc, 1.0));
0265 s = std::max(s, sigma_min);
0266 s = std::min(s, petal / 8.0);
0267
0268
0269 const int c0 = (int) ((p - lo) / dp);
0270
0271 const int half = (int) ceil(4.0 * s / dp);
0272 for(int k=-half; k<=half; k++)
0273 {
0274 const int b = (((c0 + k) % ngrid) + ngrid) % ngrid;
0275 const double d = circDiff(out.grid[b], p, petal);
0276 out.density[b] += exp(-0.5 * d * d / (s * s)) / s;
0277 }
0278 }
0279
0280
0281 auto dens = [&](int i){ return out.density[((i % ngrid) + ngrid) % ngrid]; };
0282
0283
0284 int maxCand = -1;
0285 double maxCont = 0.0;
0286 for(int i=(int)floor(ngrid/4.0); i<(int)ceil(3.0*ngrid/4.0); i++)
0287 {
0288 double cont = dens(i);
0289 if(cont > maxCont)
0290 {
0291 maxCont = cont;
0292 maxCand = i;
0293 }
0294 }
0295
0296 if(maxCand < 0){ return out; }
0297 std::vector<int> cand;
0298 cand.push_back(maxCand);
0299
0300
0301 int searchStep = (int) round(recoDphi / dp);
0302 int minSearch = (int) floor(0.8 * recoDphi / dp);
0303 int maxSearch = (int) ceil(1.2 * recoDphi / dp);
0304 int searchWidth = std::max(maxSearch - searchStep, searchStep - minSearch);
0305
0306 int nLoop = 0;
0307
0308
0309 while(true)
0310 {
0311 int prevIdx = cand.back();
0312 int j0 = prevIdx + searchStep - searchWidth;
0313 int j1 = std::min(prevIdx + searchStep + searchWidth, ngrid-1);
0314 if(j0 > j1){ break; }
0315
0316 int localCand = -1;
0317 double localMax = 0.0;
0318 for(int j=j0; j<=j1; j++)
0319 {
0320 double cont = dens(j);
0321 if(cont > localMax)
0322 {
0323 localMax = cont;
0324 localCand = j;
0325 }
0326 }
0327 if(localCand < 0 || nLoop > 20){ break; }
0328 cand.push_back(localCand);
0329 nLoop++;
0330 }
0331
0332 nLoop = 0;
0333
0334
0335 while(true)
0336 {
0337 int prevIdx = cand.front();
0338 int j0 = std::max(prevIdx - searchStep - searchWidth, 0);
0339 int j1 = prevIdx - searchStep + searchWidth;
0340 if(j0 > j1){ break; }
0341
0342 int localCand = -1;
0343 double localMax = 0.0;
0344 for(int j=j0; j<=j1; j++)
0345 {
0346 double cont = dens(j);
0347 if(cont > localMax)
0348 {
0349 localMax = cont;
0350 localCand = j;
0351 }
0352 }
0353 if(localCand < 0 || nLoop > 20){ break; }
0354 cand.insert(cand.begin(), localCand);
0355 nLoop++;
0356 }
0357
0358 std::sort(cand.begin(), cand.end());
0359
0360
0361 const int checkSamples = 8;
0362 auto isEdgeStable = [&](int idx)
0363 {
0364 for (int k = 1; k <= checkSamples; ++k)
0365 {
0366 if (dens(idx - k) > dens(idx - k + 1))
0367 {
0368 return false;
0369 }
0370 if (dens(idx + k) > dens(idx + k - 1))
0371 {
0372 return false;
0373 }
0374 }
0375 return true;
0376 };
0377
0378
0379 while (cand.size() > 1 && !isEdgeStable(cand.front())){ cand.erase(cand.begin()); }
0380 while (cand.size() > 1 && !isEdgeStable(cand.back())){ cand.pop_back(); }
0381
0382 auto isLocalMax = [&](int idx)
0383 {
0384 return dens(idx) >= dens(idx - 1) && dens(idx) >= dens(idx + 1);
0385 };
0386
0387 double medPeakHeight = 0.0;
0388 if(!cand.empty())
0389 {
0390 std::vector<double> candH;
0391 candH.reserve(cand.size());
0392 for(const auto &c : cand)
0393 {
0394 candH.push_back(dens(c));
0395 }
0396 std::nth_element(candH.begin(), candH.begin() + candH.size() / 2, candH.end());
0397 medPeakHeight = candH[candH.size() / 2];
0398 }
0399
0400
0401 auto scanForPeak = [&](int i0, int i1) -> int
0402 {
0403 int best = -1;
0404 double bestH = 0.0;
0405 for(int i=i0; i<=i1; i++)
0406 {
0407 if(!isLocalMax(i)){ continue; }
0408 if(dens(i) > bestH)
0409 {
0410 bestH = dens(i);
0411 best = i;
0412 }
0413 }
0414 return best;
0415 };
0416
0417
0418
0419
0420
0421
0422 auto scanRangeForPeaks = [&](int i0, int i1, bool prepend)
0423 {
0424 std::vector<std::pair<int,int>> ranges = { {i0,i1} };
0425 std::vector<int> found;
0426
0427 while(!ranges.empty())
0428 {
0429 auto [a, b] = ranges.back();
0430 ranges.pop_back();
0431 if(a > b){ continue; }
0432
0433 int best = scanForPeak(a, b);
0434 if(best < 0 || dens(best) <= edgeFloorFrac * medPeakHeight){ continue; }
0435
0436 found.emplace_back(best);
0437 ranges.emplace_back(a, best - 1);
0438 ranges.emplace_back(best + 1, b);
0439 }
0440
0441 std::sort(found.begin(), found.end());
0442 if(prepend){ cand.insert(cand.begin(), found.begin(), found.end()); }
0443 else{ cand.insert(cand.end(), found.begin(), found.end()); }
0444 };
0445
0446
0447
0448 if(!cand.empty())
0449 {
0450 if(cand.front() > 0){ scanRangeForPeaks(0, cand.front() - 1, true); }
0451 if(cand.back() < ngrid - 1){ scanRangeForPeaks(cand.back() + 1, ngrid - 1, false); }
0452 }
0453
0454
0455
0456
0457
0458
0459
0460
0461
0462
0463
0464 if (recoDphi > 0.0 && cand.size() > 1)
0465 {
0466 const int mergeGap = std::max(1, (int) std::round(mergeFrac * recoDphi / dp));
0467
0468 bool mergedAny = true;
0469 while (mergedAny && cand.size() > 1)
0470 {
0471 mergedAny = false;
0472
0473 std::vector<int> merged;
0474 merged.push_back(cand.front());
0475 for (size_t ci = 1; ci < cand.size(); ++ci)
0476 {
0477 if (cand[ci] - merged.back() < mergeGap)
0478 {
0479 if (dens(cand[ci]) > dens(merged.back())){ merged.back() = cand[ci]; }
0480 mergedAny = true;
0481 }
0482 else
0483 {
0484 merged.push_back(cand[ci]);
0485 }
0486 }
0487
0488
0489 if (merged.size() > 1)
0490 {
0491 const int wrapGap = (merged.front() + ngrid) - merged.back();
0492 if (wrapGap < mergeGap)
0493 {
0494 if (dens(merged.front()) > dens(merged.back())){ merged.erase(merged.end() - 1); }
0495 else{ merged.erase(merged.begin()); }
0496 mergedAny = true;
0497 }
0498 }
0499
0500 cand = std::move(merged);
0501 }
0502 }
0503
0504
0505 for(int i : cand)
0506 {
0507 out.peakPhi.push_back(out.grid[i]);
0508 }
0509 std::sort(out.peakPhi.begin(), out.peakPhi.end());
0510 return out;
0511 }
0512
0513
0514
0515
0516
0517
0518 int findLaminationNear(const std::vector<std::vector<double>> &peaksPerRow, int rowIdx, double edge, double searchGate,
0519 int rowHalfWindow, double matchTol, double matchFrac, double petal)
0520 {
0521 const auto &peaks = peaksPerRow[rowIdx];
0522 const int nrow = (int) peaksPerRow.size();
0523
0524 int bestIdx = -1;
0525 int bestMatches = -1;
0526 for(size_t idx=0; idx<peaks.size(); idx++)
0527 {
0528 if(fabs(circDiff(peaks[idx], edge, petal)) > searchGate){ continue; }
0529
0530 int nMatched = 0;
0531 int nChecked = 0;
0532 for(int j=std::max(0, rowIdx - rowHalfWindow);
0533 j<=std::min(nrow - 1, rowIdx + rowHalfWindow);
0534 j++)
0535 {
0536 if(j == rowIdx || peaksPerRow[j].empty()){ continue; }
0537 nChecked++;
0538 double best = 1e9;
0539 for(double q : peaksPerRow[j])
0540 {
0541 best = std::min(best, fabs(circDiff(peaks[idx], q, petal)));
0542 }
0543 if(best < matchTol){ nMatched++; }
0544 }
0545 if(nChecked == 0){ continue; }
0546 if((double) nMatched / nChecked >= matchFrac && nMatched > bestMatches)
0547 {
0548 bestMatches = nMatched;
0549 bestIdx = (int) idx;
0550 }
0551 }
0552 return bestIdx;
0553 }
0554
0555
0556
0557
0558
0559
0560
0561 struct LamEdgeSnapshot
0562 {
0563 std::vector<bool> confirmed;
0564 std::vector<double> pos;
0565 };
0566
0567 LamEdgeSnapshot snapshotLamEdge(const std::vector<std::vector<double>> &peaksPerRow, const std::vector<int> &edgeIdx)
0568 {
0569 LamEdgeSnapshot snap;
0570 int nrow = (int) peaksPerRow.size();
0571 snap.confirmed.assign(nrow, false);
0572 snap.pos.assign(nrow, 0.0);
0573 for(int i=0; i<nrow; i++)
0574 {
0575 if(edgeIdx[i] < 0){ continue; }
0576 snap.confirmed[i] = true;
0577 snap.pos[i] = peaksPerRow[i][edgeIdx[i]];
0578 }
0579 return snap;
0580 }
0581
0582
0583
0584
0585 std::pair<double, bool> predictLamPos(const LamEdgeSnapshot &snap, int row, int maxSearchRows, double petal)
0586 {
0587 int nrow = (int) snap.confirmed.size();
0588 int below = -1;
0589 int above = -1;
0590 for(int j=row-1; j>=std::max(0, row-maxSearchRows); j--)
0591 {
0592 if(snap.confirmed[j])
0593 {
0594 below = j;
0595 break;
0596 }
0597 }
0598 for(int j=row+1; j<=std::min(nrow-1, row+maxSearchRows); j++)
0599 {
0600 if(snap.confirmed[j])
0601 {
0602 above = j;
0603 break;
0604 }
0605 }
0606
0607 if(below >= 0 && above >= 0)
0608 {
0609 double frac = double(row - below) / (double)(above - below);
0610 return { snap.pos[below] + frac * circDiff(snap.pos[above], snap.pos[below], petal), true};
0611 }
0612 if(below >= 0){ return {snap.pos[below], true}; }
0613 if(above >= 0){ return {snap.pos[above], true}; }
0614 return {0.0, false};
0615 }
0616
0617
0618
0619 double medianPeakDensity(const Stripes &stripeRow, const std::vector<double> &peaks)
0620 {
0621 if(peaks.empty() || stripeRow.grid.size() < 2){ return 0.0; }
0622 const double dp = stripeRow.grid[1] - stripeRow.grid[0];
0623 std::vector<double> h;
0624 for(double p : peaks)
0625 {
0626 int idx = (int) std::round((p - stripeRow.grid[0]) / dp);
0627 idx = std::max(0, std::min((int) stripeRow.grid.size() - 1, idx));
0628 h.push_back(stripeRow.density[idx]);
0629 }
0630 std::nth_element(h.begin(), h.begin() + h.size() / 2, h.end());
0631 return h[h.size() / 2];
0632 }
0633
0634
0635
0636
0637
0638
0639
0640
0641 bool recoverLamination(std::vector<double> &peaks, std::vector<bool> &isLam, const Stripes &stripeRow,
0642 double predicted, double matchTol, double petal, double absFloor, double recoDphi)
0643 {
0644 for(size_t k=0; k<peaks.size(); k++)
0645 {
0646 if(fabs(circDiff(peaks[k], predicted, petal)) < matchTol)
0647 {
0648 isLam[k] = true;
0649 return true;
0650 }
0651 }
0652
0653 if(stripeRow.grid.size() < 2){ return false; }
0654 const double dp = stripeRow.grid[1] - stripeRow.grid[0];
0655 const int half = std::max(1, (int) (matchTol / dp));
0656 const int ngrid = (int) stripeRow.grid.size();
0657
0658 int c0 = -1;
0659 double bestD = 1e300;
0660 for(int b=0; b<ngrid; b++)
0661 {
0662 double d = fabs(circDiff(stripeRow.grid[b], predicted, petal));
0663 if(d < bestD)
0664 {
0665 bestD = d;
0666 c0 = b;
0667 }
0668 }
0669 if(c0 < 0){ return false; }
0670
0671 int bestBin = -1;
0672 double bestH = 0.0;
0673 for(int k=-half; k<=half; k++)
0674 {
0675 int b = ((c0 + k) % ngrid + ngrid) % ngrid;
0676 if(stripeRow.density[b] > bestH)
0677 {
0678 bestH = stripeRow.density[b];
0679 bestBin = b;
0680 }
0681 }
0682 if(bestBin < 0 || bestH < absFloor){ return false; }
0683
0684 double newPeakPos = stripeRow.grid[bestBin];
0685
0686
0687
0688
0689
0690
0691
0692 if (recoDphi > 0.0)
0693 {
0694 const double mergeTol = mergeFrac * recoDphi;
0695 for(size_t k=peaks.size(); k-- > 0;)
0696 {
0697
0698
0699 if(!isLam[k] && fabs(circDiff(peaks[k], newPeakPos, petal)) < mergeTol)
0700 {
0701 peaks.erase(peaks.begin() + k);
0702 isLam.erase(isLam.begin() + k);
0703 }
0704 }
0705 }
0706
0707 size_t insertAt = std::lower_bound(peaks.begin(), peaks.end(), newPeakPos) - peaks.begin();
0708
0709 peaks.insert(peaks.begin() + insertAt, newPeakPos);
0710 isLam.insert(isLam.begin() + insertAt, true);
0711 return true;
0712 }
0713
0714 struct Cost
0715 {
0716 double nMismatch{1e300};
0717 double gapChi2{1e300};
0718 double rChi2{1e300};
0719 };
0720
0721
0722 bool operator<(const Cost &a, const Cost &b)
0723 {
0724 if (a.nMismatch != b.nMismatch){ return a.nMismatch < b.nMismatch; }
0725 if (a.gapChi2 != b.gapChi2){ return a.gapChi2 < b.gapChi2; }
0726 return a.rChi2 < b.rChi2;
0727 }
0728
0729 Cost operator+(const Cost &a, const Cost &b)
0730 {
0731 return {a.nMismatch + b.nMismatch, a.gapChi2 + b.gapChi2, a.rChi2 + b.rChi2};
0732 }
0733
0734
0735 const Cost INF = {1e300, 1e300, 1e300};
0736
0737 struct MatchResult
0738 {
0739 std::vector<int> truthOf;
0740 std::vector<int> skipped;
0741 Cost dp{1e300, 1e300, 1e300};
0742 };
0743
0744 Cost pairCost(double recoR, int recoN, const TruthRow &t, double sigmaR)
0745 {
0746 Cost c{0.0, 0.0, 0.0};
0747 if(recoN > 0 && t.nstripes > 0)
0748 {
0749 double d = recoN - t.nstripes;
0750 c.nMismatch = d * d;
0751 }
0752 double dR = recoR - t.R;
0753 c.rChi2 = dR * dR / (sigmaR * sigmaR);
0754 return c;
0755 }
0756
0757
0758
0759
0760 inline double gapCost(double recoR_prev, double recoR_curr, double truthR_prev, double truthR_curr, double sigmaGap)
0761 {
0762 double recoGap = recoR_curr - recoR_prev;
0763 double truthGap = truthR_curr - truthR_prev;
0764 double d = recoGap - truthGap;
0765 return d * d / (sigmaGap * sigmaGap);
0766 }
0767
0768
0769
0770 void matchDP(const std::vector<double> &recoR, const std::vector<int> &recoN, const std::vector<TruthRow> &truth, double sigmaR, double sigmaGap, MatchResult &best, int verbosity)
0771 {
0772 const int nr = recoR.size();
0773 const int nt = truth.size();
0774 best = MatchResult();
0775 if(nr == 0 || nt == 0 || nr > nt)
0776 {
0777 if(verbosity){ std::cout << "rowmatch: " << nr << " reco rows vs " << nt << " truth rows -- cannot align" << std::endl; }
0778 return;
0779 }
0780
0781 std::vector<std::vector<Cost>> dp(nr, std::vector<Cost>(nt, INF));
0782 std::vector<std::vector<int>> prevJ(nr, std::vector<int>(nt, -1));
0783
0784 for(int j=0; j<nt; j++)
0785 {
0786 dp[0][j] = pairCost(recoR[0], recoN[0], truth[j], sigmaR);
0787 }
0788
0789 for(int i=1; i<nr; i++)
0790 {
0791 for(int j=i; j<nt; j++)
0792 {
0793 Cost matchP = pairCost(recoR[i], recoN[i], truth[j], sigmaR);
0794 for(int jp = i - 1; jp < j; jp++)
0795 {
0796 if(!(dp[i-1][jp] < INF)){ continue; }
0797 double gc = gapCost(recoR[i-1], recoR[i], truth[jp].R, truth[j].R, sigmaGap);
0798 Cost cand = dp[i-1][jp] + Cost{matchP.nMismatch, gc, matchP.rChi2};
0799 if(cand < dp[i][j])
0800 {
0801 dp[i][j] = cand;
0802 prevJ[i][j] = jp;
0803 }
0804 }
0805 }
0806 }
0807
0808 int bestJ = -1;
0809 Cost bestCost = INF;
0810 for(int j=nr-1; j<nt; j++)
0811 {
0812 if(dp[nr-1][j] < bestCost)
0813 {
0814 bestCost = dp[nr-1][j];
0815 bestJ = j;
0816 }
0817 }
0818
0819 if(bestJ < 0){ return; }
0820
0821 std::vector<int> truthOf(nr);
0822 int i = nr - 1;
0823 int j = bestJ;
0824 while(i>=0)
0825 {
0826 truthOf[i] = j;
0827 int pj = (i > 0 ? prevJ[i][j] : -1);
0828 i--;
0829 j = pj;
0830 }
0831
0832 std::vector<bool> used(nt, false);
0833 for(int t : truthOf){ used[t] = true; }
0834 std::vector<int> skipped;
0835 for(int t=0; t<nt; t++)
0836 {
0837 if(!used[t]){ skipped.push_back(t); }
0838 }
0839
0840 best = {truthOf, skipped, bestCost};
0841 return;
0842 }
0843
0844
0845
0846
0847 std::vector<int> alignPhiPeaksToTruth(const std::vector<double> &peaks, const std::vector<bool> &isLam, double dPhi, double lo, double petal, const TruthRowPattern &truthRowPattern)
0848 {
0849 std::vector<double> peaksToUse;
0850 std::vector<int> origIdx;
0851 double lamPhi = 0.0;
0852 bool haveLam = false;
0853 for(size_t i=0; i<isLam.size(); i++)
0854 {
0855 if(isLam[i])
0856 {
0857 lamPhi = peaks[i];
0858 haveLam = true;
0859 continue;
0860 }
0861 peaksToUse.push_back(peaks[i]);
0862 origIdx.push_back((int) i);
0863 }
0864
0865 std::vector<int> iphiAssignment(peaks.size(), -1);
0866 if(!haveLam || peaksToUse.empty()){ return iphiAssignment; }
0867
0868 for(auto &p : peaksToUse)
0869 {
0870 p = p - lamPhi + lo;
0871 while(p < lo){ p += petal; }
0872 while(p >= lo + petal){ p -= petal; }
0873 }
0874
0875 std::vector<std::size_t> phiSortIdx(peaksToUse.size());
0876 std::iota(phiSortIdx.begin(), phiSortIdx.end(), 0);
0877
0878 std::sort(phiSortIdx.begin(), phiSortIdx.end(), [&](std::size_t a, std::size_t b){ return peaksToUse[a] < peaksToUse[b]; });
0879
0880 if(peaksToUse.size() == truthRowPattern.stripePhi.size())
0881 {
0882 bool allGood = true;
0883 for(size_t p = 0; p+1 < peaksToUse.size(); p++)
0884 {
0885 double gap = peaksToUse[phiSortIdx[p+1]] - peaksToUse[phiSortIdx[p]];
0886 if(gap > 1.2 * dPhi || gap < 0.8 * dPhi)
0887 {
0888 allGood = false;
0889 break;
0890 }
0891 }
0892 if(allGood)
0893 {
0894 for(size_t p = 0; p < peaksToUse.size(); p++)
0895 {
0896 iphiAssignment[origIdx[phiSortIdx[p]]] = truthRowPattern.stripeIPhi[p];
0897 }
0898 }
0899 }
0900 return iphiAssignment;
0901 }
0902
0903 struct PetalCand
0904 {
0905 double dPhi;
0906 int petal;
0907 };
0908
0909 int nearestPeak(double foldedPhi, const std::vector<double> &peaks, double petal)
0910 {
0911 int best = 0;
0912 double bestD = 1e300;
0913 for(size_t k = 0; k<peaks.size(); k++)
0914 {
0915 double d = fabs(circDiff(foldedPhi, peaks[k], petal));
0916 if(d < bestD)
0917 {
0918 bestD = d;
0919 best = (int) k;
0920 }
0921 }
0922 return best;
0923 }
0924
0925 unsigned int encodeTruthIndex(int side, int petalIdx, int row, int stripeIdx)
0926 {
0927 int pp = (side == 0) ? petalIdx : petalIdx + 18;
0928 return (unsigned int) (pp*10000 + row*100 + stripeIdx);
0929 }
0930
0931 std::map<TrkrDefs::cluskey, std::pair<int,bool>> matchSide(const std::vector<Cluster>& clusters, CDBTTree &cdbttree, const std::map<int, TruthRowPattern> &truthRowPatterns, const std::vector<TruthRow> &truth, int side, std::string QABase, int verbosity)
0932 {
0933 const char *sname = side ? "North" : "South";
0934 const double lo = phiPetalLo[side];
0935 const double petal = kPetalDefault;
0936
0937 std::map<TrkrDefs::cluskey, std::pair<int,bool>> keyMatch;
0938 for(const auto &c : clusters)
0939 {
0940 keyMatch[c.key] = {-999,false};
0941 }
0942
0943 if (clusters.empty())
0944 {
0945 std::cout << "LaserClusterTruthMatcher - matchSide: no clusters found for side " << sname << std::endl;
0946 return keyMatch;
0947 }
0948
0949
0950 Result rowResult = findRows(clusters, 0.15, 0.05);
0951 std::vector<double> rowR = refine(clusters, rowResult);
0952 const int nrow = (int)rowR.size();
0953 if (nrow == 0)
0954 {
0955 std::cout << "LaserClusterTruthMatcher: found 0 rows for side " << sname << std::endl;
0956 return keyMatch;
0957 }
0958
0959
0960 std::vector<std::vector<Cluster>> rowClus(nrow);
0961 for (const auto &c : clusters)
0962 {
0963 rowClus[rowOf(c.R, rowResult.bound)].push_back(c);
0964 }
0965
0966 if(!QABase.empty())
0967 {
0968 const auto &palette = TColor::GetPalette();
0969 const Int_t nColors = palette.GetSize();
0970
0971 std::vector<TH1D *> hRADCrow(nrow);
0972 TCanvas *c1 = new TCanvas();
0973 gStyle->SetOptStat(0);
0974 TH1D *hRADC = new TH1D(std::format("hRADC_{}", sname).c_str(),
0975 std::format("R of {} ADC-Weighted Aggregated Clusters;R [cm]",
0976 sname).c_str(),
0977 400, 28, 78);
0978 for(int i=0; i<nrow; i++)
0979 {
0980 hRADCrow[i] = new TH1D(std::format("hRADC_{}_row{}", sname, i).c_str(),
0981 std::format("R of {} ADC-Weighted Aggregated Clusters;R [cm]",
0982 sname).c_str(),
0983 400, 28, 78);
0984 const Int_t code = palette.At(i * (nColors / std::max(nrow, 1)));
0985 hRADCrow[i]->SetLineColor(code);
0986 hRADCrow[i]->SetFillColor(code);
0987
0988 for(const auto& c : rowClus[i])
0989 {
0990 hRADCrow[i]->Fill(c.R, c.adc);
0991 }
0992
0993 hRADC->Add(hRADCrow[i]);
0994 }
0995
0996 hRADC->Draw();
0997 for(int i=0; i<nrow; i++){ hRADCrow[i]->Draw("HIST SAME"); }
0998
0999
1000 for (double b : rowResult.bound)
1001 {
1002 TLine *l = new TLine(b, 0, b, hRADC->GetMaximum());
1003 l->SetBit(kCanDelete);
1004 l->SetLineColor(kRed);
1005 l->Draw("same");
1006 }
1007 c1->SaveAs(std::format("{}_RPeaks_{}.pdf",QABase, sname).c_str());
1008
1009 delete hRADC;
1010 for(auto *hr : hRADCrow){ delete hr; }
1011 delete c1;
1012 }
1013
1014
1015
1016 std::vector<TH1D *> hDPhi(nrow);
1017 std::vector<double> recoDphi(nrow, -1.0);
1018
1019 for(int i=0; i<nrow; i++)
1020 {
1021 hDPhi[i] = new TH1D(std::format("hDPhi_{}_row{}",sname,i).c_str(),
1022 std::format("#Delta#phi, {} row {};#Delta#phi [rad]",sname, i).c_str(),
1023 1000, 0, petal);
1024
1025 std::sort(rowClus[i].begin(), rowClus[i].end(),
1026 [](const Cluster &a, const Cluster &b){ return a.phi < b.phi; });
1027
1028 for(int j=0; j<(int)rowClus[i].size(); j++)
1029 {
1030 const double dPhi = (j == (int) rowClus[i].size() - 1)
1031 ? rowClus[i][0].phi + 2*TMath::Pi() - rowClus[i][j].phi
1032 : rowClus[i][j+1].phi - rowClus[i][j].phi;
1033 hDPhi[i]->Fill(dPhi);
1034 }
1035 if(hDPhi[i]->GetEntries() > 20)
1036 {
1037 recoDphi[i] = hDPhi[i]->GetBinCenter(hDPhi[i]->GetMaximumBin());
1038 }
1039 }
1040
1041 if(!QABase.empty())
1042 {
1043 TCanvas *c1 = new TCanvas();
1044 c1->SaveAs(std::format("{}_dPhi_{}.pdf[",QABase, sname).c_str());
1045 for(int i=0; i<nrow; i++)
1046 {
1047 c1->Clear();
1048 hDPhi[i]->Draw();
1049 c1->SaveAs(std::format("{}_dPhi_{}.pdf",QABase, sname).c_str());
1050 }
1051 c1->SaveAs(std::format("{}_dPhi_{}.pdf]",QABase, sname).c_str());
1052
1053 delete c1;
1054 }
1055
1056 for(auto *hp : hDPhi){ delete hp; }
1057
1058
1059 std::vector<Stripes> stripes(nrow);
1060 std::vector<std::vector<double>> peaksPerRow(nrow);
1061
1062 for(int i=0; i<nrow; i++)
1063 {
1064
1065
1066 if(recoDphi[i] <= 0.0)
1067 {
1068 if(verbosity)
1069 {
1070 std::cout << " row " << i << ": no stripe spacing (" << rowClus[i].size()
1071 << " clusters), skipping stripe finding" << std::endl;
1072 }
1073 continue;
1074 }
1075 stripes[i] = findStripes(rowClus[i], lo, 0.004, 0.0015, 720,
1076 petal, recoDphi[i], stripeEdgeFloorFrac);
1077 peaksPerRow[i] = stripes[i].peakPhi;
1078 }
1079
1080
1081 std::vector<std::vector<bool>> isLamRow(nrow);
1082 std::vector<int> recoN(nrow, 0);
1083 std::vector<int> lamIdxPerRow(nrow, -1);
1084
1085 for(int i=0; i<nrow; i++)
1086 {
1087 isLamRow[i] = std::vector<bool>(peaksPerRow[i].size(), false);
1088
1089
1090
1091 int lamIdx = findLaminationNear(peaksPerRow, i, lo, lamSearchGate, lamRowHalfWindow,
1092 lamMatchTol, lamMatchFrac, petal);
1093 if (lamIdx >= 0){ isLamRow[i][lamIdx] = true; }
1094 lamIdxPerRow[i] = lamIdx;
1095
1096 recoN[i] = (int) std::count(isLamRow[i].begin(), isLamRow[i].end(), false);
1097
1098 if(verbosity > 1)
1099 {
1100 std::cout << " row " << std::setw(2) << i
1101 << " R = " << std::setw(8) << rowR[i]
1102 << " clusters " << std::setw(5) << (int) rowClus[i].size()
1103 << " spacing " << std::setw(10) << recoDphi[i]
1104 << " peaks " << std::setw(3) << (int) peaksPerRow[i].size()
1105 << " stripes " << std::setw(3) << recoN[i]
1106 << " lam " << ((int) peaksPerRow[i].size() - recoN[i])
1107 << std::endl;
1108 }
1109 }
1110
1111
1112
1113 auto snapLam = snapshotLamEdge(peaksPerRow, lamIdxPerRow);
1114
1115 for(int i=0; i<nrow; i++)
1116 {
1117 if(lamIdxPerRow[i] < 0)
1118 {
1119 auto [pred, have] = predictLamPos(snapLam, i, lamRecoverySearchRows, petal);
1120 if(have)
1121 {
1122 double floor = lamRecoveryFloorFrac * medianPeakDensity(stripes[i], peaksPerRow[i]);
1123 const bool recovered = recoverLamination(peaksPerRow[i], isLamRow[i], stripes[i], pred,
1124 lamRecoveryMatchTol, petal, floor, recoDphi[i]);
1125 if(verbosity)
1126 {
1127 std::cout << " row " << i << " lamination "
1128 << (recovered ? "recovered" : "NOT recovered")
1129 << " near predicted " << pred << std::endl;
1130 }
1131 }
1132 }
1133 recoN[i] = std::count(isLamRow[i].begin(), isLamRow[i].end(), false);
1134 }
1135
1136 if(!QABase.empty())
1137 {
1138 TCanvas *c1 = new TCanvas();
1139 gStyle->SetOptStat(0);
1140 c1->SaveAs(std::format("{}_phiPeaks_{}.pdf[",QABase, sname).c_str());
1141 for(int i=0; i<nrow; i++)
1142 {
1143 if(stripes[i].grid.empty()){ continue; }
1144 TGraph *gr = new TGraph(stripes[i].grid.size(), stripes[i].grid.data(), stripes[i].density.data());
1145 gr->SetTitle(std::format("{} row {} (R = {:.2f}, spacing = {:.4f}, stripes = {});"
1146 "folded #phi [rad];density",
1147 sname, i, rowR[i], recoDphi[i], recoN[i]).c_str());
1148 gr->SetBit(kCanDelete);
1149 gr->GetXaxis()->SetRangeUser(lo, lo+petal);
1150 gr->SetMarkerStyle(20);
1151 gr->SetMarkerSize(0.5);
1152 gr->Draw("ALP");
1153 for (size_t ip = 0; ip < peaksPerRow[i].size(); ip++)
1154 {
1155 const double p = peaksPerRow[i][ip];
1156 TLine *l = new TLine(p, 0, p, gr->GetHistogram()->GetMaximum());
1157 l->SetBit(kCanDelete);
1158 l->SetLineColor(isLamRow[i][ip] ? kRed : kBlue);
1159 l->Draw("same");
1160 }
1161 c1->SaveAs(std::format("{}_phiPeaks_{}.pdf",QABase, sname).c_str());
1162 }
1163 c1->SaveAs(std::format("{}_phiPeaks_{}.pdf]",QABase, sname).c_str());
1164 delete c1;
1165 }
1166
1167
1168 if(truth.empty())
1169 {
1170 if(verbosity){ std::cout << "no truth table for " << sname << ", skipping matching" << std::endl; }
1171 return keyMatch;
1172 }
1173
1174 MatchResult best;
1175 matchDP(rowR, recoN, truth, sigR, sigGap, best, verbosity);
1176 if(best.truthOf.empty())
1177 {
1178 if(verbosity){ std::cout << "no good matching found for " << sname << ", returning without matches" << std::endl; }
1179 return keyMatch;
1180 }
1181
1182 if(verbosity > 1)
1183 {
1184 std::cout << "--- " << sname << " truth matching ---" << std::endl;
1185 std::cout << " best nMismatch " << best.dp.nMismatch << " (primary)" << std::endl;
1186 std::cout << " best gapChi2 " << best.dp.gapChi2 << " (spacing tiebreak)" << std::endl;
1187 std::cout << " best rChi2 " << best.dp.rChi2 << " (last resort)" << std::endl;
1188
1189 std::cout << " unmatched truth rows:";
1190 if (best.skipped.empty()){ std::cout << " none"; }
1191 for (int t : best.skipped)
1192 {
1193 std::cout << " " << t << " (R=" << truth[t].R << ")";
1194 }
1195 std::cout << std::endl;
1196
1197 for (int i = 0; i < nrow; i++)
1198 {
1199 const TruthRow &t = truth[best.truthOf[i]];
1200 const double dR = rowR[i] - t.R;
1201 std::cout << " reco " << std::setw(2) << i
1202 << " -> truth " << std::setw(2) << best.truthOf[i]
1203 << " dR = " << std::setw(10) << dR;
1204 if (recoDphi[i] > 0.0 && t.dphi > 0.0)
1205 {
1206 std::cout << " dphi ratio = " << recoDphi[i] / t.dphi;
1207 }
1208 if (recoN[i] > 0 && t.nstripes > 0)
1209 {
1210 std::cout << " stripes " << recoN[i] << " vs " << t.nstripes;
1211 }
1212 std::cout << std::endl;
1213 }
1214 }
1215
1216
1217 std::vector<std::vector<int>> iphiAssignments;
1218 int nRowsFullyAligned = 0;
1219 int nRowsWithRealPeaks = 0;
1220 for(int i=0; i<nrow; i++)
1221 {
1222 std::vector<int> iphiAssignment = alignPhiPeaksToTruth(peaksPerRow[i], isLamRow[i], recoDphi[i],
1223 lo, petal, truthRowPatterns.at(best.truthOf[i]));
1224
1225 bool haveRealPeak = false;
1226 bool allAssigned = true;
1227 for(size_t k=0; k<isLamRow[i].size(); k++)
1228 {
1229 if(isLamRow[i][k]){ continue; }
1230 haveRealPeak = true;
1231 if(iphiAssignment[k] < 0){ allAssigned = false; }
1232 }
1233 if(haveRealPeak)
1234 {
1235 nRowsWithRealPeaks++;
1236 if(allAssigned){ nRowsFullyAligned++; }
1237 }
1238
1239 if(verbosity > 1)
1240 {
1241 std::cout << " row " << std::setw(3) << i << " " << std::setw(8) << "peak" << std::setw(12) << "assignment" << std::endl;
1242 for (int j = 0; j < (int) peaksPerRow[i].size(); j++)
1243 {
1244 std::cout << " " << std::setw(8) << peaksPerRow[i][j] << std::setw(12) << iphiAssignment[j] << std::endl;
1245 }
1246 }
1247 iphiAssignments.push_back(iphiAssignment);
1248 }
1249
1250 if(verbosity){ std::cout << " phi alignment: " << nRowsFullyAligned << "/" << nRowsWithRealPeaks << " rows fully assigned" << std::endl; }
1251
1252
1253
1254
1255
1256 std::vector<int> clusterRow(clusters.size(), -1);
1257 std::vector<int> clusterIphi(clusters.size(), -1);
1258 std::vector<bool> clusterIsLam(clusters.size(), false);
1259 std::vector<std::vector<PetalCand>> clusterPetalCands(clusters.size());
1260
1261 for(size_t ci = 0; ci < clusters.size(); ci++)
1262 {
1263 const auto &c = clusters[ci];
1264 const int row = rowOf(c.R, rowResult.bound);
1265 clusterRow[ci] = row;
1266
1267 if(peaksPerRow[row].empty()){ continue; }
1268 int peak = nearestPeak(foldPetal(c.phi, lo, petal), peaksPerRow[row], petal);
1269 if(isLamRow[row][peak])
1270 {
1271 clusterIsLam[ci] = true;
1272 clusterIphi[ci] = -1;
1273 continue;
1274 }
1275
1276 const int iphi = iphiAssignments[row][peak];
1277 clusterIphi[ci] = iphi;
1278 if(iphi < 0){ continue; }
1279
1280 std::vector<PetalCand> cands;
1281 cands.reserve(18);
1282 for(int p=0; p<18; p++)
1283 {
1284 unsigned int currentIndex = encodeTruthIndex(side, p, best.truthOf[row], iphi);
1285 double candTruthPhi = cdbttree.GetDoubleValue(currentIndex, "truthPhi");
1286 double dPhi = std::abs(circDiff(c.phi, candTruthPhi, 2.0*TMath::Pi()));
1287 cands.push_back({dPhi, p});
1288 }
1289 std::sort(cands.begin(), cands.end(), [](const PetalCand &a, const PetalCand &b){ return a.dPhi < b.dPhi; });
1290 clusterPetalCands[ci] = std::move(cands);
1291 }
1292
1293 std::map<std::pair<int,int>, std::vector<int>> groups;
1294 for(size_t ci = 0; ci < clusters.size(); ci++)
1295 {
1296 if(clusterIphi[ci] < 0){ continue; }
1297 groups[{clusterRow[ci], clusterIphi[ci]}].push_back((int) ci);
1298 }
1299
1300 std::vector<int> resolvedPetal(clusters.size(), -1);
1301 for(auto &[key, members] : groups)
1302 {
1303 std::sort(members.begin(), members.end(), [&](int a, int b)
1304 {
1305 return clusters[a].adc > clusters[b].adc;
1306 });
1307
1308 std::set<int> takenPetals;
1309 for(int ci : members)
1310 {
1311 for(const auto &c : clusterPetalCands[ci])
1312 {
1313 if(c.dPhi > petal / 2.0){ break; }
1314 if(takenPetals.contains(c.petal)){ continue; }
1315 resolvedPetal[ci] = c.petal;
1316 takenPetals.insert(c.petal);
1317 break;
1318 }
1319 }
1320 }
1321
1322 for(int ci = 0; ci<(int)clusters.size(); ci++)
1323 {
1324 if (clusterIsLam[ci])
1325 {
1326 keyMatch[clusters[ci].key] = {-999, true};
1327 }
1328 else if (clusterIphi[ci] < 0)
1329 {
1330 keyMatch[clusters[ci].key] = {-999, false};
1331 }
1332 else if (resolvedPetal[ci] >= 0)
1333 {
1334 unsigned int matchedIndex = encodeTruthIndex(side, resolvedPetal[ci], best.truthOf[clusterRow[ci]], clusterIphi[ci]);
1335 keyMatch[clusters[ci].key] = {matchedIndex, false};
1336 }
1337 }
1338 return keyMatch;
1339 }
1340
1341 struct RowPoints
1342 {
1343 std::vector<double> phi;
1344 std::vector<double> dR;
1345 std::vector<double> dPhi;
1346 std::vector<double> adc;
1347 double recoRow{0};
1348 };
1349
1350 inline double wrapTwoPi(double phi)
1351 {
1352 phi = std::fmod(phi, 2*TMath::Pi());
1353 return (phi < 0) ? phi + 2*TMath::Pi() : phi;
1354 }
1355
1356
1357
1358
1359 double localLinear(const std::vector<double>& phi, const std::vector<double>& y,
1360 const std::vector<double>& w, double phi0, double h, double& wSum)
1361 {
1362 phi0 = wrapTwoPi(phi0);
1363
1364 double S0=0;
1365 double S1=0;
1366 double S2=0;
1367 double T0=0;
1368 double T1=0;
1369 for (size_t k = 0; k < phi.size(); ++k)
1370 {
1371 double d = phi[k] - phi0;
1372 if (d > TMath::Pi()){ d -= 2*TMath::Pi(); }
1373 else if (d < -TMath::Pi()){ d += 2*TMath::Pi(); }
1374 if (std::abs(d) > 4*h){ continue; }
1375
1376 const double K = w[k] * std::exp(-0.5*d*d/(h*h));
1377 S0 += K;
1378 S1 += K*d;
1379 S2 += K*d*d;
1380 T0 += K*y[k];
1381 T1 += K*d*y[k];
1382 }
1383 wSum = S0;
1384 if (S0 <= 0){ return NAN; }
1385 const double det = S0*S2 - S1*S1;
1386 if (det < 1e-12 * S0*S0*h*h){ return T0/S0; }
1387 return (S2*T0 - S1*T1) / det;
1388 }
1389
1390 void fill_guarding_bins(TpcDistortionCorrectionContainer *dcc)
1391 {
1392 for(int side=0; side<2; side++)
1393 {
1394 for(const auto& h : {dcc->m_hDRint[side], dcc->m_hDPint[side], dcc->m_hentries[side]})
1395 {
1396
1397
1398
1399
1400
1401
1402 const auto phibins = h->GetNbinsX();
1403 const auto rbins = h->GetNbinsY();
1404 for (int ir = 0; ir < rbins; ++ir)
1405 {
1406
1407 h->SetBinContent(1, ir + 1, h->GetBinContent(phibins - 1, ir + 1));
1408 h->SetBinError(1, ir + 1, h->GetBinError(phibins - 1, ir + 1));
1409
1410
1411 h->SetBinContent(phibins, ir + 1, h->GetBinContent(2, ir + 1));
1412 h->SetBinError(phibins, ir + 1, h->GetBinError(2, ir + 1));
1413 }
1414 }
1415 }
1416 }
1417
1418 }
1419
1420 LaserClusterTruthMatcher::LaserClusterTruthMatcher(const std::string &name)
1421 : SubsysReco(name)
1422 {
1423 }
1424
1425 int LaserClusterTruthMatcher::getNodes(PHCompositeNode *topNode)
1426 {
1427 m_laserClusterContainer = findNode::getClass<LaserClusterContainer>(topNode, m_laserClusterNodeName);
1428 if (!m_laserClusterContainer)
1429 {
1430 std::cout << PHWHERE << " no LaserClusterContainer named " << m_laserClusterNodeName
1431 << " on the node tree" << std::endl;
1432 return Fun4AllReturnCodes::ABORTRUN;
1433 }
1434
1435 PHNodeIterator iter(topNode);
1436
1437
1438 PHCompositeNode *dstNode = dynamic_cast<PHCompositeNode *>(iter.findFirst("PHCompositeNode", "DST"));
1439 if (!dstNode)
1440 {
1441 std::cout << PHWHERE << "DST Node missing, doing nothing." << std::endl;
1442 return Fun4AllReturnCodes::ABORTRUN;
1443 }
1444
1445 auto *laserClusterContainerOut = findNode::getClass<LaserClusterContainer>(dstNode, m_laserClusterNodeNameOut);
1446 if (!laserClusterContainerOut)
1447 {
1448 PHNodeIterator dstiter(dstNode);
1449 PHCompositeNode *DetNode =
1450 dynamic_cast<PHCompositeNode *>(dstiter.findFirst("PHCompositeNode", "TRKR"));
1451 if (!DetNode)
1452 {
1453 DetNode = new PHCompositeNode("TRKR");
1454 dstNode->addNode(DetNode);
1455 }
1456
1457 laserClusterContainerOut = new LaserClusterContainerv1;
1458 PHIODataNode<PHObject> *LaserClusterContainerNodeOut =
1459 new PHIODataNode<PHObject>(laserClusterContainerOut, m_laserClusterNodeNameOut, "PHObject");
1460 DetNode->addNode(LaserClusterContainerNodeOut);
1461 }
1462
1463 m_laserClusterHelper.set_useZ(false);
1464 m_laserClusterHelper.set_useDouble(true);
1465 m_laserClusterHelper.set_useGlobal(m_useGlobal);
1466 m_laserClusterHelper.set_useGarfield(m_usePHGarfieldDistortions);
1467 if(m_usePHGarfieldDistortions)
1468 {
1469 m_laserClusterHelper.set_garfield_cmvoltage(m_garfield_cmvoltage);
1470 m_laserClusterHelper.set_garfield_zerofield(m_garfield_zerofield);
1471 if(m_manual_garfield_keffside0){ m_laserClusterHelper.set_garfield_keffside0(m_garfield_keffside0); }
1472 if(m_manual_garfield_keffside1){ m_laserClusterHelper.set_garfield_keffside1(m_garfield_keffside1); }
1473 m_laserClusterHelper.set_garfield_stepns(m_garfield_stepns);
1474 }
1475 m_laserClusterHelper.loadNodes(topNode);
1476
1477 const float phiMin = m_phiMin - (m_phiMax - m_phiMin) / m_phibins;
1478 const float phiMax = m_phiMax + (m_phiMax - m_phiMin) / m_phibins;
1479
1480 const float rMin = m_rMin - (m_rMax - m_rMin) / m_rbins;
1481 const float rMax = m_rMax + (m_rMax - m_rMin) / m_rbins;
1482
1483 const std::array<const std::string, 2> extension = {{"_negz", "_posz"}};
1484
1485 m_dcc_out = new TpcDistortionCorrectionContainer();
1486 m_dcc_out->m_dimensions = 2;
1487
1488
1489 for (int i = 0; i < 2; ++i)
1490 {
1491 delete m_dcc_out->m_hDPint[i];
1492 m_dcc_out->m_hDPint[i] = new TH2F(std::format("hIntDistortionP{}", extension[i]).c_str(), std::format("hIntDistortionP{}", extension[i]).c_str(), m_phibins + 2, phiMin, phiMax, m_rbins + 2, rMin, rMax);
1493 delete m_dcc_out->m_hDRint[i];
1494 m_dcc_out->m_hDRint[i] = new TH2F(std::format("hIntDistortionR{}", extension[i]).c_str(), std::format("hIntDistortionR{}", extension[i]).c_str(), m_phibins + 2, phiMin, phiMax, m_rbins + 2, rMin, rMax);
1495 delete m_dcc_out->m_hDZint[i];
1496 m_dcc_out->m_hDZint[i] = new TH2F(std::format("hIntDistortionZ{}", extension[i]).c_str(), std::format("hIntDistortionZ{}", extension[i]).c_str(), m_phibins + 2, phiMin, phiMax, m_rbins + 2, rMin, rMax);
1497 delete m_dcc_out->m_hentries[i];
1498 m_dcc_out->m_hentries[i] = new TH2I(std::format("hEntries{}", extension[i]).c_str(), std::format("hEntries{}", extension[i]).c_str(), m_phibins + 2, phiMin, phiMax, m_rbins + 2, rMin, rMax);
1499 }
1500
1501
1502 return Fun4AllReturnCodes::EVENT_OK;
1503 }
1504
1505 int LaserClusterTruthMatcher::InitRun(PHCompositeNode *topNode)
1506 {
1507
1508
1509 delete m_cdbttree;
1510 m_cdbttree = new CDBTTree(m_truthFile);
1511 m_cdbttree->LoadCalibrations();
1512
1513 for(int side=0; side<2; side++)
1514 {
1515 m_truthRowPatterns[side] = buildTruthRowPattern(*m_cdbttree, side);
1516 m_truthRows[side].clear();
1517 for(int row=0; row<(int)m_truthRowPatterns[side].size(); row++)
1518 {
1519 TruthRow newRow;
1520 unsigned int truthIndex = (side ? 18 : 0)*10000 + (row*100) + 0;
1521 newRow.R = m_cdbttree->GetDoubleValue(truthIndex, "truthR");
1522 const auto &sp = m_truthRowPatterns[side][row].stripePhi;
1523 newRow.dphi = (sp.size() >= 2) ? sp[1] - sp[0] : -1.0;
1524 newRow.nstripes = static_cast<int>(sp.size());
1525
1526 m_truthRows[side].push_back(newRow);
1527 }
1528 }
1529
1530 return getNodes(topNode);
1531 }
1532
1533 int LaserClusterTruthMatcher::process_event(PHCompositeNode *topNode)
1534 {
1535
1536
1537
1538
1539
1540 LaserClusterContainer *laserClusterContainerOut = findNode::getClass<LaserClusterContainer>(topNode, m_laserClusterNodeNameOut);
1541 if (!laserClusterContainerOut)
1542 {
1543 std::cout << PHWHERE << " no output LaserClusterContainer named " << m_laserClusterNodeNameOut
1544 << " on the node tree" << std::endl;
1545 return Fun4AllReturnCodes::ABORTRUN;
1546 }
1547
1548
1549 std::vector<Cluster> clusters[2]{};
1550
1551 auto clusrange = m_laserClusterContainer->getClusters();
1552 for (auto cmitr = clusrange.first; cmitr != clusrange.second; ++cmitr)
1553 {
1554 const auto &[cmkey, cmclus_orig] = *cmitr;
1555 LaserCluster *cmclus = cmclus_orig;
1556
1557 if (auto *outClus = dynamic_cast<LaserCluster *>(cmclus->CloneMe()))
1558 {
1559 outClus->setTruthIndex(-999);
1560 outClus->setIsLamination(false);
1561 laserClusterContainerOut->addClusterSpecifyKey(cmkey, outClus);
1562 }
1563
1564 int side = TpcDefs::getSide(cmkey);
1565
1566 Acts::Vector3 pos;
1567 if(m_usePHGarfieldDistortions){ pos = m_laserClusterHelper.getClusterCentroidWithPHGarfield(cmclus); }
1568 else{ pos = m_laserClusterHelper.getClusterCentroid(cmclus); }
1569 if(pos.hasNaN())
1570 {
1571 continue;
1572 }
1573
1574 double R = sqrt(pos[0]*pos[0] + pos[1]*pos[1]);
1575 double phi = atan2(pos[1], pos[0]);
1576
1577 clusters[side].push_back({R, phi, cmclus->getAdcDouble(), cmkey});
1578 }
1579
1580 TFile *distortionOutputfile = new TFile(m_distortionOutputfile.c_str(), "RECREATE");
1581 TGraph2D *gr_dR{nullptr};
1582 TGraph2D *gr_dPhi{nullptr};
1583 TGraph *gr_recoPoints{nullptr};
1584 TGraph *gr_truthPoints{nullptr};
1585
1586 TGraph2D *gr_dR_toInterp{nullptr};
1587 TGraph2D *gr_dPhi_toInterp{nullptr};
1588
1589 TGraph *gR{nullptr};
1590 TGraph *gP{nullptr};
1591
1592
1593 TTree *QATree = nullptr;
1594 double recoR = 0.0;
1595 double recoPhi = 0.0;
1596 double truthR = 0.0;
1597 double truthPhi = 0.0;
1598 bool sideVar = false;
1599 bool lamVar = false;
1600 int truthIndex = 0;
1601 if(!m_QABase.empty())
1602 {
1603 QATree = new TTree("QATree","Matching QA TTree");
1604 QATree->Branch("recoR",&recoR);
1605 QATree->Branch("recoPhi",&recoPhi);
1606 QATree->Branch("truthR",&truthR);
1607 QATree->Branch("truthPhi",&truthPhi);
1608 QATree->Branch("side",&sideVar);
1609 QATree->Branch("isLam",&lamVar);
1610 QATree->Branch("truthIndex",&truthIndex);
1611 }
1612
1613
1614 for(int side=0; side<2; side++)
1615 {
1616 gr_dR = new TGraph2D();
1617 gr_dPhi = new TGraph2D();
1618 gr_recoPoints = new TGraph();
1619 gr_truthPoints = new TGraph();
1620
1621 std::map<int, RowPoints> rowPts;
1622
1623 Result rowResult = findRows(clusters[side], 0.15, 0.05);
1624 std::vector<double> rowR = refine(clusters[side], rowResult);
1625
1626 std::map<TrkrDefs::cluskey, std::pair<int,bool>> matched = matchSide(clusters[side], *m_cdbttree, m_truthRowPatterns[side], m_truthRows[side], side, m_QABase, Verbosity());
1627 for(auto &[key, matchedIndex] : matched)
1628 {
1629 LaserCluster *clus = laserClusterContainerOut->findCluster(key);
1630 if(clus)
1631 {
1632 clus->setTruthIndex(matchedIndex.first);
1633 clus->setIsLamination(matchedIndex.second);
1634
1635 int matchedTruthRow = (matchedIndex.first - (10000*(matchedIndex.first / 10000)))/100;
1636
1637 Acts::Vector3 pos;
1638 if(m_usePHGarfieldDistortions){ pos = m_laserClusterHelper.getClusterCentroidWithPHGarfield(clus); }
1639 else{ pos = m_laserClusterHelper.getClusterCentroid(clus); }
1640 if(pos.hasNaN())
1641 {
1642 continue;
1643 }
1644
1645 recoR = sqrt(pos[0]*pos[0] + pos[1]*pos[1]);
1646 recoPhi = atan2(pos[1], pos[0]);
1647 if(recoPhi < 0){ recoPhi += 2*TMath::Pi(); }
1648 if(matchedIndex.first >= 0)
1649 {
1650 truthR = m_cdbttree->GetDoubleValue(matchedIndex.first, "truthR");
1651 truthPhi = m_cdbttree->GetDoubleValue(matchedIndex.first, "truthPhi");
1652 }
1653 else
1654 {
1655 truthR = -999;
1656 truthPhi = -999;
1657 }
1658 if(truthPhi < 0){ truthPhi += 2*TMath::Pi(); }
1659
1660 int recoRow = rowOf(recoR, rowResult.bound);
1661
1662
1663 if(!m_QABase.empty())
1664 {
1665 sideVar = (bool)side;
1666 lamVar = matchedIndex.second;
1667 truthIndex = matchedIndex.first;
1668 QATree->Fill();
1669 }
1670
1671
1672 double dR = recoR - truthR;
1673 double dPhi = recoPhi - truthPhi;
1674 if(dPhi > TMath::Pi()){ dPhi -= 2*TMath::Pi(); }
1675 else if(dPhi < -TMath::Pi()){ dPhi += 2*TMath::Pi(); }
1676 if(!matchedIndex.second && matchedIndex.first >= 0 && matchedTruthRow != 23 && matchedTruthRow != 15)
1677 {
1678
1679 auto &rp = rowPts[matchedTruthRow];
1680 rp.phi.push_back(recoPhi);
1681 rp.dR.push_back(dR);
1682 rp.dPhi.push_back(dPhi);
1683 rp.adc.push_back(1.0);
1684
1685 rp.recoRow = rowR[recoRow];
1686
1687 gr_dR->AddPoint(recoPhi, recoR, dR);
1688 gr_dPhi->AddPoint(recoPhi, recoR, dPhi);
1689 gr_recoPoints->AddPoint(recoPhi, recoR);
1690 gr_truthPoints->AddPoint(truthPhi, truthR);
1691
1692 if(recoPhi < TMath::Pi()/12)
1693 {
1694 gr_dR->AddPoint(recoPhi+2*TMath::Pi(), recoR, dR);
1695 gr_dPhi->AddPoint(recoPhi+2*TMath::Pi(), recoR, dPhi);
1696 }
1697 else if(recoPhi > 23*TMath::Pi()/12)
1698 {
1699 gr_dR->AddPoint(recoPhi-2*TMath::Pi(), recoR, dR);
1700 gr_dPhi->AddPoint(recoPhi-2*TMath::Pi(), recoR, dPhi);
1701 }
1702
1703 }
1704 }
1705 }
1706
1707 const double hCm = 3.0;
1708 const double minW = 2.0;
1709
1710
1711 gr_dR_toInterp = (TGraph2D*)gr_dR->Clone();
1712 gr_dPhi_toInterp = (TGraph2D*)gr_dPhi->Clone();
1713
1714 for(int i=2; i<=m_dcc_out->m_hDRint[side]->GetNbinsX()-1; i++)
1715 {
1716 const double phiVal = m_dcc_out->m_hDRint[side]->GetXaxis()->GetBinCenter(i);
1717
1718 std::vector<double> Rs;
1719 std::vector<double> dRs;
1720 std::vector<double> dPs;
1721 for (auto &[row, rp] : rowPts)
1722 {
1723 double wS = 0.0;
1724 const double binW = m_dcc_out->m_hDRint[side]->GetXaxis()->GetBinWidth(1);
1725 const double h = std::max(hCm / rp.recoRow, 0.5 * binW);
1726 const double vR = localLinear(rp.phi, rp.dR, rp.adc, phiVal, h, wS);
1727 if (std::isnan(vR) || wS < minW){ continue; }
1728 const double vP = localLinear(rp.phi, rp.dPhi, rp.adc, phiVal, h, wS);
1729 Rs.push_back(rp.recoRow);
1730 dRs.push_back(vR);
1731 dPs.push_back(vP);
1732 }
1733 if (Rs.size() < 2){ continue; }
1734
1735 gR = new TGraph(Rs.size(), Rs.data(), dRs.data());
1736 gP = new TGraph(Rs.size(), Rs.data(), dPs.data());
1737
1738 for(int j=2; j<=m_dcc_out->m_hDRint[side]->GetNbinsY()-1; j++)
1739 {
1740 const double RVal = m_dcc_out->m_hDRint[side]->GetYaxis()->GetBinCenter(j);
1741 const bool inside = (RVal >= Rs.front() && RVal <= Rs.back());
1742 if(inside)
1743 {
1744 m_dcc_out->m_hDRint[side]->SetBinContent(i, j, gR->Eval(RVal));
1745 m_dcc_out->m_hDPint[side]->SetBinContent(i, j, gP->Eval(RVal));
1746 m_dcc_out->m_hentries[side]->SetBinContent(i, j, Rs.size());
1747 }
1748 }
1749 if(!m_QABase.empty())
1750 {
1751 distortionOutputfile->cd();
1752 gR->SetTitle(std::format("TPC {};R_{{reco}} [cm];#DeltaR [cm]",(side ? "North" : "South")).c_str());
1753 gR->Write(std::format("dR_phiBin{}_{}z",i,(side == 1 ? "pos" : "neg")).c_str());
1754 gP->SetTitle(std::format("TPC {};R_{{reco}} [cm];#Delta#phi [rad]",(side ? "North" : "South")).c_str());
1755 gP->Write(std::format("dPhi_phiBin{}_{}z",i,(side == 1 ? "pos" : "neg")).c_str());
1756 }
1757
1758 delete gR;
1759 delete gP;
1760 }
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775 fill_guarding_bins(m_dcc_out);
1776
1777 distortionOutputfile->cd();
1778 for (const auto& h : {m_dcc_out->m_hDRint[side], m_dcc_out->m_hDPint[side], m_dcc_out->m_hDZint[side], m_dcc_out->m_hentries[side]})
1779 {
1780 if (h)
1781 {
1782 std::cout << "writing in " << m_distortionOutputfile << " histogram " << h->GetName() << std::endl;
1783 h->Write();
1784 }
1785 }
1786 gr_recoPoints->Write(std::format("gr_recoPoints_{}z", (side == 1 ? "pos" : "neg")).c_str());
1787 gr_truthPoints->Write(std::format("gr_truthPoints_{}z", (side == 1 ? "pos" : "neg")).c_str());
1788 gr_dR->Write(std::format("gr_dr_{}z", (side == 1 ? "pos" : "neg")).c_str());
1789 gr_dPhi->Write(std::format("gr_dPhi_{}z", (side == 1 ? "pos" : "neg")).c_str());
1790
1791 gr_dR_toInterp->Write(std::format("gr_dr_toInterp_{}z", (side == 1 ? "pos" : "neg")).c_str());
1792 gr_dPhi_toInterp->Write(std::format("gr_dPhi_toInterp_{}z", (side == 1 ? "pos" : "neg")).c_str());
1793
1794
1795
1796
1797 }
1798 if(!m_QABase.empty())
1799 {
1800 QATree->Write();
1801 }
1802 distortionOutputfile->Close();
1803
1804 return Fun4AllReturnCodes::EVENT_OK;
1805 }
1806
1807 int LaserClusterTruthMatcher::End(PHCompositeNode * )
1808 {
1809 delete m_cdbttree;
1810 m_cdbttree = nullptr;
1811 return Fun4AllReturnCodes::EVENT_OK;
1812 }