MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
BeamFit2D.cpp
Go to the documentation of this file.
1#include "BeamFit2D.hpp"
2#include <TEllipse.h>
3#include <TF1.h>
4#include <TF2.h>
5#include <TFitResult.h>
6#include <TFitResultPtr.h>
7#include <TMath.h>
8
9Bool_t BeamFitUtils::InEllipseXY(const BeamFit2D &b, Double_t x, Double_t y,
10 Double_t n) {
11 Double_t dx = x - b.mu_x;
12 Double_t dy = y - b.mu_y;
13 Double_t sx = b.sigma_x, sy = b.sigma_y, rho = b.rho;
14 if (sx <= 0 || sy <= 0)
15 return kFALSE;
16 // Correlated 2D Gaussian (Mahalanobis) chi2: inside n sigma is chi2 < n^2.
17 Double_t dx_s = dx / sx, dy_s = dy / sy;
18 Double_t r2 = rho * rho;
19 Double_t chi2 =
20 (dx_s * dx_s + dy_s * dy_s - 2.0 * rho * dx_s * dy_s) / (1.0 - r2);
21 return chi2 < n * n;
22}
23
24void BeamFitUtils::DrawEllipse(const BeamFit2D &b, Double_t n, Color_t color) {
25 Double_t sxx = b.sigma_x * b.sigma_x;
26 Double_t syy = b.sigma_y * b.sigma_y;
27 Double_t sxy = b.rho * b.sigma_x * b.sigma_y;
28 Double_t sum = sxx + syy;
29 Double_t diff = sxx - syy;
30 Double_t det = TMath::Sqrt(diff * diff + 4.0 * sxy * sxy);
31 Double_t lambda1 = 0.5 * (sum + det);
32 Double_t lambda2 = 0.5 * (sum - det);
33 Double_t theta = 0.5 * TMath::ATan2(2.0 * sxy, diff) * 180.0 / TMath::Pi();
34 TEllipse *e = new TEllipse(b.mu_x, b.mu_y, n * TMath::Sqrt(lambda1),
35 n * TMath::Sqrt(lambda2), 0, 360, theta);
36 e->SetFillStyle(0);
37 e->SetLineColor(color);
38 e->SetLineWidth(2);
39 e->Draw();
40}
41
42Moments2D BeamFitUtils::ComputeMoments(TH2F *h, Int_t lo_bx, Int_t hi_bx,
43 Int_t lo_by, Int_t hi_by,
44 Double_t thresh, Double_t bw_x,
45 Double_t bw_y) {
46 Moments2D m;
47 Double_t W = 0, Mx = 0, My = 0, Cxx = 0, Cyy = 0, Cxy = 0;
48 for (Int_t ix = lo_bx; ix <= hi_bx; ix++) {
49 Double_t x = h->GetXaxis()->GetBinCenter(ix);
50 for (Int_t iy = lo_by; iy <= hi_by; iy++) {
51 Double_t w = h->GetBinContent(ix, iy);
52 if (w < thresh)
53 continue;
54 Double_t y = h->GetYaxis()->GetBinCenter(iy);
55 W += w;
56 Mx += w * x;
57 My += w * y;
58 }
59 }
60 if (W <= 0)
61 return m;
62 Mx /= W;
63 My /= W;
64 for (Int_t ix = lo_bx; ix <= hi_bx; ix++) {
65 Double_t x = h->GetXaxis()->GetBinCenter(ix);
66 for (Int_t iy = lo_by; iy <= hi_by; iy++) {
67 Double_t w = h->GetBinContent(ix, iy);
68 if (w < thresh)
69 continue;
70 Double_t y = h->GetYaxis()->GetBinCenter(iy);
71 Double_t dx = x - Mx, dy = y - My;
72 Cxx += w * dx * dx;
73 Cyy += w * dy * dy;
74 Cxy += w * dx * dy;
75 }
76 }
77 Cxx /= W;
78 Cyy /= W;
79 Cxy /= W;
80 m.mu_x = Mx;
81 m.mu_y = My;
82 m.sigma_x = std::max(std::sqrt(std::max(Cxx, 0.0)), 2.0 * bw_x);
83 m.sigma_y = std::max(std::sqrt(std::max(Cyy, 0.0)), 2.0 * bw_y);
84 Double_t r = Cxy / (m.sigma_x * m.sigma_y);
85 if (r > 0.95)
86 r = 0.95;
87 if (r < -0.95)
88 r = -0.95;
89 m.rho = r;
90 m.weight = W;
91 return m;
92}
93
95 const std::vector<std::pair<Float_t, Float_t>> &pts, const Moments2D &seed,
96 Double_t clip) {
97 const Int_t kMaxPasses = 50;
98 const Double_t kTolerance = 1.0e-4;
99 // A 2-D Gaussian's r^2 is exponential with mean 2: inside radius c each
100 // axis keeps 0.949 of its variance at c = 3.
101 const Double_t e = std::exp(-0.5 * clip * clip);
102 const Double_t kept_var = 1.0 - 0.5 * clip * clip * e / (1.0 - e);
103 Moments2D m = seed;
104 for (Int_t pass = 0; pass < kMaxPasses; pass++) {
105 if (!(m.sigma_x > 0.0 && m.sigma_y > 0.0))
106 return seed;
107 Double_t W = 0, Sx = 0, Sy = 0, Sxx = 0, Syy = 0, Sxy = 0;
108 const Double_t one_minus_r2 = 1.0 - m.rho * m.rho;
109 for (size_t k = 0; k < pts.size(); k++) {
110 const Double_t x = pts[k].first, y = pts[k].second;
111 const Double_t u = (x - m.mu_x) / m.sigma_x;
112 const Double_t v = (y - m.mu_y) / m.sigma_y;
113 if ((u * u + v * v - 2.0 * m.rho * u * v) / one_minus_r2 >= clip * clip)
114 continue;
115 W += 1.0;
116 Sx += x;
117 Sy += y;
118 Sxx += x * x;
119 Syy += y * y;
120 Sxy += x * y;
121 }
122 if (W < 10.0)
123 return seed;
124 Moments2D next;
125 next.mu_x = Sx / W;
126 next.mu_y = Sy / W;
127 const Double_t cxx = std::max(Sxx / W - next.mu_x * next.mu_x, 0.0);
128 const Double_t cyy = std::max(Syy / W - next.mu_y * next.mu_y, 0.0);
129 const Double_t cxy = Sxy / W - next.mu_x * next.mu_y;
130 next.sigma_x = std::sqrt(cxx / kept_var);
131 next.sigma_y = std::sqrt(cyy / kept_var);
132 if (!(next.sigma_x > 0.0 && next.sigma_y > 0.0))
133 return seed;
134 next.rho = std::max(-0.95, std::min(0.95, cxy / std::sqrt(cxx * cyy)));
135 next.weight = W;
136 const Bool_t done =
137 std::fabs(next.sigma_x / m.sigma_x - 1.0) < kTolerance &&
138 std::fabs(next.sigma_y / m.sigma_y - 1.0) < kTolerance &&
139 std::fabs(next.rho - m.rho) < kTolerance;
140 m = next;
141 if (done)
142 break;
143 }
144 return m;
145}
146
147Moments2D BeamFitUtils::SeedSpotMoments(TH2F *h, Int_t seed_half_bins) {
148 // The seed threshold: a fraction of the peak bin.
149 const Double_t kSeedFrac = 0.3;
150 const Double_t bw_x = h->GetXaxis()->GetBinWidth(1);
151 const Double_t bw_y = h->GetYaxis()->GetBinWidth(1);
152 Int_t bx, by, bz;
153 h->GetMaximumBin(bx, by, bz);
154 const Double_t peak_val = h->GetBinContent(bx, by);
155 Int_t lo_bx = std::max(1, bx - seed_half_bins);
156 Int_t hi_bx = std::min(h->GetNbinsX(), bx + seed_half_bins);
157 Int_t lo_by = std::max(1, by - seed_half_bins);
158 Int_t hi_by = std::min(h->GetNbinsY(), by + seed_half_bins);
159 return ComputeMoments(h, lo_bx, hi_bx, lo_by, hi_by, kSeedFrac * peak_val,
160 bw_x, bw_y);
161}
162
165 std::vector<std::pair<Float_t, Float_t>> &pts,
166 const Moments2D &seed) {
167 // The core moments only seed the clip; the clip seeds the fit and is the
168 // fallback.
169 Moments2D m = ClippedMoments(pts, seed);
170
171 // The width the fit reports is a fitted sigma on the inner kFitWindow,
172 // finely binned so pileup and the reaction shoulder stay out of it.
173 const Double_t kSpotWindow = 4.0;
174 const Double_t kFitWindow = 2.0;
175 const Int_t kSpotBins = 160;
176 TH2F *hf = new TH2F(Form("%s_fit", h->GetName()), "", kSpotBins,
177 m.mu_x - kSpotWindow * m.sigma_x,
178 m.mu_x + kSpotWindow * m.sigma_x, kSpotBins,
179 m.mu_y - kSpotWindow * m.sigma_y,
180 m.mu_y + kSpotWindow * m.sigma_y);
181 hf->SetDirectory(nullptr);
182 for (size_t k = 0; k < pts.size(); k++)
183 hf->Fill(pts[k].first, pts[k].second);
184 std::vector<std::pair<Float_t, Float_t>>().swap(pts);
185 SpotFit out;
186 BeamFit2D fit = FitSpot(hf, m, kFitWindow, &out.chi2_ndf);
187 delete hf;
188 if (fit.ok) {
189 out.fit = fit;
190 out.fit_used = kTRUE;
191 return out;
192 }
193 // A fit that ran away is dropped for the clipped moments.
194 Int_t bx, by, bz;
195 h->GetMaximumBin(bx, by, bz);
196 out.fit.amp = h->GetBinContent(bx, by);
197 out.fit.mu_x = m.mu_x;
198 out.fit.mu_y = m.mu_y;
199 out.fit.sigma_x = m.sigma_x;
200 out.fit.sigma_y = m.sigma_y;
201 out.fit.rho = m.rho;
202 out.fit.ok = kTRUE;
203 return out;
204}
205
207 Double_t window_nsigma, Double_t *chi2_ndf) {
208 // Off the limits by this much still counts as a parameter Minuit chose.
209 const Double_t kOffLimit = 1.0e-3;
210 const Double_t kSigmaLo = 0.2, kSigmaHi = 5.0;
211 BeamFit2D out;
212 if (chi2_ndf)
213 *chi2_ndf = -1.0;
214 if (!h || !(seed.sigma_x > 0.0 && seed.sigma_y > 0.0))
215 return out;
216
217 const Double_t x_lo = seed.mu_x - window_nsigma * seed.sigma_x;
218 const Double_t x_hi = seed.mu_x + window_nsigma * seed.sigma_x;
219 const Double_t y_lo = seed.mu_y - window_nsigma * seed.sigma_y;
220 const Double_t y_hi = seed.mu_y + window_nsigma * seed.sigma_y;
221 const Double_t peak = TMath::Max(h->GetMaximum(), 1.0);
222
223 // bigaus in its parameter order, plus [6], a flat pedestal.
224 TF2 f(Form("beamspot_fit_%s", h->GetName()),
225 "[0]*exp(-0.5/(1.0-[5]*[5])*((x-[1])*(x-[1])/([2]*[2])"
226 "+(y-[3])*(y-[3])/([4]*[4])"
227 "-2.0*[5]*(x-[1])*(y-[3])/([2]*[4])))+[6]",
228 x_lo, x_hi, y_lo, y_hi);
229 f.SetParameters(peak, seed.mu_x, seed.sigma_x, seed.mu_y, seed.sigma_y,
230 TMath::Max(-0.9, TMath::Min(0.9, seed.rho)), 0.0);
231 // Unbounded, Minuit runs the amplitude to a bound and walks the mean out.
232 f.SetParLimits(0, 0.0, 1.0e3 * peak);
233 f.SetParLimits(1, x_lo, x_hi);
234 f.SetParLimits(2, kSigmaLo * seed.sigma_x, kSigmaHi * seed.sigma_x);
235 f.SetParLimits(3, y_lo, y_hi);
236 f.SetParLimits(4, kSigmaLo * seed.sigma_y, kSigmaHi * seed.sigma_y);
237 f.SetParLimits(5, -0.95, 0.95);
238 f.SetParLimits(6, 0.0, peak);
239 // "R" fits the window only; the rest of h is the goodness check below.
240 TFitResultPtr r = h->Fit(&f, "QNRSL");
241 if (!r.Get() || !r->IsValid())
242 return out;
243
244 // Judged over the whole histogram: a shoulder the window kept out of the
245 // width still shows here.
246 if (chi2_ndf) {
247 Double_t chi2 = 0.0;
248 Int_t nbin = 0;
249 for (Int_t ix = 1; ix <= h->GetNbinsX(); ix++)
250 for (Int_t iy = 1; iy <= h->GetNbinsY(); iy++) {
251 const Double_t e = f.Eval(h->GetXaxis()->GetBinCenter(ix),
252 h->GetYaxis()->GetBinCenter(iy));
253 if (!(e > 1.0))
254 continue;
255 const Double_t o = h->GetBinContent(ix, iy);
256 chi2 += (o - e) * (o - e) / e;
257 nbin++;
258 }
259 if (nbin > 7)
260 *chi2_ndf = chi2 / Double_t(nbin - 7);
261 }
262
263 const Double_t sx = TMath::Abs(f.GetParameter(2));
264 const Double_t sy = TMath::Abs(f.GetParameter(4));
265 const Double_t mx = f.GetParameter(1), my = f.GetParameter(3);
266 const Double_t rho = f.GetParameter(5);
267 // Minuit reports success on fits that ran away: every parameter off its
268 // limit, and finite.
269 const Bool_t sane =
270 TMath::Finite(sx) && TMath::Finite(sy) && TMath::Finite(rho) &&
271 sx > (kSigmaLo + kOffLimit) * seed.sigma_x &&
272 sx < (kSigmaHi - kOffLimit) * seed.sigma_x &&
273 sy > (kSigmaLo + kOffLimit) * seed.sigma_y &&
274 sy < (kSigmaHi - kOffLimit) * seed.sigma_y && TMath::Abs(rho) < 0.94 &&
275 mx > x_lo && mx < x_hi && my > y_lo && my < y_hi;
276 if (!sane)
277 return out;
278
279 out.amp = f.GetParameter(0);
280 out.mu_x = mx;
281 out.sigma_x = sx;
282 out.mu_y = my;
283 out.sigma_y = sy;
284 out.rho = rho;
285 out.ok = kTRUE;
286 return out;
287}
288
289Bool_t BeamFitUtils::FitPeak(TH1F *h, Double_t seed_mu, Double_t seed_sigma,
290 Double_t window_nsigma, Bool_t fix_mu,
291 Double_t &mu, Double_t &sigma,
292 Double_t *chi2_ndf) {
293 const Double_t kOffLimit = 1.0e-3;
294 const Double_t kSigmaLo = 0.2, kSigmaHi = 5.0;
295 if (chi2_ndf)
296 *chi2_ndf = -1.0;
297 if (!h || !(seed_sigma > 0.0))
298 return kFALSE;
299 const Double_t lo = seed_mu - window_nsigma * seed_sigma;
300 const Double_t hi = seed_mu + window_nsigma * seed_sigma;
301 const Double_t peak = TMath::Max(h->GetMaximum(), 1.0);
302 TF1 f(Form("beampeak_fit_%s", h->GetName()), "gaus(0)+[3]", lo, hi);
303 f.SetParameters(peak, seed_mu, seed_sigma, 0.0);
304 f.SetParLimits(0, 0.0, 1.0e3 * peak);
305 f.SetParLimits(1, lo, hi);
306 f.SetParLimits(2, kSigmaLo * seed_sigma, kSigmaHi * seed_sigma);
307 f.SetParLimits(3, 0.0, peak);
308 if (fix_mu)
309 f.FixParameter(1, seed_mu);
310 TFitResultPtr r = h->Fit(&f, "QNRSL");
311 if (!r.Get() || !r->IsValid())
312 return kFALSE;
313 if (chi2_ndf) {
314 Double_t chi2 = 0.0;
315 Int_t nbin = 0;
316 for (Int_t ix = 1; ix <= h->GetNbinsX(); ix++) {
317 const Double_t e = f.Eval(h->GetXaxis()->GetBinCenter(ix));
318 if (!(e > 1.0))
319 continue;
320 const Double_t o = h->GetBinContent(ix);
321 chi2 += (o - e) * (o - e) / e;
322 nbin++;
323 }
324 if (nbin > 4)
325 *chi2_ndf = chi2 / Double_t(nbin - 4);
326 }
327 const Double_t fit_mu = f.GetParameter(1);
328 const Double_t fit_sigma = TMath::Abs(f.GetParameter(2));
329 // Minuit reports success on fits that ran away: every parameter off its
330 // limit, and finite.
331 const Bool_t sane = TMath::Finite(fit_mu) && TMath::Finite(fit_sigma) &&
332 fit_sigma > (kSigmaLo + kOffLimit) * seed_sigma &&
333 fit_sigma < (kSigmaHi - kOffLimit) * seed_sigma &&
334 fit_mu > lo && fit_mu < hi;
335 if (!sane)
336 return kFALSE;
337 mu = fit_mu;
338 sigma = fit_sigma;
339 return kTRUE;
340}
static BeamFit2D FitSpot(TH2F *h, const Moments2D &seed, Double_t window_nsigma=2.0, Double_t *chi2_ndf=nullptr)
Fit a correlated 2-D Gaussian on a flat pedestal to h: ROOT's bigaus written out, Poisson likelihood,...
static SpotFit FitSpotFromPoints(TH2F *h, std::vector< std::pair< Float_t, Float_t > > &pts, const Moments2D &seed)
A beam spot from a coarse histogram and the same events unbinned: the sample's sigma-clipped moments ...
static Bool_t FitPeak(TH1F *h, Double_t seed_mu, Double_t seed_sigma, Double_t window_nsigma, Bool_t fix_mu, Double_t &mu, Double_t &sigma, Double_t *chi2_ndf=nullptr)
The 1-D counterpart: a Gaussian on a flat pedestal, fitted over window_nsigma of the seed and judged ...
static void DrawEllipse(const BeamFit2D &b, Double_t n, Color_t color)
Draw the n-sigma contour of a fitted spot on the current pad.
Definition BeamFit2D.cpp:24
static Bool_t InEllipseXY(const BeamFit2D &b, Double_t x, Double_t y, Double_t n)
Whether a point lies inside the fitted ellipse at n sigma: the correlation-aware Mahalanobis chi2 bel...
Definition BeamFit2D.cpp:9
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.
Definition BeamFit2D.cpp:42
static Moments2D SeedSpotMoments(TH2F *h, Int_t seed_half_bins)
Second moments of a coarse spot histogram's seed window: the maximum bin, ± seed_half_bins around it,...
static Moments2D ClippedMoments(const std::vector< std::pair< Float_t, Float_t > > &pts, const Moments2D &seed, Double_t clip=3.0)
Sigma-clipped centroid and covariance of a point sample, the 2-D counterpart of the strip-width clip:...
Definition BeamFit2D.cpp:94
A fitted 2-D Gaussian beam spot.
Definition BeamFit2D.hpp:19
Double_t rho
Correlation coefficient, in [-1, 1].
Definition BeamFit2D.hpp:23
Double_t sigma_y
Widths along each axis.
Definition BeamFit2D.hpp:22
Double_t mu_x
Definition BeamFit2D.hpp:21
Double_t amp
Peak amplitude.
Definition BeamFit2D.hpp:20
Double_t mu_y
Centroid.
Definition BeamFit2D.hpp:21
Bool_t ok
Whether the fit converged.
Definition BeamFit2D.hpp:24
Double_t sigma_x
Definition BeamFit2D.hpp:22
Result of a beam spot fit: the spot to use, and whether the Gaussian fit (rather than the clipped-mom...
Double_t chi2_ndf
The fit's reduced chi2; -1 when it did not run.
Bool_t fit_used
kTRUE when fit is the fit, not the fallback.
BeamFit2D fit
The spot to use; ok when usable.
Second moments of a 2-D distribution over a bin range.
Definition BeamFit2D.hpp:33
Double_t rho
Correlation coefficient, in [-1, 1].
Definition BeamFit2D.hpp:36
Double_t sigma_y
Weighted RMS widths.
Definition BeamFit2D.hpp:35
Double_t sigma_x
Definition BeamFit2D.hpp:35
Double_t mu_x
Definition BeamFit2D.hpp:34
Double_t mu_y
Weighted centroid.
Definition BeamFit2D.hpp:34
Double_t weight
Total weight included; zero means the range held nothing above threshold.
Definition BeamFit2D.hpp:37