Skip to content

Commit 72010ca

Browse files
committed
Feat: add cutculator
1 parent 90fac01 commit 72010ca

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

PWGCF/Femto/Macros/cutculator.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
#!/usr/bin/env python3
2+
3+
#!/usr/bin/env python3
4+
5+
# Copyright 2019-2025 CERN and copyright holders of ALICE O2.
6+
# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
7+
# All rights not expressly granted are reserved.
8+
#
9+
# This software is distributed under the terms of the GNU General Public
10+
# License v3 (GPL Version 3), copied verbatim in the file "COPYING".
11+
#
12+
# In applying this license CERN does not waive the privileges and immunities
13+
# granted to it by virtue of its status as an Intergovernmental Organization
14+
# or submit itself to any jurisdiction.
15+
16+
"""!
17+
@brief CutCulator (Compute bitmask for selecting particles in the Femto Framework)
18+
@author Anton Riedel <anton.riedel@cern.ch>, Technical University of Munich
19+
"""
20+
21+
import ROOT
22+
import argparse
23+
24+
VALUE_DELIM = "___"
25+
SECTION_DELIM = ":::"
26+
27+
28+
def parse_bin_label(label):
29+
"""Parse a bin label into a dictionary."""
30+
result = {}
31+
sections = label.split(SECTION_DELIM)
32+
for sec in sections:
33+
if VALUE_DELIM not in sec:
34+
continue
35+
key, value = sec.split(VALUE_DELIM, 1)
36+
result[key] = value
37+
return result
38+
39+
40+
def ask_user_selection(group):
41+
"""Prompt user to select bin(s) for this selection group."""
42+
# Separate minimal and optional bins
43+
minimal_bins = [b for b in group if b.get("MinimalCut", "0") == "1" and b.get("OptionalCut", "0") == "0"]
44+
optional_bins = [b for b in group if b.get("OptionalCut", "0") == "1"]
45+
46+
selected_bins = []
47+
48+
# Minimal selection (cannot skip, 0 = loosest minimal)
49+
if minimal_bins:
50+
print(f"\nSelection: {group[0].get('SelectionName', 'unknown')}")
51+
for idx, b in enumerate(minimal_bins):
52+
print(f" [{idx}] {b.get('Value', '')}")
53+
while True:
54+
sel_input = input("Enter index for minimal cut (0 = loosest minimal): ")
55+
if sel_input.strip() == "":
56+
sel_input = "0"
57+
try:
58+
sel_idx = int(sel_input)
59+
if 0 <= sel_idx < len(minimal_bins):
60+
selected_bins.append(minimal_bins[sel_idx])
61+
break
62+
except ValueError:
63+
pass
64+
print("Invalid input. Please enter a valid index.")
65+
66+
# Optional selection (can skip with 0)
67+
if optional_bins:
68+
print(f"Selection: {group[0].get('SelectionName', 'unknown')} (optional selection, 0 to skip)")
69+
for idx, b in enumerate(optional_bins, start=1):
70+
print(f" [{idx}] {b.get('Value', '')}")
71+
while True:
72+
sel_input = input("Enter indices separated by space (0 to skip): ")
73+
if not sel_input.strip() or sel_input.strip() == "0":
74+
break
75+
try:
76+
indices = [int(x) for x in sel_input.split()]
77+
if all(0 <= i <= len(optional_bins) for i in indices):
78+
for i in indices:
79+
if i != 0:
80+
selected_bins.append(optional_bins[i - 1])
81+
break
82+
except ValueError:
83+
pass
84+
print("Invalid input. Please enter valid indices separated by space.")
85+
86+
return selected_bins
87+
88+
89+
def main(rootfile_path, tdir_path="femto-producer"):
90+
print(f"Opening ROOT file: {rootfile_path}")
91+
f = ROOT.TFile.Open(rootfile_path)
92+
if not f:
93+
print("Cannot open ROOT file")
94+
return
95+
96+
print(f"Accessing directory: {tdir_path}")
97+
d = f.Get(tdir_path)
98+
if not d:
99+
print(f"Cannot access directory {tdir_path}")
100+
return
101+
102+
histograms = [k.GetName() for k in d.GetListOfKeys() if k.ReadObj().InheritsFrom("TH1")]
103+
if not histograms:
104+
print("No histograms found")
105+
return
106+
107+
print("\nHistograms found in directory:")
108+
for i, hname in enumerate(histograms):
109+
print(f" [{i}] {hname}")
110+
hidx = int(input("\nSelect histogram index: "))
111+
hname = histograms[hidx]
112+
hist = d.Get(hname)
113+
nbins = hist.GetNbinsX()
114+
print(f"\nUsing histogram: {hname}")
115+
print(f"Histogram contains {nbins} bins.\n")
116+
117+
# parse all bins, ignoring the last 2 special bins
118+
bins = []
119+
for i in range(1, nbins - 2 + 1):
120+
label = hist.GetXaxis().GetBinLabel(i)
121+
if not label:
122+
continue
123+
bdict = parse_bin_label(label)
124+
bdict["_bin_index"] = i
125+
bins.append(bdict)
126+
127+
# group by SelectionName
128+
groups = {}
129+
for b in bins:
130+
sel_name = b.get("SelectionName", f"unknown_{b['_bin_index']}")
131+
groups.setdefault(sel_name, []).append(b)
132+
133+
selected_bins = []
134+
135+
for group in groups.values():
136+
res = ask_user_selection(group)
137+
if res:
138+
selected_bins.extend(res)
139+
140+
# compute bitmask from selected bins
141+
bitmask = 0
142+
for b in selected_bins:
143+
pos = b.get("BitPosition", "")
144+
if pos.upper() == "X":
145+
continue
146+
bitmask |= 1 << int(pos)
147+
148+
print("\nFinal selected bitmask:")
149+
print(f"Decimal: {bitmask}")
150+
print(f"Binary: {bin(bitmask)}")
151+
print(f"Hex: {hex(bitmask)}")
152+
153+
154+
if __name__ == "__main__":
155+
parser = argparse.ArgumentParser()
156+
parser.add_argument("rootfile", help="Path to ROOT file")
157+
parser.add_argument("--dir", default="femto-producer", help="TDirectory path in ROOT file")
158+
args = parser.parse_args()
159+
main(args.rootfile, args.dir)

0 commit comments

Comments
 (0)