Analysis-Utilities 26.9.9
C++/ROOT utilities for nuclear measurement data analysis
Loading...
Searching...
No Matches
InteractiveFitEditor.cpp
Go to the documentation of this file.
2
4
5InteractiveFitEditor::InteractiveFitEditor(const TGWindow *parent, TH1 *hist,
6 TF1 *fit_func, Double_t range_low,
7 Double_t range_high, Int_t num_peaks,
8 const TString &info_label)
9 : TGMainFrame(parent, 1400, 900) {
10
11 hist_ = hist;
12 fit_func_ = fit_func;
13 info_label_text_ = info_label;
14 range_low_ = range_low;
15 range_high_ = range_high;
16 original_range_low_ = range_low;
17 original_range_high_ = range_high;
18 hist_x_min_ = hist->GetXaxis()->GetXmin();
19 hist_x_max_ = hist->GetXaxis()->GetXmax();
20 num_peaks_ = num_peaks;
21 num_params_ = num_peaks * 10 + 2;
22
23 accepted_ = kFALSE;
24 done_ = kFALSE;
25 needs_redraw_ = kFALSE;
26 syncing_ = kFALSE;
27
28 if (fit_func_->GetNpar() != num_params_) {
29 std::cerr << "InteractiveFitEditor: expected " << num_params_
30 << " parameters but TF1 has " << fit_func_->GetNpar()
31 << std::endl;
32 }
33
34 // Save original state
35 original_params_ = new Double_t[num_params_];
36 original_bounds_low_ = new Double_t[num_params_];
37 original_bounds_high_ = new Double_t[num_params_];
38 original_fixed_ = new Bool_t[num_params_];
39 current_bounds_low_ = new Double_t[num_params_];
40 current_bounds_high_ = new Double_t[num_params_];
41
42 for (Int_t i = 0; i < num_params_; i++) {
43 original_params_[i] = fit_func_->GetParameter(i);
44
45 Double_t lo = 0, hi = 0;
46 fit_func_->GetParLimits(i, lo, hi);
47
48 // lo >= hi means the parameter is fixed (FixParameter sets lo == hi)
49 original_fixed_[i] = (lo >= hi);
50
51 if (original_fixed_[i]) {
52 // FixParameter overwrites limits; infer reasonable defaults
53 GetDefaultBounds(i, lo, hi);
54 }
55
56 original_bounds_low_[i] = lo;
57 original_bounds_high_[i] = hi;
58 current_bounds_low_[i] = lo;
59 current_bounds_high_[i] = hi;
60 }
61
62 // Allocate widget arrays
63 sliders_ = new TGHSlider *[num_params_];
64 value_entries_ = new TGNumberEntry *[num_params_];
65 fix_checks_ = new TGCheckButton *[num_params_];
66 lo_bound_entries_ = new TGNumberEntry *[num_params_];
67 hi_bound_entries_ = new TGNumberEntry *[num_params_];
68
69 hist_draw_ = nullptr;
70 bkg_draw_ = nullptr;
71 res_graph_ = nullptr;
72 zero_line_ = nullptr;
73 plus3_line_ = nullptr;
74 minus3_line_ = nullptr;
75 chi2_label_ = nullptr;
76 n_res_points_ = 0;
77 for (Int_t p = 0; p < 3; p++) {
78 for (Int_t c = 0; c < 4; c++) {
79 comp_graphs_[p][c] = nullptr;
80 }
81 }
82
83 BuildGUI();
84 InitDrawing();
85
86 redraw_timer_ = new TTimer(this, 50);
87 redraw_timer_->TurnOn();
88
89 SetWindowName("Interactive Fit Editor");
90 MapSubwindows();
91 Resize(GetDefaultSize());
92 MapWindow();
93
94 UpdateCanvas();
95}
96
98 if (redraw_timer_) {
99 redraw_timer_->TurnOff();
100 delete redraw_timer_;
101 }
102
103 delete[] original_params_;
104 delete[] original_bounds_low_;
105 delete[] original_bounds_high_;
106 delete[] original_fixed_;
107 delete[] current_bounds_low_;
108 delete[] current_bounds_high_;
109
110 delete[] sliders_;
111 delete[] value_entries_;
112 delete[] fix_checks_;
113 delete[] lo_bound_entries_;
114 delete[] hi_bound_entries_;
115
116 // Drawing primitives drawn on the embedded canvas — TPad does not
117 // auto-delete user primitives, so they have to go by hand.
118 //
119 // Pre-clear each pad's primitives list with "nodelete" before destroying
120 // the primitives themselves. The launcher removes the embedded canvas
121 // from gROOT->GetListOfCanvases() to suppress X11 errors during teardown,
122 // which also disables RecursiveRemove's cleanup of pad lists; without
123 // this manual Clear, ~TPad later walks stale pointers and segfaults.
124 if (main_pad_ && main_pad_->GetListOfPrimitives()) {
125 main_pad_->GetListOfPrimitives()->Clear("nodelete");
126 }
127 if (residual_pad_ && residual_pad_->GetListOfPrimitives()) {
128 residual_pad_->GetListOfPrimitives()->Clear("nodelete");
129 }
130 for (Int_t p = 0; p < 3; p++) {
131 for (Int_t c = 0; c < 4; c++) {
132 delete comp_graphs_[p][c];
133 }
134 }
135 delete chi2_label_;
136 delete zero_line_;
137 delete plus3_line_;
138 delete minus3_line_;
139 delete res_graph_;
140 delete bkg_draw_;
141 delete hist_draw_;
142 delete residual_pad_;
143 delete main_pad_;
144}
145
146// GUI Construction
147
148void InteractiveFitEditor::BuildGUI() {
149 // Main horizontal split: canvas (left) | controls (right)
150 TGHorizontalFrame *main_frame = new TGHorizontalFrame(this, 1400, 850);
151 AddFrame(main_frame,
152 new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2));
153
154 embedded_canvas_ =
155 new TRootEmbeddedCanvas("FitEditorCanvas", main_frame, 850, 800);
156 main_frame->AddFrame(
157 embedded_canvas_,
158 new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2));
159
160 TGVerticalFrame *controls = new TGVerticalFrame(main_frame, 520, 800);
161 main_frame->AddFrame(
162 controls, new TGLayoutHints(kLHintsExpandY | kLHintsRight, 2, 2, 2, 2));
163
164 if (info_label_text_.Length() > 0) {
165 TGGroupFrame *info_grp = new TGGroupFrame(controls, "Info", kVerticalFrame);
166 controls->AddFrame(info_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 3, 3));
167 TGLabel *info_label = new TGLabel(info_grp, info_label_text_);
168 info_grp->AddFrame(info_label,
169 new TGLayoutHints(kLHintsCenterX, 4, 4, 4, 4));
170 }
171
172 TGGroupFrame *range_grp =
173 new TGGroupFrame(controls, "Fit Range", kVerticalFrame);
174 controls->AddFrame(range_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 3, 3));
175
176 range_slider_ =
177 new TGDoubleHSlider(range_grp, 200, kDoubleScaleNo, kRangeSlider);
178 range_slider_->SetRange(hist_x_min_, hist_x_max_);
179 range_slider_->SetPosition(range_low_, range_high_);
180 range_slider_->Associate(this);
181 range_grp->AddFrame(range_slider_,
182 new TGLayoutHints(kLHintsExpandX, 2, 2, 2, 2));
183
184 TGHorizontalFrame *range_entries = new TGHorizontalFrame(range_grp, 200, 28);
185 range_grp->AddFrame(range_entries,
186 new TGLayoutHints(kLHintsExpandX, 2, 2, 2, 2));
187
188 TGLabel *range_lo_label = new TGLabel(range_entries, "Low:");
189 range_entries->AddFrame(range_lo_label,
190 new TGLayoutHints(kLHintsCenterY, 2, 2, 2, 2));
191 range_lo_entry_ = new TGNumberEntry(
192 range_entries, range_low_, 8, kRangeLoEntry, TGNumberFormat::kNESReal,
193 TGNumberFormat::kNEAAnyNumber, TGNumberFormat::kNELNoLimits);
194 range_lo_entry_->GetNumberEntry()->Associate(this);
195 range_entries->AddFrame(
196 range_lo_entry_,
197 new TGLayoutHints(kLHintsExpandX | kLHintsCenterY, 2, 2, 2, 2));
198
199 TGLabel *range_hi_label = new TGLabel(range_entries, "High:");
200 range_entries->AddFrame(range_hi_label,
201 new TGLayoutHints(kLHintsCenterY, 8, 2, 2, 2));
202 range_hi_entry_ = new TGNumberEntry(
203 range_entries, range_high_, 8, kRangeHiEntry, TGNumberFormat::kNESReal,
204 TGNumberFormat::kNEAAnyNumber, TGNumberFormat::kNELNoLimits);
205 range_hi_entry_->GetNumberEntry()->Associate(this);
206 range_entries->AddFrame(
207 range_hi_entry_,
208 new TGLayoutHints(kLHintsExpandX | kLHintsCenterY, 2, 2, 2, 2));
209
210 // Tab widget for peaks + background
211 TGTab *tabs = new TGTab(controls, 510, 700);
212 controls->AddFrame(
213 tabs, new TGLayoutHints(kLHintsExpandX | kLHintsExpandY, 2, 2, 2, 2));
214
215 for (Int_t p = 0; p < num_peaks_; p++) {
216 TString tab_name = TString::Format("Peak %d", p + 1);
217 TGCompositeFrame *tab_frame = tabs->AddTab(tab_name);
218 BuildPeakTab(tab_frame, p);
219 }
220
221 TGCompositeFrame *bkg_tab = tabs->AddTab("Background");
222 BuildBackgroundTab(bkg_tab);
223
224 TGHorizontalFrame *btn_frame = new TGHorizontalFrame(controls, 510, 40);
225 controls->AddFrame(
226 btn_frame, new TGLayoutHints(kLHintsExpandX | kLHintsBottom, 2, 2, 5, 5));
227
228 TGTextButton *refit_btn = new TGTextButton(btn_frame, "Refit", kBtnRefit);
229 refit_btn->Associate(this);
230 TGTextButton *accept_btn = new TGTextButton(btn_frame, "Accept", kBtnAccept);
231 accept_btn->Associate(this);
232 TGTextButton *cancel_btn = new TGTextButton(btn_frame, "Cancel", kBtnCancel);
233 cancel_btn->Associate(this);
234 TGTextButton *reset_btn = new TGTextButton(btn_frame, "Reset", kBtnReset);
235 reset_btn->Associate(this);
236
237 TGLayoutHints *btn_hints = new TGLayoutHints(kLHintsExpandX, 4, 4, 2, 2);
238
239 btn_frame->AddFrame(refit_btn, btn_hints);
240 btn_frame->AddFrame(accept_btn, btn_hints);
241 btn_frame->AddFrame(cancel_btn, btn_hints);
242 btn_frame->AddFrame(reset_btn, btn_hints);
243}
244
245void InteractiveFitEditor::BuildPeakTab(TGCompositeFrame *parent,
246 Int_t peak_idx) {
247 Int_t offset = peak_idx * 10;
248
249 // Gaussian group
250 TGGroupFrame *gaus_grp = new TGGroupFrame(parent, "Gaussian", kVerticalFrame);
251 parent->AddFrame(gaus_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 3, 1));
252 AddParamRow(gaus_grp, offset + 0, "Mu");
253 AddParamRow(gaus_grp, offset + 1, "Sigma");
254 AddParamRow(gaus_grp, offset + 2, "Amplitude");
255
256 // Step group
257 TGGroupFrame *step_grp = new TGGroupFrame(parent, "Step", kVerticalFrame);
258 parent->AddFrame(step_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 1, 1));
259 AddParamRow(step_grp, offset + 3, "Step Amp");
260
261 // Low tail group
262 TGGroupFrame *ltail_grp =
263 new TGGroupFrame(parent, "Low-Side Tails", kVerticalFrame);
264 parent->AddFrame(ltail_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 1, 1));
265 AddParamRow(ltail_grp, offset + 4, "Exp Amp");
266 AddParamRow(ltail_grp, offset + 5, "Exp Decay/Sigma");
267 AddParamRow(ltail_grp, offset + 6, "Lin Amp");
268 AddParamRow(ltail_grp, offset + 7, "Lin Slope");
269
270 // High tail group
271 TGGroupFrame *htail_grp =
272 new TGGroupFrame(parent, "High-Side Tail", kVerticalFrame);
273 parent->AddFrame(htail_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 1, 3));
274 AddParamRow(htail_grp, offset + 8, "Exp Amp");
275 AddParamRow(htail_grp, offset + 9, "Exp Decay/Sigma");
276}
277
278void InteractiveFitEditor::BuildBackgroundTab(TGCompositeFrame *parent) {
279 TGGroupFrame *bkg_grp =
280 new TGGroupFrame(parent, "Background", kVerticalFrame);
281 parent->AddFrame(bkg_grp, new TGLayoutHints(kLHintsExpandX, 3, 3, 3, 3));
282 AddParamRow(bkg_grp, BkgConstIdx(), "Constant");
283 AddParamRow(bkg_grp, BkgSlopeIdx(), "Slope");
284}
285
286void InteractiveFitEditor::AddParamRow(TGCompositeFrame *parent,
287 Int_t param_idx, const char *name) {
288 TGHorizontalFrame *row = new TGHorizontalFrame(parent, 490, 28);
289 parent->AddFrame(row, new TGLayoutHints(kLHintsExpandX, 1, 1, 1, 1));
290
291 Double_t val = fit_func_->GetParameter(param_idx);
292 Bool_t fixed = original_fixed_[param_idx];
293
294 // Label
295 TGLabel *label = new TGLabel(row, name);
296 row->AddFrame(label, new TGLayoutHints(kLHintsCenterY, 2, 4, 2, 2));
297 label->Resize(90, 20);
298
299 // Slider
300 TGHSlider *slider =
301 new TGHSlider(row, 140, kSlider1, kSliderBase + param_idx);
302 slider->SetRange(0, kSliderRes);
303 slider->SetPosition(ValToSlider(param_idx, val));
304 slider->Associate(this);
305 row->AddFrame(slider,
306 new TGLayoutHints(kLHintsExpandX | kLHintsCenterY, 2, 2, 2, 2));
307 sliders_[param_idx] = slider;
308
309 // Value entry
310 TGNumberEntry *entry = new TGNumberEntry(
311 row, val, 8, kEntryBase + param_idx, TGNumberFormat::kNESReal,
312 TGNumberFormat::kNEAAnyNumber, TGNumberFormat::kNELNoLimits);
313 entry->GetNumberEntry()->Associate(this);
314 row->AddFrame(entry, new TGLayoutHints(kLHintsCenterY, 2, 2, 2, 2));
315 value_entries_[param_idx] = entry;
316
317 // Fix checkbox
318 TGCheckButton *fix_cb = new TGCheckButton(row, "Fix", kFixBase + param_idx);
319 fix_cb->Associate(this);
320 if (fixed) {
321 fix_cb->SetState(kButtonDown);
322 }
323 row->AddFrame(fix_cb, new TGLayoutHints(kLHintsCenterY, 2, 2, 2, 2));
324 fix_checks_[param_idx] = fix_cb;
325
326 // Low bound entry
327 TGNumberEntry *lo_entry = new TGNumberEntry(
328 row, current_bounds_low_[param_idx], 6, kLoBoundBase + param_idx,
329 TGNumberFormat::kNESReal, TGNumberFormat::kNEAAnyNumber,
330 TGNumberFormat::kNELNoLimits);
331 lo_entry->GetNumberEntry()->Associate(this);
332 row->AddFrame(lo_entry, new TGLayoutHints(kLHintsCenterY, 1, 1, 2, 2));
333 lo_bound_entries_[param_idx] = lo_entry;
334
335 // High bound entry
336 TGNumberEntry *hi_entry = new TGNumberEntry(
337 row, current_bounds_high_[param_idx], 6, kHiBoundBase + param_idx,
338 TGNumberFormat::kNESReal, TGNumberFormat::kNEAAnyNumber,
339 TGNumberFormat::kNELNoLimits);
340 hi_entry->GetNumberEntry()->Associate(this);
341 row->AddFrame(hi_entry, new TGLayoutHints(kLHintsCenterY, 1, 1, 2, 2));
342 hi_bound_entries_[param_idx] = hi_entry;
343
344 // Disable slider and entry if parameter is fixed
345 if (fixed) {
346 slider->SetEnabled(kFALSE);
347 entry->GetNumberEntry()->SetEnabled(kFALSE);
348 }
349}
350
351// Drawing
352
353void InteractiveFitEditor::InitDrawing() {
354 TCanvas *canvas = embedded_canvas_->GetCanvas();
355 canvas->cd();
356
357 main_pad_ = new TPad("editor_main", "editor_main", 0, 0.3, 1, 1.0);
358 main_pad_->SetBottomMargin(0.04);
359 main_pad_->SetGridx(1);
360 main_pad_->SetGridy(1);
361 main_pad_->SetTopMargin(0.08);
362 main_pad_->Draw();
363
364 residual_pad_ = new TPad("editor_res", "editor_res", 0, 0, 1, 0.3);
365 residual_pad_->SetTopMargin(0.04);
366 residual_pad_->SetBottomMargin(0.35);
367 residual_pad_->SetGridx(1);
368 residual_pad_->SetGridy(1);
369 residual_pad_->Draw();
370
371 // Main pad
372 main_pad_->cd();
373
374 hist_draw_ = static_cast<TH1 *>(hist_->Clone("hist_editor_draw"));
375 hist_draw_->SetDirectory(0);
376 Double_t min_disp = 0.9 * range_low_;
377 Double_t max_disp = 1.1 * range_high_;
378 hist_draw_->GetXaxis()->SetRangeUser(min_disp, max_disp);
379 hist_draw_->GetXaxis()->SetLabelSize(0);
380 hist_draw_->GetXaxis()->SetTitleSize(0);
381 hist_draw_->SetLineColor(kViolet);
382 hist_draw_->SetLineWidth(2);
383 hist_draw_->Draw();
384
385 fit_func_->SetLineColor(kAzure);
386 fit_func_->SetNpx(1000);
387 fit_func_->Draw("same");
388
389 bkg_draw_ = new TF1("bkg_editor", FittingFunctions::LinearBackground,
390 range_low_, range_high_, 2);
391 bkg_draw_->SetParameter(0, fit_func_->GetParameter(BkgConstIdx()));
392 bkg_draw_->SetParameter(1, fit_func_->GetParameter(BkgSlopeIdx()));
393 bkg_draw_->SetLineColor(kGreen);
394 bkg_draw_->SetNpx(1000);
395 bkg_draw_->Draw("same");
396
397 Int_t comp_colors[4] = {kBlack, kGray, kRed, kOrange};
398
399 for (Int_t p = 0; p < num_peaks_; p++) {
400 for (Int_t c = 0; c < 4; c++) {
401 comp_graphs_[p][c] = new TGraph(kNDrawPts);
402 comp_graphs_[p][c]->SetLineColor(comp_colors[c]);
403 comp_graphs_[p][c]->SetLineStyle(PeakStyle(p));
404 comp_graphs_[p][c]->SetLineWidth(2);
405 comp_graphs_[p][c]->Draw("L same");
406 }
407 }
408
409 chi2_label_ = new TLatex();
410 chi2_label_->SetNDC();
411 chi2_label_->SetTextSize(0.045);
412 chi2_label_->SetTextAlign(31); // right-aligned
413 chi2_label_->Draw();
414
415 main_pad_->SetLogy(kTRUE);
416
417 // Residual pad
418 residual_pad_->cd();
419
420 n_res_points_ = 0;
421 Int_t nbins = hist_->GetNbinsX();
422 for (Int_t i = 1; i <= nbins; i++) {
423 Double_t x = hist_->GetBinCenter(i);
424 if (x < range_low_ || x > range_high_)
425 continue;
426 Double_t data = hist_->GetBinContent(i);
427 Double_t error = hist_->GetBinError(i);
428 if (error > 0 && data > 0)
429 n_res_points_++;
430 }
431
432 if (n_res_points_ < 1)
433 n_res_points_ = 1;
434
435 res_graph_ = new TGraph(n_res_points_);
436 res_graph_->SetMarkerStyle(20);
437 res_graph_->SetMarkerSize(0.8);
438 res_graph_->SetMarkerColor(kAzure);
439 res_graph_->SetLineColor(kAzure);
440 res_graph_->SetTitle("");
441
442 Double_t ax_min =
443 hist_draw_->GetXaxis()->GetBinLowEdge(hist_draw_->GetXaxis()->GetFirst());
444 Double_t ax_max =
445 hist_draw_->GetXaxis()->GetBinUpEdge(hist_draw_->GetXaxis()->GetLast());
446 res_graph_->GetXaxis()->SetLimits(ax_min, ax_max);
447 res_graph_->GetYaxis()->SetTitle("#delta/#sigma");
448 res_graph_->GetXaxis()->SetTitle(hist_->GetXaxis()->GetTitle());
449 res_graph_->GetXaxis()->SetTitleSize(0.13);
450 res_graph_->GetYaxis()->SetTitleSize(0.13);
451 res_graph_->GetXaxis()->SetLabelSize(0.12);
452 res_graph_->GetYaxis()->SetLabelSize(0.12);
453 res_graph_->GetXaxis()->SetTitleOffset(1.0);
454 res_graph_->GetYaxis()->SetTitleOffset(0.3);
455 res_graph_->GetYaxis()->SetNdivisions(505);
456 res_graph_->GetXaxis()->SetNdivisions(510);
457 res_graph_->GetYaxis()->CenterTitle(kTRUE);
458 res_graph_->GetYaxis()->SetRangeUser(-5.5, 5.5);
459 res_graph_->Draw("AP");
460
461 zero_line_ = new TF1("zero_editor", "0", ax_min, ax_max);
462 zero_line_->SetLineColor(kBlack);
463 zero_line_->SetLineStyle(2);
464 zero_line_->SetLineWidth(2);
465 zero_line_->Draw("same");
466
467 plus3_line_ = new TF1("plus3_editor", "3", ax_min, ax_max);
468 plus3_line_->SetLineColor(kGray + 2);
469 plus3_line_->SetLineStyle(3);
470 plus3_line_->SetLineWidth(2);
471 plus3_line_->Draw("same");
472
473 minus3_line_ = new TF1("minus3_editor", "-3", ax_min, ax_max);
474 minus3_line_->SetLineColor(kGray + 2);
475 minus3_line_->SetLineStyle(3);
476 minus3_line_->SetLineWidth(2);
477 minus3_line_->Draw("same");
478
479 UpdateCompPoints();
480 UpdateResPoints();
481}
482
483void InteractiveFitEditor::UpdateCanvas() {
484 UpdateCompPoints();
485 UpdateResPoints();
486
487 // Update background TF1 parameters
488 bkg_draw_->SetParameter(0, fit_func_->GetParameter(BkgConstIdx()));
489 bkg_draw_->SetParameter(1, fit_func_->GetParameter(BkgSlopeIdx()));
490
491 // Compute live chi2/ndf
492 Double_t chi2_sum = 0;
493 Int_t ndf_bins = 0;
494 Int_t nbins_chi2 = hist_->GetNbinsX();
495 for (Int_t i = 1; i <= nbins_chi2; i++) {
496 Double_t x = hist_->GetBinCenter(i);
497 if (x < range_low_ || x > range_high_)
498 continue;
499 Double_t data = hist_->GetBinContent(i);
500 Double_t error = hist_->GetBinError(i);
501 if (error > 0 && data > 0) {
502 Double_t fit_val = fit_func_->Eval(x);
503 Double_t residual = (data - fit_val) / error;
504 chi2_sum += residual * residual;
505 ndf_bins++;
506 }
507 }
508 Int_t n_free = 0;
509 for (Int_t i = 0; i < num_params_; i++) {
510 if (!IsFixed(i))
511 n_free++;
512 }
513 Int_t ndf = ndf_bins - n_free;
514 if (ndf > 0) {
515 chi2_label_->SetText(0.85, 0.85,
516 Form("#chi^{2}/ndf = %.3f", chi2_sum / ndf));
517 } else {
518 chi2_label_->SetText(0.85, 0.85, "#chi^{2}/ndf = N/A");
519 }
520
521 // Update zero line to match current range
522 zero_line_->SetRange(0.9 * range_low_, 1.1 * range_high_);
523 plus3_line_->SetRange(0.9 * range_low_, 1.1 * range_high_);
524 minus3_line_->SetRange(0.9 * range_low_, 1.1 * range_high_);
525
526 // Lock residual Y range
527 res_graph_->GetYaxis()->SetRangeUser(-5.5, 5.5);
528
529 TCanvas *canvas = embedded_canvas_->GetCanvas();
530 main_pad_->Modified();
531 residual_pad_->Modified();
532 canvas->Modified();
533 canvas->Update();
534
535 needs_redraw_ = kFALSE;
536}
537
538void InteractiveFitEditor::UpdateCompPoints() {
539 Double_t x_step = (range_high_ - range_low_) / (kNDrawPts - 1);
540
541 Double_t bkg_const = fit_func_->GetParameter(BkgConstIdx());
542 Double_t bkg_slope = fit_func_->GetParameter(BkgSlopeIdx());
543
544 for (Int_t p = 0; p < num_peaks_; p++) {
545 Int_t off = p * 10;
546
547 Double_t mu = fit_func_->GetParameter(off + 0);
548 Double_t sigma = fit_func_->GetParameter(off + 1);
549 Double_t gaus_amp = fit_func_->GetParameter(off + 2);
550 // Amplitude params are ratios; convert to absolute for component drawing
551 Double_t step_amp = fit_func_->GetParameter(off + 3) * gaus_amp;
552 Double_t lexp_amp = fit_func_->GetParameter(off + 4) * gaus_amp;
553 Double_t lexp_dec = fit_func_->GetParameter(off + 5);
554 Double_t llin_amp = fit_func_->GetParameter(off + 6) * gaus_amp;
555 Double_t llin_slp = fit_func_->GetParameter(off + 7);
556 Double_t hexp_amp = fit_func_->GetParameter(off + 8) * gaus_amp;
557 Double_t hexp_dec = fit_func_->GetParameter(off + 9);
558
559 for (Int_t i = 0; i < kNDrawPts; i++) {
560 Double_t x = range_low_ + i * x_step;
561 Double_t x_arr[1] = {x};
562
563 Double_t bkg_par[2] = {bkg_const, bkg_slope};
564 Double_t bkg_val = FittingFunctions::LinearBackground(x_arr, bkg_par);
565
566 Double_t gaus_par[3] = {mu, sigma, gaus_amp};
567 comp_graphs_[p][0]->SetPoint(
568 i, x, FittingFunctions::Gaussian(x_arr, gaus_par) + bkg_val);
569
570 Double_t step_par[3] = {mu, sigma, step_amp};
571 comp_graphs_[p][1]->SetPoint(
572 i, x, FittingFunctions::Step(x_arr, step_par) + bkg_val);
573
574 Double_t lt_par[6] = {mu, sigma, lexp_amp, lexp_dec, llin_amp, llin_slp};
575 comp_graphs_[p][2]->SetPoint(
576 i, x, FittingFunctions::LowTail(x_arr, lt_par) + bkg_val);
577
578 Double_t ht_par[4] = {mu, sigma, hexp_amp, hexp_dec};
579 comp_graphs_[p][3]->SetPoint(
580 i, x, FittingFunctions::HighTail(x_arr, ht_par) + bkg_val);
581 }
582 }
583}
584
585void InteractiveFitEditor::UpdateResPoints() {
586 Int_t nbins = hist_->GetNbinsX();
587 Int_t pt = 0;
588 for (Int_t i = 1; i <= nbins; i++) {
589 Double_t x = hist_->GetBinCenter(i);
590 if (x < range_low_ || x > range_high_)
591 continue;
592 Double_t data = hist_->GetBinContent(i);
593 Double_t error = hist_->GetBinError(i);
594 if (error > 0 && data > 0) {
595 Double_t fit_val = fit_func_->Eval(x);
596 Double_t pull = (data - fit_val) / error;
597 res_graph_->SetPoint(pt, x, pull);
598 pt++;
599 }
600 }
601 res_graph_->Set(pt);
602 n_res_points_ = pt;
603
604 // Update residual x-axis to match current range
605 Double_t disp_lo = 0.9 * range_low_;
606 Double_t disp_hi = 1.1 * range_high_;
607 res_graph_->GetXaxis()->SetLimits(disp_lo, disp_hi);
608}
609
610// Widget Synchronization
611
612void InteractiveFitEditor::SyncAllWidgets() {
613 syncing_ = kTRUE;
614 for (Int_t i = 0; i < num_params_; i++) {
615 SyncWidget(i);
616 }
617 syncing_ = kFALSE;
618}
619
620void InteractiveFitEditor::SyncWidget(Int_t param_idx) {
621 Double_t val = fit_func_->GetParameter(param_idx);
622
623 // Update fixed state from current TF1
624 Double_t lo = 0, hi = 0;
625 fit_func_->GetParLimits(param_idx, lo, hi);
626 Bool_t fixed = (lo >= hi);
627
628 // Update bounds if parameter is not fixed
629 if (!fixed && lo < hi) {
630 current_bounds_low_[param_idx] = lo;
631 current_bounds_high_[param_idx] = hi;
632 }
633
634 // Clamp value to bounds for slider
635 Double_t clamped = val;
636 if (clamped < current_bounds_low_[param_idx])
637 clamped = current_bounds_low_[param_idx];
638 if (clamped > current_bounds_high_[param_idx])
639 clamped = current_bounds_high_[param_idx];
640
641 sliders_[param_idx]->SetPosition(ValToSlider(param_idx, clamped));
642 value_entries_[param_idx]->SetNumber(val);
643
644 if (fixed) {
645 fix_checks_[param_idx]->SetState(kButtonDown);
646 sliders_[param_idx]->SetEnabled(kFALSE);
647 value_entries_[param_idx]->GetNumberEntry()->SetEnabled(kFALSE);
648 } else {
649 fix_checks_[param_idx]->SetState(kButtonUp);
650 sliders_[param_idx]->SetEnabled(kTRUE);
651 value_entries_[param_idx]->GetNumberEntry()->SetEnabled(kTRUE);
652 }
653
654 lo_bound_entries_[param_idx]->SetNumber(current_bounds_low_[param_idx]);
655 hi_bound_entries_[param_idx]->SetNumber(current_bounds_high_[param_idx]);
656}
657
658// Event Handlers
659
660void InteractiveFitEditor::OnSliderMoved(Int_t param_idx) {
661 if (syncing_)
662 return;
663 if (IsFixed(param_idx))
664 return;
665
666 Int_t pos = sliders_[param_idx]->GetPosition();
667 Double_t val = SliderToVal(param_idx, pos);
668 fit_func_->SetParameter(param_idx, val);
669
670 syncing_ = kTRUE;
671 value_entries_[param_idx]->SetNumber(val);
672 syncing_ = kFALSE;
673
674 needs_redraw_ = kTRUE;
675}
676
677void InteractiveFitEditor::OnEntryChanged(Int_t param_idx) {
678 if (syncing_)
679 return;
680 if (IsFixed(param_idx))
681 return;
682
683 Double_t val = value_entries_[param_idx]->GetNumber();
684
685 // Expand bounds if the entered value falls outside
686 Bool_t bounds_changed = kFALSE;
687 if (val < current_bounds_low_[param_idx]) {
688 current_bounds_low_[param_idx] = val;
689 bounds_changed = kTRUE;
690 }
691 if (val > current_bounds_high_[param_idx]) {
692 current_bounds_high_[param_idx] = val;
693 bounds_changed = kTRUE;
694 }
695
696 fit_func_->SetParameter(param_idx, val);
697 fit_func_->SetParLimits(param_idx, current_bounds_low_[param_idx],
698 current_bounds_high_[param_idx]);
699
700 syncing_ = kTRUE;
701 sliders_[param_idx]->SetPosition(ValToSlider(param_idx, val));
702 if (bounds_changed) {
703 lo_bound_entries_[param_idx]->SetNumber(current_bounds_low_[param_idx]);
704 hi_bound_entries_[param_idx]->SetNumber(current_bounds_high_[param_idx]);
705 }
706 syncing_ = kFALSE;
707
708 needs_redraw_ = kTRUE;
709}
710
711void InteractiveFitEditor::OnBoundsChanged(Int_t param_idx) {
712 if (syncing_)
713 return;
714
715 Double_t new_lo = lo_bound_entries_[param_idx]->GetNumber();
716 Double_t new_hi = hi_bound_entries_[param_idx]->GetNumber();
717
718 if (new_lo >= new_hi)
719 return; // invalid bounds, ignore
720
721 current_bounds_low_[param_idx] = new_lo;
722 current_bounds_high_[param_idx] = new_hi;
723
724 // Clamp current value to new bounds
725 Double_t val = fit_func_->GetParameter(param_idx);
726 if (val < new_lo)
727 val = new_lo;
728 if (val > new_hi)
729 val = new_hi;
730 fit_func_->SetParameter(param_idx, val);
731
732 if (!IsFixed(param_idx)) {
733 fit_func_->SetParLimits(param_idx, new_lo, new_hi);
734 }
735
736 syncing_ = kTRUE;
737 sliders_[param_idx]->SetPosition(ValToSlider(param_idx, val));
738 value_entries_[param_idx]->SetNumber(val);
739 syncing_ = kFALSE;
740
741 needs_redraw_ = kTRUE;
742}
743
744void InteractiveFitEditor::OnFixToggled(Int_t param_idx) {
745 Bool_t now_fixed = (fix_checks_[param_idx]->GetState() == kButtonDown);
746
747 if (now_fixed) {
748 Double_t val = fit_func_->GetParameter(param_idx);
749 fit_func_->FixParameter(param_idx, val);
750 sliders_[param_idx]->SetEnabled(kFALSE);
751 value_entries_[param_idx]->GetNumberEntry()->SetEnabled(kFALSE);
752 } else {
753 // Restore bounds and release
754 fit_func_->SetParLimits(param_idx, current_bounds_low_[param_idx],
755 current_bounds_high_[param_idx]);
756 sliders_[param_idx]->SetEnabled(kTRUE);
757 value_entries_[param_idx]->GetNumberEntry()->SetEnabled(kTRUE);
758 }
759
760 needs_redraw_ = kTRUE;
761}
762
763void InteractiveFitEditor::OnRangeChanged() {
764 if (syncing_)
765 return;
766
767 Double_t new_lo = range_slider_->GetMinPosition();
768 Double_t new_hi = range_slider_->GetMaxPosition();
769
770 if (new_lo >= new_hi)
771 return;
772
773 range_low_ = new_lo;
774 range_high_ = new_hi;
775
776 fit_func_->SetRange(range_low_, range_high_);
777 bkg_draw_->SetRange(range_low_, range_high_);
778
779 // Update histogram display range
780 hist_draw_->GetXaxis()->SetRangeUser(0.9 * range_low_, 1.1 * range_high_);
781
782 // Sync number entries
783 syncing_ = kTRUE;
784 range_lo_entry_->SetNumber(range_low_);
785 range_hi_entry_->SetNumber(range_high_);
786 syncing_ = kFALSE;
787
788 needs_redraw_ = kTRUE;
789}
790
791// Actions
792
793void InteractiveFitEditor::DoRefit() {
794 // Release all non-fixed parameters and use current values as initial guesses
795 for (Int_t i = 0; i < num_params_; i++) {
796 if (!IsFixed(i)) {
797 fit_func_->SetParLimits(i, current_bounds_low_[i],
798 current_bounds_high_[i]);
799 }
800 }
801
802 hist_->Fit(fit_func_, "LSMRBEN");
803
804 SyncAllWidgets();
805 needs_redraw_ = kTRUE;
806 UpdateCanvas();
807}
808
809void InteractiveFitEditor::DoAccept() {
810 accepted_ = kTRUE;
811 done_ = kTRUE;
812}
813
814void InteractiveFitEditor::DoCancel() {
815 // Restore original parameters and fixed states
816 for (Int_t i = 0; i < num_params_; i++) {
817 fit_func_->SetParameter(i, original_params_[i]);
818 if (original_fixed_[i]) {
819 fit_func_->FixParameter(i, original_params_[i]);
820 } else {
821 fit_func_->SetParLimits(i, original_bounds_low_[i],
822 original_bounds_high_[i]);
823 }
824 }
825 // Restore original range
826 fit_func_->SetRange(original_range_low_, original_range_high_);
827 accepted_ = kFALSE;
828 done_ = kTRUE;
829}
830
831void InteractiveFitEditor::DoReset() {
832 // Restore original parameters but don't close
833 for (Int_t i = 0; i < num_params_; i++) {
834 fit_func_->SetParameter(i, original_params_[i]);
835 current_bounds_low_[i] = original_bounds_low_[i];
836 current_bounds_high_[i] = original_bounds_high_[i];
837 if (original_fixed_[i]) {
838 fit_func_->FixParameter(i, original_params_[i]);
839 } else {
840 fit_func_->SetParLimits(i, original_bounds_low_[i],
841 original_bounds_high_[i]);
842 }
843 }
844
845 // Restore range
846 range_low_ = original_range_low_;
847 range_high_ = original_range_high_;
848 fit_func_->SetRange(range_low_, range_high_);
849 bkg_draw_->SetRange(range_low_, range_high_);
850 hist_draw_->GetXaxis()->SetRangeUser(0.9 * range_low_, 1.1 * range_high_);
851 syncing_ = kTRUE;
852 range_slider_->SetPosition(range_low_, range_high_);
853 range_lo_entry_->SetNumber(range_low_);
854 range_hi_entry_->SetNumber(range_high_);
855 syncing_ = kFALSE;
856
857 SyncAllWidgets();
858 needs_redraw_ = kTRUE;
859 UpdateCanvas();
860}
861
862// Event Loop
863
864Bool_t InteractiveFitEditor::ProcessMessage(Long_t msg, Long_t parm1,
865 Long_t parm2) {
866 if (syncing_)
867 return kTRUE;
868
869 (void)parm2; // unused directly; slider position read from widget
870
871 switch (GET_MSG(msg)) {
872 case kC_COMMAND:
873 switch (GET_SUBMSG(msg)) {
874 case kCM_BUTTON:
875 if (parm1 == kBtnRefit)
876 DoRefit();
877 else if (parm1 == kBtnAccept)
878 DoAccept();
879 else if (parm1 == kBtnCancel)
880 DoCancel();
881 else if (parm1 == kBtnReset)
882 DoReset();
883 break;
884
885 case kCM_CHECKBUTTON:
886 if (parm1 >= kFixBase && parm1 < kFixBase + num_params_) {
887 OnFixToggled(parm1 - kFixBase);
888 }
889 break;
890 }
891 break;
892
893 case kC_HSLIDER:
894 if (parm1 == kRangeSlider) {
895 OnRangeChanged();
896 } else if (parm1 >= kSliderBase && parm1 < kSliderBase + num_params_) {
897 OnSliderMoved(parm1 - kSliderBase);
898 }
899 break;
900
901 case kC_TEXTENTRY:
902 if (GET_SUBMSG(msg) == kTE_ENTER || GET_SUBMSG(msg) == kTE_TAB) {
903 if (parm1 == kRangeLoEntry || parm1 == kRangeHiEntry) {
904 // Sync slider from entries
905 Double_t new_lo = range_lo_entry_->GetNumber();
906 Double_t new_hi = range_hi_entry_->GetNumber();
907 if (new_lo < new_hi) {
908 syncing_ = kTRUE;
909 range_slider_->SetPosition(new_lo, new_hi);
910 syncing_ = kFALSE;
911 OnRangeChanged();
912 }
913 } else if (parm1 >= kEntryBase && parm1 < kEntryBase + num_params_) {
914 OnEntryChanged(parm1 - kEntryBase);
915 } else if (parm1 >= kLoBoundBase && parm1 < kLoBoundBase + num_params_) {
916 OnBoundsChanged(parm1 - kLoBoundBase);
917 } else if (parm1 >= kHiBoundBase && parm1 < kHiBoundBase + num_params_) {
918 OnBoundsChanged(parm1 - kHiBoundBase);
919 }
920 }
921 break;
922 }
923
924 return kTRUE;
925}
926
928 if (timer == redraw_timer_ && needs_redraw_) {
929 UpdateCanvas();
930 }
931 return kTRUE;
932}
933
935
936// Helpers
937
938Int_t InteractiveFitEditor::ValToSlider(Int_t param_idx, Double_t val) {
939 Double_t lo = current_bounds_low_[param_idx];
940 Double_t hi = current_bounds_high_[param_idx];
941 if (hi <= lo)
942 return 0;
943 Double_t frac = (val - lo) / (hi - lo);
944 if (frac < 0.0)
945 frac = 0.0;
946 if (frac > 1.0)
947 frac = 1.0;
948 return static_cast<Int_t>(frac * kSliderRes);
949}
950
951Double_t InteractiveFitEditor::SliderToVal(Int_t param_idx, Int_t pos) {
952 Double_t lo = current_bounds_low_[param_idx];
953 Double_t hi = current_bounds_high_[param_idx];
954 Double_t frac = static_cast<Double_t>(pos) / kSliderRes;
955 return lo + frac * (hi - lo);
956}
957
958Bool_t InteractiveFitEditor::IsFixed(Int_t param_idx) {
959 return (fix_checks_[param_idx]->GetState() == kButtonDown);
960}
961
962void InteractiveFitEditor::GetDefaultBounds(Int_t param_idx, Double_t &lo,
963 Double_t &hi) {
964 Double_t peak_height = hist_->GetMaximum();
965 Double_t range_width = range_high_ - range_low_;
966
967 if (param_idx == BkgConstIdx()) {
968 lo = 0;
969 hi = peak_height;
970 return;
971 }
972 if (param_idx == BkgSlopeIdx()) {
973 lo = -peak_height / range_width;
974 hi = peak_height / range_width;
975 return;
976 }
977
978 Int_t local = param_idx % 10;
979 switch (local) {
980 case 0: // Mu
981 lo = range_low_;
982 hi = range_high_;
983 break;
984 case 1: // Sigma
985 lo = range_width * 0.001;
986 hi = range_width * 0.5;
987 break;
988 case 2: // GausAmplitude
989 lo = 0;
990 hi = peak_height * 2;
991 break;
992 case 3: // StepAmplitude ratio
993 lo = 0;
994 hi = 0.5;
995 break;
996 case 4: // LowExpTailAmplitude ratio
997 lo = 0;
998 hi = 0.5;
999 break;
1000 case 5: // LowExpTailRatio
1001 lo = 1.0;
1002 hi = 100;
1003 break;
1004 case 6: // LowLinTailAmplitude ratio
1005 lo = 0;
1006 hi = 0.5;
1007 break;
1008 case 7: // LowLinTailSlope
1009 lo = -1;
1010 hi = 1;
1011 break;
1012 case 8: // HighExpTailAmplitude ratio
1013 lo = 0;
1014 hi = 0.5;
1015 break;
1016 case 9: // HighExpTailRatio
1017 lo = 1.0;
1018 hi = 100;
1019 break;
1020 default:
1021 lo = -1000;
1022 hi = 1000;
1023 break;
1024 }
1025}
1026
1027Int_t InteractiveFitEditor::PeakStyle(Int_t peak_idx) {
1028 if (peak_idx == 0)
1029 return 1; // solid
1030 if (peak_idx == 1)
1031 return 3; // dashed
1032 return 4; // dash-dot
1033}
1034
1035// Launcher
1036
1037Bool_t LaunchInteractiveFitEditor(TH1 *hist, TF1 *fit_func, Double_t range_low,
1038 Double_t range_high, Int_t num_peaks,
1039 const TString &info_label) {
1040 if (!gClient) {
1041 std::cerr << "InteractiveFitEditor: GUI not available (gClient is null). "
1042 << "Make sure you are not in batch mode." << std::endl;
1043 return kFALSE;
1044 }
1045
1046 // Swallow recoverable X protocol errors for the editor's lifetime so a
1047 // transient bad redraw doesn't trip ROOT's crashing default handler.
1049
1050 InteractiveFitEditor *editor =
1051 new InteractiveFitEditor(gClient->GetRoot(), hist, fit_func, range_low,
1052 range_high, num_peaks, info_label);
1053
1054 while (!editor->IsDone()) {
1055 gSystem->ProcessEvents();
1056 gSystem->Sleep(10);
1057 }
1058
1059 Bool_t result = editor->WasAccepted();
1060
1061 editor->GetRedrawTimer()->TurnOff();
1062
1063 // Prevent TCanvas::Close() from doing X11 operations during teardown.
1064 // Removing the canvas from the canvases list also suppresses X11 events
1065 // for it on the next ProcessEvents (otherwise the next editor's first
1066 // ProcessEvents picks up stale paint events and crashes in DrawString).
1067 // Batch mode disables remaining X11 drawing calls.
1068 // Note: removing the canvas from this list disables RecursiveRemove's
1069 // pad-primitive cleanup, so the editor destructor explicitly clears
1070 // pad primitive lists before deleting drawn objects.
1071 TCanvas *ecanvas = editor->GetEmbeddedCanvas()->GetCanvas();
1072 if (ecanvas) {
1073 gROOT->GetListOfCanvases()->Remove(ecanvas);
1074 ecanvas->SetBatch(kTRUE);
1075 }
1076
1077 // DontCallClose prevents TGMainFrame from triggering gApplication->Terminate
1078 // when the window is destroyed, which would kill the whole macro
1079 editor->DontCallClose();
1080 editor->UnmapWindow();
1081 gSystem->ProcessEvents();
1082 delete editor;
1083
1084 AURestoreXErrorHandler(xerr_save);
1085 return result;
1086}
Stops a recoverable X protocol error from becoming a fatal SIGSEGV.
AUXErrorHandlerSave AUInstallTolerantXErrorHandler()
Install the tolerant X error handler for the duration of an event loop.
void AURestoreXErrorHandler(AUXErrorHandlerSave s)
Put back the handler that was in place before the tolerant one.
Bool_t LaunchInteractiveFitEditor(TH1 *hist, TF1 *fit_func, Double_t range_low, Double_t range_high, Int_t num_peaks, const TString &info_label)
Interactive editor for a TF1 fit from FittingUtils.
virtual ~InteractiveFitEditor()
Destroys the widgets and drawing objects the editor created.
TRootEmbeddedCanvas * GetEmbeddedCanvas()
The canvas holding the fit and residual pads.
virtual Bool_t ProcessMessage(Long_t msg, Long_t parm1, Long_t parm2)
ROOT GUI message dispatch for every widget in the editor.
virtual void CloseWindow()
Window-manager close. Treated as a cancel, not an accept.
Bool_t IsDone() const
Whether the editor has finished and the loop may exit.
TTimer * GetRedrawTimer()
The coalescing redraw timer, for the driving event loop.
InteractiveFitEditor(const TGWindow *parent, TH1 *hist, TF1 *fit_func, Double_t range_low, Double_t range_high, Int_t num_peaks, const TString &info_label="")
Build the editor around an existing histogram and fit function.
virtual Bool_t HandleTimer(TTimer *timer)
Redraw tick.
Bool_t WasAccepted() const
Whether the user accepted rather than cancelled.
Double_t LinearBackground(Double_t *x, Double_t *par)
Straight-line background.
Double_t Step(Double_t *x, Double_t *par)
Resolution-smeared step shelf below the peak.
Double_t Gaussian(Double_t *x, Double_t *par)
Gaussian peak.
Double_t LowTail(Double_t *x, Double_t *par)
Combined exponential and linear tail below the peak.
Double_t HighTail(Double_t *x, Double_t *par)
Exponential tail above the peak, from pileup.
What AUInstallTolerantXErrorHandler() needs in order to undo itself.