7inline Double_t
StripTotalAdc(
const UShort_t *l,
const UShort_t *r, Int_t s) {
8 return (s == 0 || s == 17) ? Double_t(l[s]) : Double_t(l[s]) + Double_t(r[s]);
14inline Int_t
GatePartner(Int_t s) {
return s <= 1 ? 0 : s - 1; }
28 const std::vector<Float_t> &v, Double_t mode,
32 std::vector<ChannelCal> chans;
42 for (Int_t s = 1; s <= 16; s++) {
43 c.
name = Form(
"L%d", s);
47 c.
name = Form(
"R%d", s);
67 return 3 + (c.
strip - 1);
69 return 19 + (c.
strip - 1);
73Char_t
LongSide(Int_t strip) {
return (strip % 2 == 0) ?
'R' :
'L'; }
94 const Double_t kFwhmPerSigma = 2.0 * TMath::Sqrt(2.0 * TMath::Log(2.0));
107 const TString &run_label,
108 const TString &plot_subdir,
109 Bool_t save_plot = kTRUE) {
113 TFile *sf = IO::OpenForReading(sub);
114 if (!sf || sf->IsZombie()) {
119 TTree *tree =
static_cast<TTree *
>(sf->Get(
"events"));
127 UShort_t left_0_17_adc[18], rightdE_adc[18];
128 tree->SetBranchAddress(
"Left_0_17_dE", left_0_17_adc);
129 tree->SetBranchAddress(
"RightdE", rightdE_adc);
135 const Int_t kBeamGateNBins = 1024;
137 new TH2F(Form(
"h2_gate_s%d_s%d_%s", sx, sy, run_label.Data()),
138 Form(
";Strip%d #DeltaE [ADC];Strip%d #DeltaE [ADC]", sx, sy),
141 h->SetDirectory(
nullptr);
142 Long64_t n = tree->GetEntries();
143 for (Long64_t j = 0; j < n; j++) {
145 const Double_t tx =
StripTotalAdc(left_0_17_adc, rightdE_adc, sx);
146 const Double_t ty =
StripTotalAdc(left_0_17_adc, rightdE_adc, sy);
147 if (tx > 0.0 && ty > 0.0)
153 if (h->GetEntries() < 100) {
154 std::cerr <<
" " << run_label <<
": too few events for the strip " << sy
155 <<
" beam gate (strips " << sx <<
" vs " << sy <<
")"
161 const Double_t kSeedFrac = 0.30;
165 const Int_t kSeedHalfBins = 40;
166 const Int_t kMomentRefineIters = 4;
167 const Double_t kMomentRefineNSigma = 2.5;
168 Double_t bw_x = h->GetXaxis()->GetBinWidth(1);
169 Double_t bw_y = h->GetYaxis()->GetBinWidth(1);
171 h->GetMaximumBin(bx, by, bz);
172 Double_t peak_val = h->GetBinContent(bx, by);
173 Int_t lo_bx = std::max(1, bx - kSeedHalfBins);
174 Int_t hi_bx = std::min(h->GetNbinsX(), bx + kSeedHalfBins);
175 Int_t lo_by = std::max(1, by - kSeedHalfBins);
176 Int_t hi_by = std::min(h->GetNbinsY(), by + kSeedHalfBins);
178 kSeedFrac * peak_val, bw_x, bw_y);
180 std::cerr <<
" " << run_label <<
": no bins above beam seed threshold"
191 for (Int_t iter = 0; iter < kMomentRefineIters; iter++) {
192 Int_t wlo_bx = std::max(
193 1, h->GetXaxis()->FindBin(m.
mu_x - kMomentRefineNSigma * m.
sigma_x));
194 Int_t whi_bx = std::min(
196 h->GetXaxis()->FindBin(m.
mu_x + kMomentRefineNSigma * m.
sigma_x));
197 Int_t wlo_by = std::max(
198 1, h->GetYaxis()->FindBin(m.
mu_y - kMomentRefineNSigma * m.
sigma_y));
199 Int_t whi_by = std::min(
201 h->GetYaxis()->FindBin(m.
mu_y + kMomentRefineNSigma * m.
sigma_y));
203 h, wlo_bx, whi_bx, wlo_by, whi_by, kSeedFrac * peak_val, bw_x, bw_y);
215 std::cout <<
" beam gate strip " << sy <<
" (strips " << sx <<
" vs " << sy
216 <<
"): mu=(" << out.
mu_x <<
"," << out.
mu_y <<
") sigma=("
221 TCanvas *cv = PlottingUtils::GetConfiguredCanvas(kFALSE);
222 PlottingUtils::ConfigureAndDraw2DHistogram(h, cv);
229 Double_t sum = sxx + syy;
230 Double_t diff = sxx - syy;
231 Double_t det = TMath::Sqrt(diff * diff + 4.0 * sxy * sxy);
232 Double_t lambda1 = 0.5 * (sum + det);
233 Double_t lambda2 = 0.5 * (sum - det);
234 Double_t theta = 0.5 * TMath::ATan2(2.0 * sxy, diff) * 180.0 / TMath::Pi();
237 TEllipse *e =
new TEllipse(out.
mu_x, out.
mu_y, n * TMath::Sqrt(lambda1),
238 n * TMath::Sqrt(lambda2), 0, 360, theta);
240 e->SetLineColor(kViolet + 2);
244 PlottingUtils::SaveFigure(cv, Form(
"beam_gate_s%02d", sy),
245 plot_subdir +
"/beam_gate",
246 PlotSaveOptions::kLINEAR);
253inline Double_t
Median(std::vector<Float_t> &v) {
256 Int_t n = Int_t(v.size());
257 std::nth_element(v.begin(), v.begin() + n / 2, v.end());
258 Double_t med = Double_t(v[n / 2]);
260 std::nth_element(v.begin(), v.begin() + n / 2 - 1, v.end());
261 med = 0.5 * (med + Double_t(v[n / 2 - 1]));
271 Int_t n = Int_t(v.size());
273 Int_t i3 = (3 * n) / 4;
274 std::nth_element(v.begin(), v.begin() + i1, v.end());
275 Double_t q1 = Double_t(v[i1]);
276 std::nth_element(v.begin(), v.begin() + i3, v.end());
277 Double_t q3 = Double_t(v[i3]);
291 Int_t n = Int_t(v.size());
294 std::vector<Float_t> s(v);
295 std::sort(s.begin(), s.end());
296 Int_t i_lo = Int_t(0.05 * n);
297 Int_t i_hi = Int_t(0.95 * n);
298 Int_t i_q1 = Int_t(0.25 * n);
299 Int_t i_q3 = Int_t(0.75 * n);
302 Double_t p_lo = Double_t(s[i_lo]);
303 Double_t p_hi = Double_t(s[i_hi]);
304 Double_t med = Double_t(s[n / 2]);
305 sigma = (Double_t(s[i_q3]) - Double_t(s[i_q1])) / 1.349;
307 sigma = 0.05 * (med > 0.0 ? med : 1.0);
308 if (!(p_hi > p_lo)) {
312 const Int_t nbins = 100;
313 TH1F h(
"robust_peak_seed_h",
"", nbins, p_lo, p_hi);
314 h.SetDirectory(
nullptr);
315 for (Int_t j = 0; j < n; j++)
316 if (Double_t(s[j]) >= p_lo && Double_t(s[j]) <= p_hi)
317 h.Fill(Double_t(s[j]));
318 mode = h.GetBinCenter(h.GetMaximumBin());
330 const std::vector<Float_t> &v, Double_t mode,
332 Double_t xlo = mode - 4.0 * sigma;
333 Double_t xhi = mode + 4.0 * sigma;
336 Double_t bin_target = TMath::Max(1.0, sigma / 6.0);
337 Int_t nbins = Int_t((xhi - xlo) / bin_target);
342 TH1F *h =
new TH1F(name, title, nbins, xlo, xhi);
343 h->SetDirectory(
nullptr);
344 for (Int_t j = 0; j < Int_t(v.size()); j++)
345 h->Fill(Double_t(v[j]));
358 Double_t &peak_adc, Double_t &sigma_adc,
363 Double_t mode = 0.0, rsigma = 0.0;
365 if (!(mode > 0.0) || !(rsigma > 0.0))
369 Double_t xlo = h->GetXaxis()->GetXmin();
370 Double_t xhi = h->GetXaxis()->GetXmax();
371 Double_t bw = h->GetBinWidth(1);
372 Double_t fit_lo = mode - 2.0 * rsigma;
373 Double_t fit_hi = mode + 2.0 * rsigma;
378 Double_t amp_seed = h->GetBinContent(h->FindBin(mode));
380 TF1 *f =
new TF1(fname,
"gaus", fit_lo, fit_hi);
382 f->SetParameters(amp_seed, mode, rsigma);
383 f->SetParLimits(1, fit_lo, fit_hi);
384 f->SetParLimits(2, bw, fit_hi - fit_lo);
385 TFitResultPtr r = h->Fit(f,
"QSRNL");
386 if (!r.Get() || !r->IsValid()) {
391 peak_adc = f->GetParameter(1);
392 sigma_adc = std::fabs(f->GetParameter(2));
398 Double_t half = 1.5 * sigma_adc;
399 Double_t half_cap = 0.12 * peak_adc;
402 if (half > 2.0 * bw) {
403 Double_t lo2 = peak_adc - half;
404 Double_t hi2 = peak_adc + half;
409 TF1 *f2 =
new TF1(fname +
"_p2",
"gaus", lo2, hi2);
411 f2->SetParameters(f->GetParameter(0), peak_adc, sigma_adc);
412 f2->SetParLimits(1, lo2, hi2);
413 f2->SetParLimits(2, bw, hi2 - lo2);
414 TFitResultPtr r2 = h->Fit(f2,
"QSRNL");
415 if (r2.Get() && r2->IsValid() && f2->GetParameter(1) > 0 &&
416 std::fabs(f2->GetParameter(2)) > 0) {
417 peak_adc = f2->GetParameter(1);
418 sigma_adc = std::fabs(f2->GetParameter(2));
426 if (!(peak_adc > 0.0) || !(sigma_adc > 0.0)) {
439 Double_t &mu, Double_t &sigma) {
442 Float_t lo = v[0], hi = v[0];
443 for (Int_t j = 1; j < Int_t(v.size()); j++) {
449 Double_t pad = 0.05 * (Double_t(hi) - Double_t(lo));
452 const Int_t nbins = 75;
453 TH1F h(fname +
"_h",
"", nbins, Double_t(lo) - pad, Double_t(hi) + pad);
454 h.SetDirectory(
nullptr);
455 for (Int_t j = 0; j < Int_t(v.size()); j++)
456 h.Fill(Double_t(v[j]));
457 TF1 fg(fname,
"gaus", Double_t(lo) - pad, Double_t(hi) + pad);
458 Int_t pb = h.GetMaximumBin();
459 fg.SetParameters(h.GetBinContent(pb), h.GetBinCenter(pb), h.GetRMS());
460 TFitResultPtr r = h.Fit(&fg,
"QSRN");
461 if (!r.Get() || !r->IsValid())
463 mu = fg.GetParameter(1);
464 sigma = std::fabs(fg.GetParameter(2));
465 return mu > 0 && sigma > 0;
475 std::vector<Float_t>
l;
476 std::vector<Float_t>
r;
494 const std::vector<ChannelCal> &chans,
496 std::vector<std::vector<Float_t>> &samples,
498 Int_t n_chans = Int_t(chans.size());
499 samples.assign(n_chans, std::vector<Float_t>());
501 for (Int_t s = 0; s <= 17; s++)
502 any = any || gate[s].ok;
507 TFile *sf = IO::OpenForReading(sub);
508 if (!sf || sf->IsZombie()) {
513 TTree *tree =
static_cast<TTree *
>(sf->Get(
"events"));
522 UShort_t left_0_17_adc[18], rightdE_adc[18];
523 Short_t cathode_adc = 0;
524 tree->SetBranchAddress(
"Left_0_17_dE", left_0_17_adc);
525 tree->SetBranchAddress(
"RightdE", rightdE_adc);
526 tree->SetBranchAddress(
"Cathode", &cathode_adc);
528 Long64_t n = tree->GetEntries();
529 for (Long64_t j = 0; j < n; j++) {
538 for (Int_t s = 1; s <= 16; s++) {
539 Int_t lv = Int_t(left_0_17_adc[s]);
540 Int_t rv = Int_t(rightdE_adc[s]);
541 if (lv > 0 && rv > 0 && Long64_t(pairs[s].l.size()) <
kPairCap) {
542 pairs[s].
l.push_back(Float_t(lv));
543 pairs[s].
r.push_back(Float_t(rv));
548 Bool_t pass[18] = {kFALSE};
549 for (Int_t s = 1; s <= 17; s++) {
554 const Double_t y =
StripTotalAdc(left_0_17_adc, rightdE_adc, s);
555 if (x <= 0.0 || y <= 0.0)
568 for (Int_t s = 1; s <= 16; s++) {
569 if (!pass[s] || Long64_t(pairs[s].gated_long.size()) >=
kPairCap)
571 Bool_t l_is_long = (
LongSide(s) ==
'L');
573 l_is_long ? Int_t(left_0_17_adc[s]) : Int_t(rightdE_adc[s]);
575 l_is_long ? Int_t(rightdE_adc[s]) : Int_t(left_0_17_adc[s]);
577 pairs[s].
gated_long.push_back(Float_t(long_v));
578 pairs[s].
gated_short.push_back(Float_t(short_v > 0 ? short_v : 0));
582 for (Int_t i = 0; i < n_chans; i++) {
583 if (Long64_t(samples[i].size()) >=
kSampleCap)
587 const Int_t s = (c.
side ==
'C') ? 1 : c.
strip;
588 if (s < 0 || s > 17 || !pass[s])
592 v = Int_t(left_0_17_adc[c.
strip]);
593 else if (c.
side ==
'R')
594 v = Int_t(rightdE_adc[c.
strip]);
595 else if (c.
side ==
'C')
596 v = Int_t(cathode_adc);
598 samples[i].push_back(Float_t(v));
677 std::vector<Double_t>
x,
y,
ey;
698 const std::vector<Double_t> &y, Double_t &slope,
699 Double_t &intercept) {
700 const Int_t n = Int_t(x.size());
703 std::vector<Double_t> slopes;
704 slopes.reserve(n * (n - 1) / 2);
705 for (Int_t i = 0; i < n; i++)
706 for (Int_t j = i + 1; j < n; j++) {
707 Double_t dx = x[j] - x[i];
710 slopes.push_back((y[j] - y[i]) / dx);
714 std::sort(slopes.begin(), slopes.end());
715 const Int_t m = Int_t(slopes.size());
717 (m % 2 == 1) ? slopes[m / 2] : 0.5 * (slopes[m / 2 - 1] + slopes[m / 2]);
718 std::vector<Double_t> resid;
720 for (Int_t i = 0; i < n; i++)
721 resid.push_back(y[i] - slope * x[i]);
722 std::sort(resid.begin(), resid.end());
723 const Int_t r = Int_t(resid.size());
725 (r % 2 == 1) ? resid[r / 2] : 0.5 * (resid[r / 2 - 1] + resid[r / 2]);
734 const std::vector<Float_t> &v_long, Double_t c_long,
735 Double_t &slope_out, Double_t &intercept_out,
740 dbg->c_long = c_long;
741 dbg->n_gated = Long64_t(v_short.size());
743 if (c_long <= 0 || v_short.size() != v_long.size() || v_short.size() < 500) {
745 dbg->fail = c_long <= 0 ?
"c_long<=0"
746 : v_short.size() != v_long.size()
748 :
"fewer than 500 gated pairs";
752 const Double_t lo = 0.04 * c_long;
754 for (Int_t j = 0; j < Int_t(v_short.size()); j++) {
755 Double_t sh = Double_t(v_short[j]), lg = Double_t(v_long[j]);
756 if (sh < lo || sh >= hi)
759 dbg->n_short_window++;
760 if (lg <= kRidgeBandLo * c_long || lg >=
kRidgeBandHi * c_long)
766 slice[b].push_back(lg);
768 std::vector<Double_t> x, y, ey;
784 if (Long64_t(slice[b].size()) < min_per_slice)
786 std::sort(slice[b].begin(), slice[b].end());
787 Double_t med = slice[b][slice[b].size() / 2];
789 slice[b][slice[b].size() * 3 / 4] - slice[b][slice[b].size() / 4];
792 ey.push_back(1.253 * (iqr / 1.349) /
793 TMath::Sqrt(Double_t(slice[b].size())));
801 dbg->min_per_slice = min_per_slice;
809 dbg->n_slices = Int_t(x.size());
813 dbg->fail =
"fewer than kRidgeMinPts filled slices";
820 Double_t slope = 0.0, inter = 0.0;
823 dbg->fail =
"degenerate slice set (TheilSenLine)";
827 intercept_out = inter;
830 dbg->intercept = inter;
833 dbg->c_short = -inter / slope;
837 dbg->fail =
"slope >= 0";
843 if (inter < 0.80 * c_long || inter > 1.20 * c_long) {
845 dbg->fail =
"intercept outside [0.80, 1.20]*C_long";
852 return -inter / slope;
861 const TString &plot_subdir) {
862 TString subdir = plot_subdir +
"/ridge";
863 for (Int_t s = 1; s <= 16; s++) {
868 Bool_t l_is_long = (
LongSide(s) ==
'L');
871 TH2F *h =
new TH2F(Form(
"h_ridge_s%d", s),
872 Form(
";Strip %d Short (%c) #DeltaE [ADC];Strip %d Long "
873 "(%c) #DeltaE [ADC]",
874 s, l_is_long ?
'R' :
'L', s, l_is_long ?
'L' :
'R'),
875 200, 0.0, xhi, 200, 0.0, 1.60 * d.
c_long);
876 for (Int_t j = 0; j < Int_t(p.
gated_short.size()); j++)
878 TCanvas *cv = PlottingUtils::GetConfiguredCanvas(kFALSE);
879 PlottingUtils::Configure2DHistogram(h, cv);
881 TGraphErrors *g =
nullptr;
883 g =
new TGraphErrors(Int_t(d.
x.size()), &d.
x[0], &d.
y[0],
nullptr,
885 g->SetMarkerStyle(20);
886 g->SetMarkerSize(0.8);
887 g->SetMarkerColor(kBlack);
888 g->SetLineColor(kBlack);
896 lf->SetLineColor(kViolet + 2);
901 PlottingUtils::SaveFigure(cv, Form(
"ridge_s%02d", s), subdir,
902 PlotSaveOptions::kLINEAR);
913Double_t
GmFindPeak(
const std::vector<Float_t> &v, Double_t lo, Double_t hi,
914 Double_t skip_frac) {
915 if (v.empty() || hi <= lo)
917 std::vector<Long64_t> h(
kGmBins, 0);
918 Double_t bw = (hi - lo) /
kGmBins;
919 for (Int_t j = 0; j < Int_t(v.size()); j++) {
920 Double_t x = Double_t(v[j]);
921 if (x < lo || x >= hi)
923 Int_t b = Int_t((x - lo) / bw);
927 Int_t skip = Int_t(
kGmBins * skip_frac);
930 for (Int_t b = skip; b <
kGmBins; b++) {
936 if (bmax < 0 || vmax <= 0)
938 return lo + (bmax + 0.5) * bw;
943 const TString &plot_subdir) {
945 Int_t idx_l[18], idx_r[18];
946 for (Int_t s = 0; s < 18; s++) {
950 for (Int_t i = 0; i < Int_t(chans.size()); i++) {
951 if (chans[i].strip >= 1 && chans[i].strip <= 16) {
952 if (chans[i].side ==
'L')
953 idx_l[chans[i].strip] = i;
954 else if (chans[i].side ==
'R')
955 idx_r[chans[i].strip] = i;
965 Bool_t matched[18] = {kFALSE};
966 Double_t short_anchor_adc[18] = {0};
967 std::vector<Double_t> ratios_found[2];
968 for (Int_t s = 1; s <= 16; s++) {
969 if (idx_l[s] < 0 || idx_r[s] < 0)
971 Bool_t l_is_long = (
LongSide(s) ==
'L');
972 ChannelCal &c_long = chans[l_is_long ? idx_l[s] : idx_r[s]];
973 ChannelCal &c_short = chans[l_is_long ? idx_r[s] : idx_l[s]];
975 std::cerr <<
" strip " << s
976 <<
": long side uncalibrated; skipping L/R gain match"
980 Double_t peak_short = 0.0;
991 const std::vector<Float_t> &v_short = p.
gated_short;
992 const std::vector<Float_t> &v_long = p.
gated_long;
993 Double_t slope = 0.0, inter = 0.0;
995 inter, &ridge_dbg[s]);
997 std::cout <<
" strip " << s <<
" ridge slope=" << Form(
"%.3f", slope)
998 <<
" intercept/C_long="
1001 <<
" short_anchor=" << Form(
"%.1f", peak_short) <<
" ADC"
1004 std::cerr <<
" strip " << s <<
": ridge not measurable "
1005 <<
"(slope=" << Form(
"%.3f", slope)
1006 <<
", intercept=" << Form(
"%.1f", inter) <<
")"
1007 << (ridge_dbg[s].
fail[0] ? Form(
" [%s]", ridge_dbg[s].fail)
1009 <<
" gated=" << Form(
"%lld", ridge_dbg[s].n_gated)
1010 <<
" short_win=" << Form(
"%lld", ridge_dbg[s].n_short_window)
1011 <<
" long_band=" << Form(
"%lld", ridge_dbg[s].n_long_band)
1012 <<
" slices=" << ridge_dbg[s].
n_slices
1017 if (peak_short <= 0)
1023 Double_t ratio = peak_short / c_long.
fit_adc;
1025 std::cerr <<
" strip " << s <<
": ridge ratio " << Form(
"%.2f", ratio)
1027 <<
"]; rejecting anchor " << Form(
"%.1f", peak_short) <<
" ADC"
1031 short_anchor_adc[s] = peak_short;
1032 ratios_found[s % 2].push_back(ratio);
1033 chans[l_is_long ? idx_r[s] : idx_l[s]].ridge_ratio = ratio;
1034 std::cout <<
" strip " << s <<
" short_anchor=" << Form(
"%.1f", peak_short)
1035 <<
" ADC (ratio " << Form(
"%.3f", ratio) <<
")" << std::endl;
1045 Double_t median_ratio[2] = {0.0, 0.0};
1046 std::vector<Double_t> all_ratios;
1047 for (Int_t par = 0; par < 2; par++) {
1048 std::vector<Double_t> &v = ratios_found[par];
1049 all_ratios.insert(all_ratios.end(), v.begin(), v.end());
1052 std::sort(v.begin(), v.end());
1053 Int_t m = Int_t(v.size());
1055 (m % 2 == 1) ? v[m / 2] : 0.5 * (v[m / 2 - 1] + v[m / 2]);
1057 Double_t global_ratio = 0.0;
1058 if (!all_ratios.empty()) {
1059 std::sort(all_ratios.begin(), all_ratios.end());
1060 Int_t m = Int_t(all_ratios.size());
1061 global_ratio = (m % 2 == 1)
1063 : 0.5 * (all_ratios[m / 2 - 1] + all_ratios[m / 2]);
1065 std::cout <<
" ridge ratio medians: odd=" << Form(
"%.3f", median_ratio[1])
1066 <<
" even=" << Form(
"%.3f", median_ratio[0])
1067 <<
" all=" << Form(
"%.3f", global_ratio) << std::endl;
1069 for (Int_t s = 1; s <= 16; s++) {
1070 if (idx_l[s] < 0 || idx_r[s] < 0)
1072 Bool_t l_is_long = (
LongSide(s) ==
'L');
1073 ChannelCal &c_long = chans[l_is_long ? idx_l[s] : idx_r[s]];
1074 ChannelCal &c_short = chans[l_is_long ? idx_r[s] : idx_l[s]];
1077 if (short_anchor_adc[s] <= 0) {
1078 Double_t r = median_ratio[s % 2] > 0 ? median_ratio[s % 2] : global_ratio;
1080 std::cerr <<
" strip " << s
1081 <<
": no ridge and no ratio fallback; keeping "
1086 short_anchor_adc[s] = r * c_long.
fit_adc;
1087 std::cout <<
" strip " << s <<
" short_anchor=ratio fallback "
1089 <<
" x C_long = " << Form(
"%.1f", short_anchor_adc[s]) <<
" ADC"
1090 << (median_ratio[s % 2] > 0 ?
"" :
" (global, parity had none)")
1094 c_short.
gain = 1.0 / short_anchor_adc[s];
1096 std::cout <<
" strip " << s
1097 <<
" L/R match: long peak=" << Form(
"%.1f", c_long.
fit_adc)
1098 <<
" ADC short anchor=" << Form(
"%.1f", short_anchor_adc[s])
1099 <<
" ADC" << std::endl;
1108 Double_t esum_peak[18] = {0};
1109 for (Int_t s = 1; s <= 16; s++) {
1113 Bool_t l_is_long = (
LongSide(s) ==
'L');
1114 Double_t g_long =
Gain(chans[l_is_long ? idx_l[s] : idx_r[s]]);
1115 Double_t g_short =
Gain(chans[l_is_long ? idx_r[s] : idx_l[s]]);
1116 std::vector<Float_t> esum;
1118 for (Int_t j = 0; j < Int_t(p.
gated_long.size()); j++)
1119 esum.push_back(Float_t(g_long * Double_t(p.
gated_long[j]) +
1123 for (Int_t s = 1; s <= 16; s++) {
1126 if (esum_peak[s] <= 0) {
1127 std::cerr <<
" strip " << s <<
": no summed beam peak in (" <<
kGmEsumLo
1128 <<
", " <<
kGmEsumHi <<
") a.u." << std::endl;
1131 Double_t dev = esum_peak[s] - 1.0;
1132 std::cout <<
" strip " << s
1133 <<
" summed beam peak=" << Form(
"%.4f", esum_peak[s]) <<
" a.u. ("
1134 << Form(
"%+.1f%%", 100.0 * dev) <<
")"
1135 << (TMath::Abs(dev) > 0.05 ?
" <-- check ridge fit" :
"")
1146 std::vector<std::vector<Float_t>> &samples,
1147 std::vector<TF1 *> &fits_out,
const TString &run_label,
1149 const TString &plot_subdir) {
1150 Int_t n_chans = Int_t(chans.size());
1151 fits_out.assign(n_chans,
nullptr);
1153 for (Int_t i = 0; i < n_chans; i++) {
1155 std::vector<Float_t> &v = samples[i];
1163 if (c.
side ==
'C') {
1171 Double_t peak = 0, sig = 0;
1174 Form(
"f_peak_gaus_%s_%s", c.
name.Data(), run_label.Data());
1183 Double_t mode = 0.0, rsigma = 0.0;
1187 TString kind = (c.
side ==
'S') ?
"guard"
1190 std::cerr <<
" [fit-fallback " << kind <<
"] " << c.
name
1191 <<
": peak fit failed; using mode anchor "
1196 std::cout <<
" " << c.
name <<
" anchor[ADC]=" << c.
fit_adc
1211 const Double_t eres_vals[35]) {
1212 toml::table eres_tbl;
1213 eres_tbl.insert(
"Cathode", eres_vals[0]);
1214 eres_tbl.insert(
"S0", eres_vals[1]);
1215 eres_tbl.insert(
"S17", eres_vals[2]);
1216 for (Int_t s = 1; s <= 16; s++) {
1217 std::string key =
"L" + std::to_string(s);
1218 eres_tbl.insert(key, eres_vals[3 + (s - 1)]);
1220 for (Int_t s = 1; s <= 16; s++) {
1221 std::string key =
"R" + std::to_string(s);
1222 eres_tbl.insert(key, eres_vals[19 + (s - 1)]);
1224 toml::table detector_tbl;
1225 detector_tbl.insert(
"eres", eres_tbl);
1226 toml::table root_tbl;
1227 root_tbl.insert(
"detector", detector_tbl);
1233 gSystem->mkdir(out_dir, kTRUE);
1234 TString out_full = out_dir +
"/" + out_subpath;
1235 std::ofstream f(out_full.Data());
1237 std::cerr <<
"Cannot write eres TOML: " << out_full << std::endl;
1240 f << root_tbl << std::endl;
1241 std::cout <<
" wrote eres TOML: " << out_full << std::endl;
1252 if (TObject *old = dst->Get(
"calibration"))
1254 TTree *cal =
new TTree(
"calibration",
"Per-channel normMUSIC calibration");
1264 Float_t gain_left[18] = {0}, gain_right[18] = {0};
1265 Float_t gain_cathode = 0.0f;
1268 Float_t ridge_ratio[18] = {0};
1269 Float_t long_anchor[18] = {0};
1270 Int_t n_actual = TMath::Min(Int_t(chans.size()),
kMaxChannels);
1271 for (Int_t k = 0; k < n_actual; k++) {
1274 gain[k] = ok[k] ? Float_t(
Gain(c)) : 0.0f;
1275 fit_adc[k] = Float_t(c.
fit_adc);
1285 gain_left[c.
strip] = gain[k];
1287 gain_left[c.
strip] = gain[k];
1289 gain_right[c.
strip] = gain[k];
1290 else if (c.
side ==
'C')
1291 gain_cathode = gain[k];
1293 cal->Branch(
"Gain", gain, Form(
"Gain[%d]/F",
kMaxChannels));
1295 cal->Branch(
"FitADC", fit_adc, Form(
"FitADC[%d]/F",
kMaxChannels));
1296 cal->Branch(
"FitSigmaADC", fit_sigma,
1298 cal->Branch(
"FitN", fit_n, Form(
"FitN[%d]/L",
kMaxChannels));
1299 cal->Branch(
"GainLeft", gain_left,
"GainLeft[18]/F");
1300 cal->Branch(
"GainRight", gain_right,
"GainRight[18]/F");
1301 cal->Branch(
"GainCathode", &gain_cathode,
"GainCathode/F");
1302 cal->Branch(
"RidgeRatio", ridge_ratio,
"RidgeRatio[18]/F");
1303 cal->Branch(
"LongAnchor", long_anchor,
"LongAnchor[18]/F");
1309 Float_t beam_e_min = 0.0f, beam_e_max = 0.0f;
1310 Float_t strip_factor[18];
1311 for (Int_t s = 0; s < 18; s++)
1312 strip_factor[s] = 1.0f;
1317 for (Int_t s = 0; s < 18; s++)
1318 strip_factor[s] = Float_t(align->
factors[s]);
1321 cal->Branch(
"BeamEMin", &beam_e_min,
"BeamEMin/F");
1322 cal->Branch(
"BeamEMax", &beam_e_max,
"BeamEMax/F");
1323 cal->Branch(
"StripFactor", strip_factor,
"StripFactor[18]/F");
1325 cal->Write(
"calibration", TObject::kOverwrite);
1331 const std::vector<ChannelCal> &chans,
1332 const std::vector<std::vector<Float_t>> &samples,
1333 const std::vector<TF1 *> &fits,
const TString &plot_subdir) {
1334 TString subdir = plot_subdir +
"/beam_peak";
1335 for (Int_t i = 0; i < Int_t(chans.size()); i++) {
1337 const std::vector<Float_t> &v = samples[i];
1342 Double_t mode = 0.0, sigma = 0.0;
1345 Form(
";%s #DeltaE [ADC];Counts", c.
name.Data()),
1347 TCanvas *cv = PlottingUtils::GetConfiguredCanvas(kFALSE);
1348 PlottingUtils::ConfigureAndDrawHistogram(h, kBlack);
1351 fit->SetLineColor(kViolet + 2);
1352 fit->SetLineWidth(2);
1353 fit->Draw(
"L SAME");
1356 PlottingUtils::SaveFigure(cv, Form(
"beam_peak_%s", c.
name.Data()), subdir,
1357 PlotSaveOptions::kLINEAR);
1368 const std::vector<ChannelCal> &chans,
1371 TFile *f = IO::OpenForWriting(events_subpath,
"UPDATE");
1372 if (!f || f->IsZombie()) {
1373 std::cerr <<
"Cannot open " << events_subpath <<
" to write calibration"
1380 std::cout <<
" wrote calibration into " << events_subpath << std::endl;
1388 const std::vector<ChannelCal> &chans,
1389 const TString &plot_subdir,
1390 const TString &file_label) {
1391 const Int_t kNStrips = 18;
1392 const Int_t nbins = 300;
1396 for (Int_t s = 0; s < kNStrips; s++) {
1397 h[s] =
new TH1D(Form(
"h_dynrange_%s_S%d", file_label.Data(), s),
1398 ";#DeltaE [a.u.];Counts", nbins, emin, emax);
1399 h[s]->SetDirectory(
nullptr);
1402 TFile *sf = IO::OpenForReading(sub);
1403 if (!sf || sf->IsZombie()) {
1406 for (Int_t s = 0; s < kNStrips; s++)
1410 TTree *tree =
static_cast<TTree *
>(sf->Get(
"events"));
1414 for (Int_t s = 0; s < kNStrips; s++)
1423 for (Int_t s = 0; s < kNStrips; s++)
1427 Long64_t n = tree->GetEntries();
1428 for (Long64_t j = 0; j < n; j++) {
1431 for (Int_t s = 0; s < kNStrips; s++) {
1432 Double_t v = ev.
total[s];
1439 std::vector<Int_t> colors = PlottingUtils::GetDefaultColors();
1441 for (Int_t s = 0; s < kNStrips; s++) {
1442 Double_t m = h[s]->GetMaximum();
1446 TCanvas *cv = PlottingUtils::GetConfiguredCanvas(kFALSE);
1447 cv->SetRightMargin(0.20);
1448 Bool_t first = kTRUE;
1449 for (Int_t s = 0; s < kNStrips; s++) {
1450 Int_t color = colors[s % Int_t(colors.size())];
1451 h[s]->SetLineColor(color);
1452 h[s]->SetLineWidth(2);
1453 h[s]->SetMaximum(1.15 * y_top);
1454 h[s]->Draw(first ?
"HIST" :
"HIST SAME");
1457 TLegend *leg = PlottingUtils::AddLegend(0.81, 0.99, 0.10, 0.95);
1458 for (Int_t s = 0; s < kNStrips; s++)
1459 leg->AddEntry(h[s], Form(
"S%d", s),
"l");
1462 PlottingUtils::SaveFigure(cv,
"dynamic_range_check", plot_subdir,
1463 PlotSaveOptions::kLOG);
1466 for (Int_t s = 0; s < kNStrips; s++)
1475 const std::vector<ChannelCal> &chans,
1476 const std::vector<std::vector<Float_t>> &samples,
1477 const TString &plot_subdir,
const TString &file_label) {
1478 const Int_t n_chans = Int_t(chans.size());
1479 const Int_t nbins = 300;
1482 std::vector<TH1D *> h(n_chans,
nullptr);
1483 for (Int_t i = 0; i < n_chans; i++) {
1488 Form(
"h_calibrange_%s_%s", file_label.Data(), c.
name.Data());
1489 h[i] =
new TH1D(hname,
";#DeltaE [a.u.];Counts", nbins, emin, emax);
1490 h[i]->SetDirectory(
nullptr);
1491 const std::vector<Float_t> &v = samples[i];
1492 for (Int_t j = 0; j < Int_t(v.size()); j++) {
1493 Double_t val =
ApplyCal(c, Double_t(v[j]));
1499 std::vector<Int_t> colors = PlottingUtils::GetDefaultColors();
1501 for (Int_t i = 0; i < n_chans; i++) {
1504 Double_t m = h[i]->GetMaximum();
1508 TCanvas *cv = PlottingUtils::GetConfiguredCanvas(kFALSE);
1509 cv->SetRightMargin(0.20);
1510 Bool_t first = kTRUE;
1511 for (Int_t i = 0; i < n_chans; i++) {
1514 Int_t color = colors[i % Int_t(colors.size())];
1515 h[i]->SetLineColor(color);
1516 h[i]->SetLineWidth(2);
1517 h[i]->SetMaximum(1.15 * y_top);
1518 h[i]->Draw(first ?
"HIST" :
"HIST SAME");
1521 TLegend *leg = PlottingUtils::AddLegend(0.81, 0.99, 0.10, 0.95);
1522 for (Int_t i = 0; i < n_chans; i++) {
1525 leg->AddEntry(h[i], chans[i].name.Data(),
"l");
1529 PlottingUtils::SaveFigure(cv,
"dynamic_range_calib_events", plot_subdir,
1530 PlotSaveOptions::kLOG);
1533 for (Int_t i = 0; i < n_chans; i++)
1545 const Double_t kBeamNSigma = 3.0;
1546 for (Int_t i = 0; i < Int_t(chans.size()); i++) {
1549 Double_t g =
Gain(c);
1552 std::cout <<
" beam energy window (from Strip0): [" << align.
beam_e_min
1553 <<
", " << align.
beam_e_max <<
"] a.u." << std::endl;
1557 std::cerr <<
" beam energy window: Strip0 not calibrated, using [0, 0]"
1572 const TString &plot_subdir,
1573 const TString &file_label) {
1574 const Int_t kNStrips = 18;
1575 const Int_t kNHistBins = 200;
1576 const Double_t kHistMin = 0.0;
1577 const Double_t kHistMax = 10.0;
1578 const Int_t kSmoothTimes = 5;
1579 const Double_t kMisalignPct = 1.5;
1580 const Int_t kMaxIter = 4;
1581 const Int_t kPolyDeg = 3;
1582 const Double_t kGausFitHalfWidth = 0.15;
1585 for (Int_t s = 0; s < kNStrips; s++) {
1591 TFile *sf = IO::OpenForReading(sub);
1592 if (!sf || sf->IsZombie()) {
1598 TTree *tree =
static_cast<TTree *
>(sf->Get(
"events"));
1607 std::cerr <<
" " << file_label
1608 <<
": calibration tree not found -- skipping alignment"
1615 TH2D *h2 =
new TH2D(Form(
"h2_strip_align_%s", file_label.Data()),
1616 ";Strip number;#DeltaE [a.u.]", kNStrips, -0.5,
1617 kNStrips - 0.5, kNHistBins, kHistMin, kHistMax);
1618 h2->SetDirectory(
nullptr);
1620 Long64_t n = tree->GetEntries();
1621 Long64_t n_used = 0;
1622 for (Long64_t j = 0; j < n; j++) {
1625 Bool_t any = kFALSE;
1626 for (Int_t s = 0; s < kNStrips; s++) {
1627 Double_t v = ev.
total[s];
1630 h2->Fill(Double_t(s), v);
1639 std::cout <<
" strip alignment: " << n_used <<
" events decoded"
1642 Double_t beam_centroids[kNStrips] = {0};
1643 Bool_t beam_ok[kNStrips] = {kFALSE};
1645 for (Int_t s = 0; s < kNStrips; s++) {
1646 Int_t bin_ix = s + 1;
1647 TH1D *proj = h2->ProjectionY(
1648 Form(
"hproj_align_%s_s%d", file_label.Data(), s), bin_ix, bin_ix);
1649 proj->SetDirectory(
nullptr);
1650 Long64_t n_entries = Long64_t(proj->GetEntries());
1652 std::cerr <<
" strip " << s <<
": too few entries for alignment ("
1653 << n_entries <<
")" << std::endl;
1657 proj->Smooth(kSmoothTimes);
1663 Double_t peak_target = 1.0;
1664 Double_t search_lo, search_hi;
1665 if (s == 0 || s == 17) {
1672 Int_t b_lo = proj->FindBin(search_lo);
1673 Int_t b_hi = proj->FindBin(search_hi);
1675 Double_t best_dist = 1e9;
1676 for (Int_t b = b_lo; b <= b_hi; b++) {
1677 Double_t v = proj->GetBinContent(b);
1681 if (b > b_lo && proj->GetBinContent(b - 1) >= v)
1683 if (b < b_hi && proj->GetBinContent(b + 1) > v)
1685 Double_t bc = proj->GetBinCenter(b);
1686 Double_t d = TMath::Abs(bc - peak_target);
1687 if (d < best_dist) {
1694 Double_t val_max = 0;
1695 for (Int_t b = b_lo; b <= b_hi; b++) {
1696 Double_t v = proj->GetBinContent(b);
1707 Double_t seed_peak = proj->GetBinCenter(b_max);
1708 Double_t seed_h = proj->GetBinContent(b_max);
1709 if (seed_h <= 0 || seed_peak <= 0) {
1715 Double_t fit_lo = seed_peak - kGausFitHalfWidth;
1716 Double_t fit_hi = seed_peak + kGausFitHalfWidth;
1717 if (fit_lo < kHistMin)
1719 if (fit_hi > kHistMax)
1721 TF1 *fg =
new TF1(
"f_align_peak_refine",
"gaus", fit_lo, fit_hi);
1722 fg->SetParameters(seed_h, seed_peak, 0.05);
1723 fg->SetParLimits(1, fit_lo, fit_hi);
1724 TFitResultPtr r = proj->Fit(fg,
"QSRN");
1725 Double_t beam_peak = fg->GetParameter(1);
1726 if (!(r.Get() && r->IsValid() && beam_peak > 0))
1727 beam_peak = seed_peak;
1730 beam_centroids[s] = beam_peak;
1732 std::cout <<
" strip " << s <<
" beam=" << Form(
"%.4f", beam_peak)
1733 <<
" a.u. (n=" << n_entries <<
")" << std::endl;
1739 TGraph *g_cent =
new TGraph(kNStrips);
1741 for (Int_t s = 1; s <= 16; s++) {
1743 g_cent->SetPoint(np, Double_t(s), beam_centroids[s]);
1749 std::set<Int_t> outliers;
1750 for (Int_t iter = 0; iter < kMaxIter; iter++) {
1751 if (g_cent->GetN() <= kPolyDeg + 1)
1753 TF1 *fpol =
new TF1(Form(
"fpol_align_%s_iter%d", file_label.Data(), iter),
1754 "pol3", -0.5, kNStrips - 0.5);
1755 TFitResultPtr r = g_cent->Fit(fpol,
"QSRN");
1756 if (!r.Get() || !r->IsValid()) {
1760 Double_t worst_pct = 0;
1761 Int_t worst_idx = -1;
1762 for (Int_t i = 0; i < g_cent->GetN(); i++) {
1764 g_cent->GetPoint(i, x, y);
1765 Double_t pred = fpol->Eval(x);
1768 Double_t resid_pct = TMath::Abs(y - pred) / pred * 100.0;
1769 if (resid_pct > worst_pct) {
1770 worst_pct = resid_pct;
1775 if (worst_pct < kMisalignPct || worst_idx < 0)
1778 g_cent->GetPoint(worst_idx, rx, ry);
1779 std::cout <<
" strip " << Int_t(rx) <<
" outlier "
1780 << Form(
"%.1f", worst_pct) <<
"% — dropped" << std::endl;
1781 outliers.insert(Int_t(rx));
1782 g_cent->RemovePoint(worst_idx);
1785 TF1 *fbeam =
nullptr;
1786 if (g_cent->GetN() > kPolyDeg + 1) {
1787 fbeam =
new TF1(Form(
"fbeam_align_%s", file_label.Data()),
"pol3", -0.5,
1789 g_cent->Fit(fbeam,
"QSRN");
1790 std::cout <<
" strip alignment reference: pol3 fitted through "
1791 << g_cent->GetN() <<
" strips" << std::endl;
1797 Int_t valid_strips = 0;
1798 for (Int_t s = 0; s < kNStrips; s++) {
1801 Double_t centro = beam_centroids[s];
1805 if (s >= 1 && s <= 16 && fbeam) {
1806 Double_t ref = fbeam->Eval(Double_t(s));
1808 result.
factors[s] = ref / centro;
1810 result.
factors[s] = 1.0 / centro;
1812 result.
factors[s] = 1.0 / centro;
1815 std::cout <<
" strip " << s <<
" centroid=" << Form(
"%.4f", centro)
1816 <<
" factor=" << Form(
"%.4f", result.
factors[s]) << std::endl;
1818 result.
ok = (valid_strips >= 4) ? kTRUE : kFALSE;
1822 TCanvas *cv = PlottingUtils::GetConfiguredCanvas(kFALSE);
1823 PlottingUtils::ConfigureAndDraw2DHistogram(h2, cv);
1824 TGraph *g_beam_plot =
new TGraph(kNStrips);
1826 for (Int_t s = 0; s < kNStrips; s++) {
1828 g_beam_plot->SetPoint(nb, Double_t(s), beam_centroids[s]);
1832 g_beam_plot->Set(nb);
1834 g_beam_plot->SetMarkerStyle(20);
1835 g_beam_plot->SetMarkerColor(kOrange);
1836 g_beam_plot->Draw(
"P SAME");
1839 fbeam->SetLineColor(kViolet + 2);
1840 fbeam->SetLineWidth(2);
1841 fbeam->Draw(
"SAME");
1844 PlottingUtils::SaveFigure(cv,
"strip_alignment_check", plot_subdir,
1845 PlotSaveOptions::kLINEAR);
1858 const FileSpec &spec,
const std::vector<ChannelCal> &chans_template) {
1860 TString plot_subdir =
"beam_calibration/" + file_label;
1861 std::cout <<
"Beam calibration: " << file_label << std::endl;
1870 for (Int_t s = 1; s <= 17; s++) {
1876 std::cerr <<
" " << file_label <<
": strip " << s
1877 <<
" beam gate failed; that strip is not calibrated here"
1885 std::cerr <<
" " << file_label <<
": every per-strip beam gate failed"
1890 std::vector<ChannelCal> chans = chans_template;
1891 std::vector<std::vector<Float_t>> samples;
1894 std::vector<TF1 *> peak_fits;
1897 ReduceToAnchors(chans, samples, peak_fits, file_label, pairs, plot_subdir);
1900 for (Int_t i = 0; i < Int_t(peak_fits.size()); i++)
1901 delete peak_fits[i];
1908 for (Int_t i = 0; i < Int_t(chans.size()); i++) {
1915 else if (c.
side ==
'S')
1924 why = Form(
"bad exp anchor (fit_adc=%.1f)", c.
fit_adc);
1925 std::cerr <<
" [uncalibrated " << kind <<
"] " << c.
name <<
" -> gain 0; "
1926 << why <<
" (beam n=" << c.
n_samples <<
")" << std::endl;
1929 for (Int_t i = 0; i < Int_t(chans.size()); i++)
1931 std::cout <<
" " << chans[i].name <<
" gain=" <<
Gain(chans[i])
1932 <<
" a.u./ADC resolution="
1974 std::cout <<
" " << file_label <<
" calibration complete." << std::endl;
1993 Int_t run,
const std::vector<FileSpec> &specs) {
1994 std::vector<Double_t> per_strip[18];
1995 for (Int_t k = 0; k < Int_t(specs.size()); k++) {
1997 TFile *cf = IO::OpenForReading(sub);
1998 if (!cf || cf->IsZombie()) {
2003 TTree *t =
static_cast<TTree *
>(cf->Get(
"calibration"));
2004 if (!t || t->GetEntries() < 1 || !t->GetBranch(
"RidgeRatio")) {
2009 Float_t rr[18] = {0};
2010 t->SetBranchAddress(
"RidgeRatio", rr);
2012 for (Int_t s = 1; s <= 16; s++)
2014 per_strip[s].push_back(Double_t(rr[s]));
2019 Double_t med[18] = {0};
2020 std::cout <<
"Run " << run <<
" ridge-ratio aggregation:" << std::endl;
2021 for (Int_t s = 1; s <= 16; s++) {
2022 std::vector<Double_t> &v = per_strip[s];
2024 std::cerr <<
" strip " << s <<
": only " << v.size()
2025 <<
" subfiles measured the ridge; leaving per-subfile gains"
2029 std::sort(v.begin(), v.end());
2030 Int_t m = Int_t(v.size());
2031 med[s] = (m % 2 == 1) ? v[m / 2] : 0.5 * (v[m / 2 - 1] + v[m / 2]);
2032 Double_t lo = v[m / 4], hi = v[(3 * m) / 4];
2033 std::cout <<
" strip " << s <<
" ratio=" << Form(
"%.4f", med[s])
2034 <<
" IQR " << Form(
"%.4f", lo) <<
"-" << Form(
"%.4f", hi)
2035 <<
" from " << m <<
" subfiles" << std::endl;
2038 Int_t n_rewritten = 0;
2039 for (Int_t k = 0; k < Int_t(specs.size()); k++) {
2041 TFile *cf = IO::OpenForWriting(sub,
"UPDATE");
2042 if (!cf || cf->IsZombie()) {
2047 TTree *t =
static_cast<TTree *
>(cf->Get(
"calibration"));
2048 if (!t || t->GetEntries() < 1 || !t->GetBranch(
"LongAnchor")) {
2053 Float_t gl[18] = {0}, gr[18] = {0}, la[18] = {0}, rr[18] = {0};
2054 t->SetBranchAddress(
"GainLeft", gl);
2055 t->SetBranchAddress(
"GainRight", gr);
2056 t->SetBranchAddress(
"LongAnchor", la);
2057 t->SetBranchAddress(
"RidgeRatio", rr);
2059 Bool_t changed = kFALSE;
2060 for (Int_t s = 1; s <= 16; s++) {
2061 if (med[s] <= 0 || la[s] <= 0)
2063 Double_t anchor = med[s] * Double_t(la[s]);
2067 gr[s] = Float_t(1.0 / anchor);
2069 gl[s] = Float_t(1.0 / anchor);
2070 rr[s] = Float_t(med[s]);
2074 TTree *nt = t->CloneTree(0);
2077 nt->Write(
"calibration", TObject::kOverwrite);
2083 std::cout <<
" rewrote short gains in " << n_rewritten <<
" subfiles"
2088 Int_t run,
const std::vector<FileSpec> &specs) {
2089 const Int_t n_eres = 35;
2090 std::vector<std::vector<Double_t>> fwhm_per_chan(n_eres);
2093 Int_t n_chans = Int_t(tmpl.size());
2095 for (Int_t s = 0; s < Int_t(specs.size()); s++) {
2098 TFile *cf = IO::OpenForReading(cal_sub);
2099 if (!cf || cf->IsZombie()) {
2104 TTree *t =
static_cast<TTree *
>(cf->Get(
"calibration"));
2113 t->SetBranchAddress(
"FitADC", fit_adc);
2114 t->SetBranchAddress(
"Ok", ok);
2115 t->SetBranchAddress(
"FitSigmaADC", fit_sigma);
2116 if (t->GetEntries() < 1) {
2123 for (Int_t i = 0; i < n_chans && i <
kMaxChannels; i++) {
2126 Double_t sig_adc = fit_sigma[i];
2127 if (sig_adc <= 0 || fit_adc[i] <= 0)
2131 const Double_t kFwhmPerSigma = 2.0 * TMath::Sqrt(2.0 * TMath::Log(2.0));
2133 100.0 * kFwhmPerSigma * Double_t(sig_adc) / Double_t(fit_adc[i]);
2135 if (idx >= 0 && idx < n_eres)
2136 fwhm_per_chan[idx].push_back(fwhm_pct);
2142 Double_t eres_vals[35];
2143 for (Int_t i = 0; i < n_eres; i++)
2144 eres_vals[i] = -1.0;
2145 for (Int_t i = 0; i < n_eres; i++) {
2146 std::vector<Double_t> &v = fwhm_per_chan[i];
2149 std::sort(v.begin(), v.end());
2150 Int_t m = Int_t(v.size());
2151 eres_vals[i] = (m % 2 == 1) ? v[m / 2] : 0.5 * (v[m / 2 - 1] + v[m / 2]);
2153 std::cout <<
"Run " << run <<
": writing per-channel %FWHM medians"
2160 InitUtils::SetROOTPreferences(PlotSaveFormat::kPNG,
2163 gROOT->SetBatch(kTRUE);
2165 std::vector<FileSpec> specs;
2166 if (file_label.IsNull()) {
2168 if (specs.empty()) {
2169 std::cerr <<
"No file specs from FileSet::BuildProcessedFileSpecs()"
2176 std::cerr <<
"Could not resolve file label '" << file_label <<
"'"
2185 Int_t n_specs = Int_t(specs.size());
2187 std::set<Int_t> runs;
2188 for (Int_t k = 0; k < n_specs; k++)
2189 runs.insert(specs[k].run);
2192 TMath::Min(Int_t(std::thread::hardware_concurrency()), n_specs);
2193 n_workers = TMath::Min(n_workers,
Constants::cfg.MAX_FUSED_WORKERS);
2196 std::cout <<
"calibrate-beam: " << n_specs <<
" subfiles on " << n_workers
2197 <<
" workers" << std::endl;
2199 std::queue<Int_t> work;
2200 for (Int_t k = 0; k < n_specs; k++)
2202 std::mutex work_mutex;
2204 std::vector<std::thread> workers;
2205 for (Int_t w = 0; w < n_workers; w++) {
2206 workers.emplace_back([&]() {
2210 std::lock_guard<std::mutex> lk(work_mutex);
2220 for (Int_t w = 0; w < Int_t(workers.size()); w++)
2223 for (std::set<Int_t>::const_iterator it = runs.begin(); it != runs.end();
2225 std::vector<FileSpec> run_specs;
2226 for (Int_t k = 0; k < n_specs; k++)
2227 if (specs[k].run == *it)
2228 run_specs.push_back(specs[k]);
void ComputeLRGainMatch(std::vector< ChannelCal > &chans, const StripPairSamples pairs[18], const TString &plot_subdir)
void DeriveBeamEnergyWindow(const std::vector< ChannelCal > &chans, StripAlignmentResult &align)
const Long64_t kRidgeMinPerSlice
const Double_t kRidgeBandHi
void RobustPeakSeed(const std::vector< Float_t > &v, Double_t &mode, Double_t &sigma)
const Double_t kRidgeRatioLo
TH1F * MakeBeamPeakHist(const TString &name, const TString &title, const std::vector< Float_t > &v, Double_t mode, Double_t sigma)
BeamFit2D FindBeamGateStrips(const FileSpec &spec, Int_t sx, Int_t sy, const TString &run_label, const TString &plot_subdir, Bool_t save_plot=kTRUE)
Bool_t TheilSenLine(const std::vector< Double_t > &x, const std::vector< Double_t > &y, Double_t &slope, Double_t &intercept)
const Double_t kRidgeBandLo
Double_t RidgeShortAnchor(const std::vector< Float_t > &v_short, const std::vector< Float_t > &v_long, Double_t c_long, Double_t &slope_out, Double_t &intercept_out, RidgeFit *dbg=nullptr)
const Long64_t kMinSamples
Bool_t FitBeamPeakGaussian(const std::vector< Float_t > &v, const TString &fname, Double_t &peak_adc, Double_t &sigma_adc, TF1 *&fit_out)
void ReduceToAnchors(std::vector< ChannelCal > &chans, std::vector< std::vector< Float_t > > &samples, std::vector< TF1 * > &fits_out, const TString &run_label, const StripPairSamples pairs[18], const TString &plot_subdir)
Double_t StripTotalAdc(const UShort_t *l, const UShort_t *r, Int_t s)
Double_t GmFindPeak(const std::vector< Float_t > &v, Double_t lo, Double_t hi, Double_t skip_frac)
Bool_t IsBeamdEChannel(const ChannelCal &c)
Char_t LongSide(Int_t strip)
const Long64_t kSampleCap
void WriteEresTomlRaw(const TString &out_subpath, const Double_t eres_vals[35])
void SaveBeamPeakChannelHistograms(const std::vector< ChannelCal > &chans, const std::vector< std::vector< Float_t > > &samples, const std::vector< TF1 * > &fits, const TString &plot_subdir)
Double_t ApplyCal(const ChannelCal &c, Double_t adc)
Double_t InterquartileRange(std::vector< Float_t > &v)
void WriteCalibrationTree(TFile *dst, const std::vector< ChannelCal > &chans, const StripAlignmentResult *align)
Int_t GatePartner(Int_t s)
void SaveRidgeFitPlots(const StripPairSamples pairs[18], const RidgeFit dbg[18], const TString &plot_subdir)
void CollectAnchorSamplesOneSubfile(const FileSpec &spec, const std::vector< ChannelCal > &chans, const BeamFit2D gate[18], std::vector< std::vector< Float_t > > &samples, StripPairSamples pairs[18])
StripAlignmentResult FindStripCentroidAlignment(const FileSpec &spec, const TString &plot_subdir, const TString &file_label)
const Double_t kRidgeShortMaxFrac
Double_t Median(std::vector< Float_t > &v)
Double_t ResolutionFWHMPercent(const ChannelCal &c)
Bool_t FitGaussianMuSigma(const std::vector< Float_t > &v, const TString &fname, Double_t &mu, Double_t &sigma)
Bool_t IsCalibrated(const ChannelCal &c)
void SaveDynamicRangeOverlay(const FileSpec &spec, const std::vector< ChannelCal > &chans, const TString &plot_subdir, const TString &file_label)
Int_t ChannelToEresIndex(const ChannelCal &c)
Double_t Gain(const ChannelCal &c)
const Double_t kRidgeRatioHi
const Long64_t kRidgeNoiseFloor
void WriteCalibrationToEvents(const FileSpec &spec, const std::vector< ChannelCal > &chans, const StripAlignmentResult *align)
std::mutex g_plot_mutex
Serialises all plotting and canvas work.
static Moments2D ComputeMoments(TH2F *h, Int_t lo_bx, Int_t hi_bx, Int_t lo_by, Int_t hi_by, Double_t thresh, Double_t bw_x, Double_t bw_y)
Second moments of a histogram region above a threshold.
static Bool_t InEllipseXY(const BeamFit2D &b, Double_t x, Double_t y, Double_t nx, Double_t ny)
Whether a point lies inside the fitted beam ellipse.
static void SaveCalibSampleOverlay(const std::vector< ChannelCal > &chans, const std::vector< std::vector< Float_t > > &samples, const TString &plot_subdir, const TString &file_label)
Save the per-channel calibration overlay figure.
static void Run(const TString &file_label="")
Calibrate every configured subfile, then aggregate per run.
static void CalibrateBeamOneSubfile(const FileSpec &spec, const std::vector< ChannelCal > &chans_template)
Calibrate one subfile end to end.
static void AggregateEresTomlForRun(Int_t run, const std::vector< FileSpec > &specs)
Aggregate the run's energy-resolution measurements into its TOML.
static std::vector< ChannelCal > BuildChannels()
One ChannelCal per readout channel in the active channel map.
static void AggregateRidgeRatiosForRun(Int_t run, const std::vector< FileSpec > &specs)
Replace each subfile's ridge ratio with the run-level median.
static TString EventsName(const FileSpec &s)
Filename of the built-events ROOT file for a subfile.
static TString FileLabel(const FileSpec &s)
Human-readable label identifying a subfile.
static FileSpec ResolveFileSpec(const TString &file_label)
Recover the FileSpec behind a label from FileLabel().
static std::vector< FileSpec > BuildProcessedFileSpecs()
Every subfile that already has processed output.
static TString DatasetDir()
Absolute path to the active dataset directory, analysis/<iso>.
static TString ResultsDir()
Absolute path to the directory receiving generated output.
Double_t ActiveStripEMaxAdc()
Upper bound of the per-strip energy range, in ADC.
const DatasetConfig & cfg
The active dataset's configuration, flat block.
A fitted 2-D Gaussian beam spot.
Double_t rho
Correlation coefficient, in [-1, 1].
Double_t sigma_y
Widths along each axis.
Double_t amp
Peak amplitude.
Bool_t ok
Whether the fit converged.
One readout channel's beam-peak calibration.
Char_t side
Readout end: 'L' or 'R'.
TString name
Channel name from the active channel map.
Double_t gain
Left/right gain-match override.
Double_t fit_adc
Fitted beam-peak centroid, in ADC.
Int_t strip
Anode strip index this channel reads.
Long64_t n_samples
Events entering the fit.
Double_t ridge_ratio
Short channels only: C_short / C_long from this subfile's own ridge fit, or 0 when the ridge was not ...
Double_t fit_sigma_adc
Fitted beam-peak width, in ADC.
A view over one event's energies, decoding raw ADC into calibrated units.
void Decode()
Decode the currently loaded entry into the value members.
Bool_t Attach(TTree *t)
Bind to an events tree and set up the branch addresses.
Double_t total[18]
Summed energy per strip, after strip_factor.
Bool_t is_normed
Whether a calibration tree was found.
One input file: a run number and the subfile suffix within it.
Second moments of a 2-D distribution over a bin range.
Double_t rho
Correlation coefficient, in [-1, 1].
Double_t sigma_y
Weighted RMS widths.
Double_t mu_y
Weighted centroid.
Double_t weight
Total weight included; zero means the range held nothing above threshold.
std::vector< Double_t > y
std::vector< Double_t > ey
std::vector< Double_t > x
Output of the two post-gain calibration steps.
Double_t centroids[18]
Measured beam-peak centroid per strip, in calibrated units.
Double_t beam_e_max
Upper edge of the beam window.
Double_t factors[18]
Per-strip multiplicative alignment factors.
Double_t beam_e_min
Lower edge of the beam window, calibrated units.
Bool_t ok
Whether the steps produced usable results.
std::vector< Float_t > gated_long
std::vector< Float_t > gated_short