// SPDX-License-Identifier: LGPL-3.0-or-other // Copyright (C) 2021 Contributors to the SLS Detector Package #include "energyCalibration.h" #ifdef __CINT #define MYROOT #endif #ifdef MYROOT #include #include #include #include #endif #include #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) #define ELEM_SWAP(a, b) \ { \ register int t = (a); \ (a) = (b); \ (b) = t; \ } using namespace std; #ifdef MYROOT Double_t energyCalibrationFunctions::pedestal(Double_t *x, Double_t *par) { return par[0] - par[1] * sign * x[0]; } Double_t energyCalibrationFunctions::gaussChargeSharing(Double_t *x, Double_t *par) { Double_t f, arg = 0; // Gaussian exponent if (par[3] != 0) { arg = sign * (x[0] - par[2]) / par[3]; } // the Gaussian f = TMath::Exp(-1 * arg * arg / 2.); // Gaussian + error function f = f + par[5] / 2. * (TMath::Erfc(arg / (TMath::Sqrt(2.)))); // Gaussian + error function + pedestal return par[4] * f + pedestal(x, par); } Double_t energyCalibrationFunctions::gaussChargeSharingKb(Double_t *x, Double_t *par) { Double_t f, arg = 0, argb = 0; // Gaussian exponent if (par[3] != 0) { arg = sign * (x[0] - par[2]) / par[3]; argb = sign * (x[0] - (par[6] * par[2])) / par[3]; // using absolute kb mean might seem better but like this // the ratio can be fixed } // the Gaussian f = TMath::Exp(-1 * arg * arg / 2.); f = f + par[7] * (TMath::Exp(-1 * argb * argb / 2.)); // Gaussian + error function f = f + par[5] / 2. * (TMath::Erfc(arg / (TMath::Sqrt(2.)))); f = f + par[7] * par[5] / 2. * (TMath::Erfc(argb / (TMath::Sqrt(2.)))); // Gaussian + error function + pedestal return par[4] * f + pedestal(x, par); } Double_t energyCalibrationFunctions::gaussChargeSharingKaDoublet(Double_t *x, Double_t *par) { Double_t f, f2, arg = 0, arg2 = 0; // Gaussian exponent if (par[3] != 0) { arg = sign * (x[0] - par[2]) / par[3]; arg2 = sign * (x[0] - par[6]) / par[3]; } // the Gaussian f = TMath::Exp(-1 * arg * arg / 2.); f2 = TMath::Exp(-1 * arg2 * arg2 / 2.); // Gaussian + error function f = f + par[5] / 2. * (TMath::Erfc(arg / (TMath::Sqrt(2.)))); f2 = f2 + par[5] / 2. * (TMath::Erfc(arg / (TMath::Sqrt(2.)))); // shouldn't this be arg2? // Gaussian + error function + pedestal return par[4] * f + par[7] * f2 + pedestal(x, par); } Double_t energyCalibrationFunctions::gaussChargeSharingPixel(Double_t *x, Double_t *par) { Double_t f; if (par[3] <= 0 || par[2] * (*x) <= 0 || par[5] < 0 || par[4] <= 0) return 0; Double_t pp[3]; pp[0] = 0; pp[1] = par[2]; pp[2] = par[3]; f = (par[5] - par[6] * (TMath::Log(*x / par[2]))) * erfBox(x, pp); f += par[4] * TMath::Gaus(*x, par[2], par[3], kTRUE); return f + pedestal(x, par); } Double_t energyCalibrationFunctions::erfBox(Double_t *z, Double_t *par) { Double_t m = par[0]; Double_t M = par[1]; if (par[0] > par[1]) { m = par[1]; M = par[0]; } if (m == M) return 0; if (par[2] <= 0) { if (*z >= m && *z <= M) return 1. / (M - m); else return 0; } return (TMath::Erfc((z[0] - M) / par[2]) - TMath::Erfc((z[0] - m) / par[2])) * 0.5 / (M - m); } // basic erf function Double_t energyCalibrationFunctions::erfFunction(Double_t *x, Double_t *par) { double arg = 0; if (par[1] != 0) arg = (par[0] - x[0]) / par[1]; return ((par[2] / 2. * (1 + TMath::Erf(sign * arg / (TMath::Sqrt(2)))))); }; Double_t energyCalibrationFunctions::erfFunctionChargeSharing(Double_t *x, Double_t *par) { Double_t f; f = erfFunction(x, par + 2) * (1 + par[5] * (par[2] - x[0])) + par[0] - par[1] * x[0] * sign; return f; }; Double_t energyCalibrationFunctions::erfFuncFluo(Double_t *x, Double_t *par) { Double_t f; f = erfFunctionChargeSharing(x, par) + erfFunction(x, par + 6) * (1 + par[9] * (par[6] - x[0])); return f; }; #endif double energyCalibrationFunctions::median(double *x, int n) { // sorts x into xmed array and returns median // n is number of values already in the xmed array double xmed[n]; int k, i, j; for (i = 0; i < n; i++) { k = 0; for (j = 0; j < n; j++) { if (*(x + i) > *(x + j)) k++; if (*(x + i) == *(x + j)) { if (i > j) k++; } } xmed[k] = *(x + i); } k = n / 2; return xmed[k]; } int energyCalibrationFunctions::quick_select(int arr[], int n) { int low, high; int median; int middle, ll, hh; low = 0; high = n - 1; median = (low + high) / 2; for (;;) { if (high <= low) /* One element only */ return arr[median]; if (high == low + 1) { /* Two elements only */ if (arr[low] > arr[high]) ELEM_SWAP(arr[low], arr[high]); return arr[median]; } /* Find median of low, middle and high items; swap into position low */ middle = (low + high) / 2; if (arr[middle] > arr[high]) ELEM_SWAP(arr[middle], arr[high]); if (arr[low] > arr[high]) ELEM_SWAP(arr[low], arr[high]); if (arr[middle] > arr[low]) ELEM_SWAP(arr[middle], arr[low]); /* Swap low item (now in position middle) into position (low+1) */ ELEM_SWAP(arr[middle], arr[low + 1]); /* Nibble from each end towards middle, swapping items when stuck */ ll = low + 1; hh = high; for (;;) { do ll++; while (arr[low] > arr[ll]); do hh--; while (arr[hh] > arr[low]); if (hh < ll) break; ELEM_SWAP(arr[ll], arr[hh]); } /* Swap middle item (in position low) back into correct position */ ELEM_SWAP(arr[low], arr[hh]); /* Re-set active partition */ if (hh <= median) low = ll; if (hh >= median) high = hh - 1; } } int energyCalibrationFunctions::kth_smallest(int *a, int n, int k) { register int i, j, l, m; register double x; l = 0; m = n - 1; while (l < m) { x = a[k]; i = l; j = m; do { while (a[i] < x) i++; while (x < a[j]) j--; if (i <= j) { ELEM_SWAP(a[i], a[j]); i++; j--; } } while (i <= j); if (j < k) l = i; if (k < i) m = j; } return a[k]; } #ifdef MYROOT Double_t energyCalibrationFunctions::spectrum(Double_t *x, Double_t *par) { return gaussChargeSharing(x, par); } Double_t energyCalibrationFunctions::spectrumkb(Double_t *x, Double_t *par) { return gaussChargeSharingKb(x, par); } Double_t energyCalibrationFunctions::spectrumkadoublet(Double_t *x, Double_t *par) { return gaussChargeSharingKaDoublet(x, par); } Double_t energyCalibrationFunctions::spectrumPixel(Double_t *x, Double_t *par) { return gaussChargeSharingPixel(x, par); } Double_t energyCalibrationFunctions::scurve(Double_t *x, Double_t *par) { return erfFunctionChargeSharing(x, par); } Double_t energyCalibrationFunctions::scurveFluo(Double_t *x, Double_t *par) { return erfFuncFluo(x, par); } #endif energyCalibration::energyCalibration() : #ifdef MYROOT fit_min(-1), fit_max(-1), bg_offset(-1), bg_slope(-1), flex(-1), noise(-1), ampl(-1), cs_slope(-1), kb_mean(-1), kb_frac(-1), mean2(-1), ampl2(-1), fscurve(NULL), fspectrum(NULL), fspectrumkb(NULL), fspectrumkadoublet(NULL), #endif funcs(NULL), plot_flag(1), // fit parameters output to screen cs_flag(1) { #ifdef MYROOT funcs = new energyCalibrationFunctions(); fscurve = new TF1("fscurve", funcs, &energyCalibrationFunctions::scurve, 0, 1000, 6, "energyCalibrationFunctions", "scurve"); fscurve->SetParNames("Background Offset", "Background Slope", "Inflection Point", "Noise RMS", "Number of Photons", "Charge Sharing Slope"); fspectrum = new TF1("fspectrum", funcs, &energyCalibrationFunctions::spectrum, 0, 1000, 6, "energyCalibrationFunctions", "spectrum"); fspectrum->SetParNames("Background Pedestal", "Background slope", "Peak position", "Noise RMS", "Number of Photons", "Charge Sharing Pedestal"); fspectrumkb = new TF1("fspectrumkb", funcs, &energyCalibrationFunctions::spectrumkb, 0, 1000, 8, "energyCalibrationFunctions", "spectrumkb"); fspectrumkb->SetParNames("Background Pedestal", "Background slope", "Peak position", "Noise RMS", "Number of Photons", "Charge Sharing Pedestal", "kb mean", "kb frac"); fspectrumkadoublet = new TF1("fspectrumkadoublet", funcs, &energyCalibrationFunctions::spectrumkadoublet, 0, 1000, 8, "energyCalibrationFunctions", "spectrumkadoublet"); fspectrumkadoublet->SetParNames( "Background Pedestal", "Background slope", "Peak position", "Noise RMS", "Number of Photons", "Charge Sharing Pedestal", "ka2 mean", "n2"); fspixel = new TF1("fspixel", funcs, &energyCalibrationFunctions::spectrumPixel, 0, 1000, 7, "energyCalibrationFunctions", "spectrumPixel"); fspixel->SetParNames("Background Pedestal", "Background slope", "Peak position", "Noise RMS", "Number of Photons", "Charge Sharing Pedestal", "Corner"); #endif } void energyCalibration::fixParameter(int ip, Double_t val) { fscurve->FixParameter(ip, val); fspectrum->FixParameter(ip, val); fspectrumkb->FixParameter(ip, val); fspectrumkadoublet->FixParameter(ip, val); } void energyCalibration::releaseParameter(int ip) { fscurve->ReleaseParameter(ip); fspectrum->ReleaseParameter(ip); fspectrumkb->ReleaseParameter(ip); fspectrumkadoublet->ReleaseParameter(ip); } energyCalibration::~energyCalibration() { #ifdef MYROOT delete fscurve; delete fspectrum; delete fspectrumkb; delete fspectrumkadoublet; #endif } #ifdef MYROOT TH1F *energyCalibration::createMedianHistogram(TH2F *h2, int ch0, int nch, int direction) { if (h2 == NULL || nch == 0) return NULL; double *x = new double[nch]; TH1F *h1 = NULL; double val = -1; if (direction == 0) { h1 = new TH1F("median", "Median", h2->GetYaxis()->GetNbins(), h2->GetYaxis()->GetXmin(), h2->GetYaxis()->GetXmax()); for (int ib = 0; ib < h1->GetXaxis()->GetNbins(); ib++) { for (int ich = 0; ich < nch; ich++) { x[ich] = h2->GetBinContent(ch0 + ich + 1, ib + 1); } val = energyCalibrationFunctions::median(x, nch); h1->SetBinContent(ib + 1, val); } } else if (direction == 1) { h1 = new TH1F("median", "Median", h2->GetXaxis()->GetNbins(), h2->GetXaxis()->GetXmin(), h2->GetXaxis()->GetXmax()); for (int ib = 0; ib < h1->GetYaxis()->GetNbins(); ib++) { for (int ich = 0; ich < nch; ich++) { x[ich] = h2->GetBinContent(ib + 1, ch0 + ich + 1); } val = energyCalibrationFunctions::median(x, nch); h1->SetBinContent(ib + 1, val); } } delete[] x; return h1; } void energyCalibration::setStartParameters(Double_t *par) { bg_offset = par[0]; bg_slope = par[1]; flex = par[2]; noise = par[3]; ampl = par[4]; cs_slope = par[5]; } void energyCalibration::setStartParametersKb(Double_t *par) { bg_offset = par[0]; bg_slope = par[1]; flex = par[2]; noise = par[3]; ampl = par[4]; cs_slope = par[5]; kb_mean = par[6]; kb_frac = par[7]; // fit_min = 400; // used for soleil flat field // fit_max = 800; } void energyCalibration::setStartParametersKaDoublet(Double_t *par) { bg_offset = par[0]; bg_slope = par[1]; flex = par[2]; noise = par[3]; ampl = par[4]; cs_slope = par[5]; mean2 = par[6]; ampl2 = par[7]; // fit_min = 400; // used for soleil flat field // fit_max = 800; } void energyCalibration::getStartParameters(Double_t *par) { par[0] = bg_offset; par[1] = bg_slope; par[2] = flex; par[3] = noise; par[4] = ampl; par[5] = cs_slope; } #endif int energyCalibration::setChargeSharing(int p) { if (p >= 0) { cs_flag = p; #ifdef MYROOT if (p) { fscurve->ReleaseParameter(5); fspectrum->ReleaseParameter(1); fspectrumkb->ReleaseParameter(1); fspectrumkadoublet->ReleaseParameter(1); } else { fscurve->FixParameter(5, 0); fspectrum->FixParameter(1, 0); fspectrumkb->FixParameter(1, 0); fspectrumkadoublet->FixParameter(1, 0); } #endif } return cs_flag; } #ifdef MYROOT void energyCalibration::initFitFunction(TF1 *fun, TH1 *h1) { Double_t min = fit_min, max = fit_max; Double_t mypar[6]; if (max == -1) max = h1->GetXaxis()->GetXmax(); if (min == -1) min = h1->GetXaxis()->GetXmin(); if (bg_offset == -1) mypar[0] = 0; else mypar[0] = bg_offset; if (bg_slope == -1) mypar[1] = 0; else mypar[1] = bg_slope; if (flex == -1) mypar[2] = (min + max) / 2.; else mypar[2] = flex; if (noise == -1) mypar[3] = 0.1; else mypar[3] = noise; if (ampl == -1) mypar[4] = h1->GetBinContent(h1->GetXaxis()->FindBin(0.5 * (max + min))); else mypar[4] = ampl; if (cs_slope == -1) mypar[5] = 0; else mypar[5] = cs_slope; fun->SetParameters(mypar); fun->SetRange(min, max); } void energyCalibration::initFitFunctionKb(TF1 *fun, TH1 *h1) { Double_t min = fit_min, max = fit_max; Double_t mypar[8]; if (max == -1) max = h1->GetXaxis()->GetXmax(); if (min == -1) min = h1->GetXaxis()->GetXmin(); if (bg_offset == -1) mypar[0] = 0; else mypar[0] = bg_offset; if (bg_slope == -1) mypar[1] = 0; else mypar[1] = bg_slope; if (flex == -1) mypar[2] = (min + max) / 2.; else mypar[2] = flex; if (noise == -1) mypar[3] = 0.1; else mypar[3] = noise; if (ampl == -1) mypar[4] = h1->GetBinContent(h1->GetXaxis()->FindBin(0.5 * (max + min))); else mypar[4] = ampl; if (cs_slope == -1) mypar[5] = 0; else mypar[5] = cs_slope; if (kb_mean == -1) mypar[6] = 0; else mypar[6] = kb_mean; if (kb_frac == -1) mypar[7] = 0; else mypar[7] = kb_frac; fun->SetParameters(mypar); fun->SetRange(min, max); } void energyCalibration::initFitFunctionKaDoublet(TF1 *fun, TH1 *h1) { Double_t min = fit_min, max = fit_max; Double_t mypar[8]; if (max == -1) max = h1->GetXaxis()->GetXmax(); if (min == -1) min = h1->GetXaxis()->GetXmin(); if (bg_offset == -1) mypar[0] = 0; else mypar[0] = bg_offset; if (bg_slope == -1) mypar[1] = 0; else mypar[1] = bg_slope; if (flex == -1) mypar[2] = (min + max) / 2.; else mypar[2] = flex; if (noise == -1) mypar[3] = 0.1; else mypar[3] = noise; if (ampl == -1) mypar[4] = h1->GetBinContent(h1->GetXaxis()->FindBin(0.5 * (max + min))); else mypar[4] = ampl; if (cs_slope == -1) mypar[5] = 0; else mypar[5] = cs_slope; if (mean2 == -1) mypar[6] = 0; else mypar[6] = mean2; if (ampl2 == -1) mypar[7] = 0; else mypar[7] = ampl2; fun->SetParameters(mypar); fun->SetRange(min, max); } TF1 *energyCalibration::fitFunction(TF1 *fun, TH1 *h1, Double_t *mypar, Double_t *emypar) { TF1 *fitfun; char fname[100]; strcpy(fname, fun->GetName()); if (plot_flag) { h1->Fit(fname, "R0Q"); } else h1->Fit(fname, "R0Q"); fitfun = h1->GetFunction(fname); fitfun->GetParameters(mypar); for (int ip = 0; ip < 6; ip++) { emypar[ip] = fitfun->GetParError(ip); } return fitfun; } TF1 *energyCalibration::fitFunctionKb(TF1 *fun, TH1 *h1, Double_t *mypar, Double_t *emypar) { TF1 *fitfun; char fname[100]; strcpy(fname, fun->GetName()); if (plot_flag) { h1->Fit(fname, "R0Q"); } else h1->Fit(fname, "R0Q"); fitfun = h1->GetFunction(fname); fitfun->GetParameters(mypar); for (int ip = 0; ip < 8; ip++) { emypar[ip] = fitfun->GetParError(ip); } return fitfun; } TF1 *energyCalibration::fitFunctionKaDoublet(TF1 *fun, TH1 *h1, Double_t *mypar, Double_t *emypar) { TF1 *fitfun; char fname[100]; strcpy(fname, fun->GetName()); if (plot_flag) { h1->Fit(fname, "R0Q"); } else h1->Fit(fname, "R0Q"); fitfun = h1->GetFunction(fname); fitfun->GetParameters(mypar); for (int ip = 0; ip < 8; ip++) { emypar[ip] = fitfun->GetParError(ip); } return fitfun; } TF1 *energyCalibration::fitSCurve(TH1 *h1, Double_t *mypar, Double_t *emypar) { initFitFunction(fscurve, h1); return fitFunction(fscurve, h1, mypar, emypar); } TF1 *energyCalibration::fitSpectrum(TH1 *h1, Double_t *mypar, Double_t *emypar) { initFitFunction(fspectrum, h1); return fitFunction(fspectrum, h1, mypar, emypar); } TF1 *energyCalibration::fitSpectrumKb(TH1 *h1, Double_t *mypar, Double_t *emypar) { initFitFunctionKb(fspectrumkb, h1); return fitFunctionKb(fspectrumkb, h1, mypar, emypar); } TF1 *energyCalibration::fitSpectrumKaDoublet(TH1 *h1, Double_t *mypar, Double_t *emypar) { initFitFunctionKaDoublet(fspectrumkadoublet, h1); return fitFunctionKaDoublet(fspectrumkadoublet, h1, mypar, emypar); } TGraphErrors *energyCalibration::linearCalibration( int nscan, Double_t *en, Double_t *een, Double_t *fl, Double_t *efl, Double_t &gain, Double_t &off, Double_t &egain, Double_t &eoff) { TGraphErrors *gr; Double_t mypar[2]; gr = new TGraphErrors(nscan, en, fl, een, efl); if (plot_flag) { gr->Fit("pol1"); gr->SetMarkerStyle(20); } else gr->Fit("pol1", "0Q"); TF1 *fitfun = gr->GetFunction("pol1"); fitfun->GetParameters(mypar); egain = fitfun->GetParError(1); eoff = fitfun->GetParError(0); gain = funcs->setScanSign() * mypar[1]; off = mypar[0]; return gr; } TGraphErrors *energyCalibration::calibrate(int nscan, Double_t *en, Double_t *een, TH1F **h1, Double_t &gain, Double_t &off, Double_t &egain, Double_t &eoff, int integral) { TH1F *h; Double_t mypar[6], emypar[6]; Double_t fl[nscan], efl[nscan]; for (int ien = 0; ien < nscan; ien++) { h = h1[ien]; if (integral) fitSCurve(h, mypar, emypar); else fitSpectrum(h, mypar, emypar); fl[ien] = mypar[2]; efl[ien] = emypar[2]; } return linearCalibration(nscan, en, een, fl, efl, gain, off, egain, eoff); } #endif