11     obj = root_file.Get(path)
 
   14             raise RuntimeError(f
"Object not found at path: {path}")
 
   16             logging.warning(f
"Object not found at path: {path}")
 
   20         path_parts = path.strip(
"/").
split(
"/")
 
   21         *dirs, name = path_parts
 
   22         current_dir = root_file
 
   24             current_dir = current_dir.Get(d)
 
   27                     raise RuntimeError(f
"Directory '{d}' not found in path '{'/'.join(dirs)}' for deletion.")
 
   29                     logging.warning(f
"Directory '{d}' not found in path '{'/'.join(dirs)}' for deletion.")
 
   32         current_dir.Delete(f
"{name};*")
 
   39     for part 
in path.strip(
"/").
split(
"/"):
 
   40         next_dir = current.Get(part)
 
   42             logging.info(f
"Saving directory not exist. Creating directory '{part}' in '{current.GetName()}'")
 
   45         current = ROOT.gDirectory
 
   52         with open(config_file, 
"r") 
as f:
 
   53             config_data = yaml.safe_load(f)
 
   54             self.
configs[name] = config_data
 
   55         logging.info(f
"Configuration for '{name}' loaded from {config_file}")
 
   58         """Returns the config for a specific domain (e.g., 'Electron')""" 
   62         """Returns all configs as (name, config) pairs""" 
   66     def __init__(self, input, output, rebin_factor, delete_original=False):
 
   76                 raise RuntimeError(f
"Histogram '{self.input}' not found for rebinning.")
 
   78                 logging.error(f
"Histogram '{self.input}' not found for rebinning.")
 
   82         *dir_parts, output_name = path_parts
 
   83         directory_path = 
"/".
join(dir_parts)
 
   85         root_file.cd(directory_path)
 
   87         if hist.GetDimension() == 1:
 
   89             rebinned.Write(output_name, ROOT.TObject.kOverwrite)
 
   90             logging.info(f
"Rebinned 1D histogram and saved as '{output_name}' in '{directory_path}'.")
 
   91         elif hist.GetDimension() == 2:
 
   94             hist.SetName(output_name)
 
   95             hist.Write(output_name, ROOT.TObject.kOverwrite)
 
   96             logging.info(f
"Rebinned 2D histogram and saved as '{output_name}' in '{directory_path}'.")
 
  103             input=fragment[
"input"],
 
  104             output=fragment[
"output"],
 
  105             rebin_factor=fragment[
"rebin"],
 
  106             delete_original=fragment.get(
"delete_original", 
False)
 
  110     def __init__(self, numerator, denominator, output):
 
  118         if not num 
or not den:
 
  120                 raise RuntimeError(f
"Missing histogram: {self.numerator} or {self.denominator}")
 
  122                 logging.error(f
"Missing histogram: {self.numerator} or {self.denominator}")
 
  125         if not ROOT.TEfficiency.CheckConsistency(num, den):
 
  126             logging.warning(
"Inconsistency detected between numerator and denominator histograms.")
 
  127             logging.info(
"Attempting to fix...")
 
  128             for i 
in range(1, num.GetNbinsX() + 1):
 
  129                 num_val = num.GetBinContent(i)
 
  130                 den_val = den.GetBinContent(i)
 
  132                     den.SetBinContent(i, 1e-6)
 
  134                     logging.info(f
"Setting denominator to a smaller value for bin {i} as it is zero. Any other options?")
 
  135                 if num_val > den_val:
 
  136                     logging.info(f
"For Bin {i}: Num ({num_val}) > Den ({den_val})! Adjusting by setting Den to Num. Any other options?")
 
  137                     den.SetBinContent(i, num_val)
 
  138             if not ROOT.TEfficiency.CheckConsistency(num, den):
 
  139                 logging.error(
"Unable to fix histogram inconsistencies. Aborting efficiency calculation.")
 
  142         eff = ROOT.TEfficiency(num, den)
 
  144         *dir_path, obj_name = path_parts
 
  145         directory_path = 
"/".
join(dir_path)
 
  146         eff.SetName(obj_name)
 
  147         eff.SetTitle(f
"{self.numerator}/{self.denominator};{num.GetXaxis().GetTitle()};Efficiency")
 
  149         root_file.cd(directory_path)
 
  151         logging.info(f
"Saved efficiency '{obj_name}' in '{directory_path}'.")
 
  156             numerator=fragment[
"numerator"],
 
  157             denominator=fragment[
"denominator"],
 
  158             output=fragment[
"output"]
 
  170                 raise RuntimeError(f
"Histogram '{self.histogram_name}' not found.")
 
  172                 logging.error(f
"Histogram '{self.histogram_name}' not found.")
 
  176         cum = [hist.GetBinContent(i) 
for i 
in range(0, hist.GetNbinsX() + 2)]
 
  177         for i 
in range(1, len(cum)):
 
  179         total = cum[-1] 
or 1.0
 
  180         cum = [x / total 
for x 
in cum]
 
  186         logging.info(f
"Resolution widths for {self.histogram_name} -> s68: {s68:.4f}, s90: {s90:.4f}")
 
  187         logging.info(
"Todo: Take a 2D histo, take the quantity say xaxis, make its small intervals %s", 
"and then compute the Resolution widths and then save as a 1D histo with quantity on xaxis and Resolution widths on yaxis")
 
  191         min_width = 
float(
"inf")
 
  193         for i 
in range(len(cum)):
 
  194             target = cum[i] + prob
 
  197             for j 
in range(i + 1, len(cum)):
 
  199                     width = hist.GetBinCenter(j) - hist.GetBinCenter(i)
 
  200                     if width < min_width:
 
  204         return 0.5 * (hist.GetBinCenter(best[1]) - hist.GetBinCenter(best[0]))
 
  209             histogram_name=fragment[
"histogram"]
 
  213     def __init__(self, signal_path, background_path, output):
 
  222         if not sig 
or not bkg: 
 
  224                 raise RuntimeError(f
"Missing histogram: {self.signal_path} or {self.background_path}")
 
  226                 logging.error(f
"Missing histogram: {self.signal_path} or {self.background_path}")
 
  231             logging.error(
"ROC graph is empty or could not be constructed.")
 
  235         *dir_path, obj_name = path_parts
 
  236         directory_path = 
"/".
join(dir_path)
 
  238         graph.SetName(obj_name)
 
  239         graph.SetTitle(f
"{obj_name};Signal Efficiency;Background Rejection")
 
  242         root_file.cd(directory_path)
 
  244         logging.info(f
"Saved ROC curve '{obj_name}' in '{directory_path}'.")
 
  247         n_bins = sig.GetNbinsX()
 
  248         total_sig = sig.Integral()
 
  249         total_bkg = bkg.Integral()
 
  251         x_vals, y_vals = [], []
 
  252         if total_sig == 0 
or total_bkg == 0:
 
  253             logging.error(
"Zero total signal or background counts; cannot compute ROC.")
 
  256         for cut_bin 
in range(1, n_bins + 1): 
 
  257             sig_pass = sig.Integral(cut_bin, n_bins)
 
  258             bkg_pass = bkg.Integral(cut_bin, n_bins)
 
  260             eff = sig_pass / total_sig 
 
  266             rej = total_bkg / bkg_pass 
 
  274         graph = ROOT.TGraphErrors(len(x_vals))
 
  275         for i, (x, y) 
in enumerate(zip(x_vals, y_vals)):
 
  276             graph.SetPoint(i, x, y)
 
  283             signal_path=fragment[
"signal"],
 
  284             background_path=fragment[
"background"],
 
  285             output=fragment[
"output"]
 
  289     def __init__(self, input_hist, bins_x, bins_y, projection_axis, output=None):
 
  301                 raise RuntimeError(f
"Histogram '{self.input_hist}' not found.")
 
  303                 logging.error(f
"Histogram '{self.input_hist}' not found.")
 
  310         *dir_path, output_name = path_parts
 
  311         directory_path = 
"/".
join(dir_path)
 
  313         root_file.cd(directory_path)
 
  316             proj_x = hist.ProjectionX(output_name, b1, b2).Clone(output_name)
 
  317             self.
project(proj_x, hist.GetXaxis().GetTitle(), 
"X")
 
  319             proj_y = hist.ProjectionY(output_name, b3, b4).Clone(output_name)
 
  320             self.
project(proj_y, hist.GetYaxis().GetTitle(), 
"Y")
 
  322     def project(self, projection, axis_title, axis):
 
  323         projection.SetTitle(f
";{axis_title};Entries")
 
  324         projection.SetStats(
False)
 
  326         logging.info(f
"Saved projection by integrating over {axis} axis as '{projection.GetName()}'.")
 
  331             input_hist=fragment[
"hist"],
 
  332             bins_x=fragment.get(
"bins_projection_x", [10, 200]),
 
  333             bins_y=fragment.get(
"bins_projection_y", [-2.47, 2.47]),
 
  334             projection_axis=fragment.get(
"projection_axis", 
"x"),
 
  335             output=fragment.get(
"output")
 
  339     def __init__(self, histo1, histo2, output, delete_inputs=False):
 
  348         if not histo1 
or not histo2:
 
  350                 raise RuntimeError(f
"Missing histograms: {self.histo1} or {self.histo2}")
 
  352                 logging.error(f
"Missing histograms: {self.histo1} or {self.histo2}")
 
  356         *dir_path, output_name = path_parts
 
  357         directory_path = 
"/".
join(dir_path)
 
  358         summed = histo1.Clone(output_name)
 
  361         root_file.cd(directory_path)
 
  362         summed.Write(output_name, ROOT.TObject.kOverwrite)
 
  363         logging.info(f
"Saved added histogram '{output_name}' in '{directory_path}'.")
 
  372             histo1=fragment[
"histo1_name"],
 
  373             histo2=fragment[
"histo2_name"],
 
  374             output=fragment[
"output"],
 
  375             delete_inputs=fragment.get(
"delete_inputs", 
False)
 
  379     def __init__(self, input_hist, new_path, x_label=None, y_label=None, xlow=None, xhigh=None):
 
  388         ROOT.TH1.AddDirectory(
False)
 
  392                 raise RuntimeError(f
"Histogram '{self.input_hist}' not found.")
 
  394                 logging.error(f
"Histogram '{self.input_hist}' not found.")
 
  398         *dir_path, new_name = path_parts
 
  399         directory_path = 
"/".
join(dir_path)
 
  401         root_file.cd(directory_path)
 
  403         if self.
xlow is None or self.
xhigh is None:
 
  404             h_out = hist.Clone(new_name)
 
  407             bin_low = 
max(1, 
min(ax.FindBin(self.
xlow), ax.GetNbins()))
 
  408             bin_high = 
max(1, 
min(ax.FindBin(self.
xhigh), ax.GetNbins()))
 
  409             nbins = bin_high - bin_low
 
  410             h_out = ROOT.TH1D(new_name, hist.GetTitle(), nbins + 1, 
float(self.
xlow), 
float(self.
xhigh))
 
  411             for i 
in range(nbins + 1):
 
  412                 h_out.SetBinContent(i + 1, hist.GetBinContent(i + bin_low))
 
  415             h_out.GetXaxis().SetTitle(self.
x_label)
 
  417             h_out.GetYaxis().SetTitle(self.
y_label)
 
  418         h_out.Write(new_name, ROOT.TObject.kOverwrite)
 
  419         logging.info(f
"Renamed and updated histogram '{self.input_hist}' → '{self.new_path}' in '{directory_path}' | X-axis: '{self.x_label}' | Y-axis: '{self.y_label}' | Range: ({self.xlow}, {self.xhigh})")
 
  424             input_hist=fragment[
"hist"],
 
  425             new_path=fragment.get(
"final_histo_name", fragment[
"hist"]),
 
  426             x_label=fragment.get(
"x_axis_label"),
 
  427             y_label=fragment.get(
"y_axis_label"),
 
  428             xlow=fragment.get(
"xlow"),
 
  429             xhigh=fragment.get(
"xhigh")
 
  442             toDelete=fragment[
"toDelete"]