|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# Copyright 2019-2020 CERN and copyright holders of ALICE O2. |
| 4 | +# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. |
| 5 | +# All rights not expressly granted are reserved. |
| 6 | +# |
| 7 | +# This software is distributed under the terms of the GNU General Public |
| 8 | +# License v3 (GPL Version 3), copied verbatim in the file "COPYING". |
| 9 | +# |
| 10 | +# In applying this license CERN does not waive the privileges and immunities |
| 11 | +# granted to it by virtue of its status as an Intergovernmental Organization |
| 12 | +# or submit itself to any jurisdiction. |
| 13 | + |
| 14 | +""" |
| 15 | +A tool to calculate efficiency and upload it to CCDB. |
| 16 | +Author: Dawid Karpiński (dawid.karpinski@cern.ch) |
| 17 | +Modified by: Zuzanna Chochulska (zchochul@cern.ch) |
| 18 | +""" |
| 19 | + |
| 20 | +import argparse |
| 21 | +import subprocess |
| 22 | +import sys |
| 23 | +import tempfile |
| 24 | +import time |
| 25 | +from pathlib import Path |
| 26 | + |
| 27 | +import ROOT # pylint: disable=import-error |
| 28 | + |
| 29 | + |
| 30 | +class CustomHelpFormatter(argparse.HelpFormatter): |
| 31 | + "Add default value to help format" |
| 32 | + |
| 33 | + def _get_help_string(self, action): |
| 34 | + help_str = action.help |
| 35 | + if help_str is not None and action.default not in [argparse.SUPPRESS, None]: |
| 36 | + help_str += f" (default: {action.default})" |
| 37 | + return help_str |
| 38 | + |
| 39 | + |
| 40 | +parser = argparse.ArgumentParser( |
| 41 | + description="A tool to calculate efficiency and upload it to CCDB", |
| 42 | + formatter_class=CustomHelpFormatter, |
| 43 | +) |
| 44 | +parser.add_argument( |
| 45 | + "--alien-path", |
| 46 | + type=Path, |
| 47 | + help="path to train run's directory in Alien with analysis results " |
| 48 | + "[example: /alice/cern.ch/user/a/alihyperloop/outputs/0033/332611/70301]", |
| 49 | +) |
| 50 | +parser.add_argument( |
| 51 | + "--mc-reco", |
| 52 | + type=str, |
| 53 | + nargs="+", |
| 54 | + help="paths to MC Reco histograms, separated by space [example: task/mcreco_one/hPt task/mcreco_two/hPt]", |
| 55 | + required=True, |
| 56 | +) |
| 57 | +parser.add_argument( |
| 58 | + "--mc-truth", |
| 59 | + type=str, |
| 60 | + nargs="+", |
| 61 | + help="paths to MC Truth histograms, separated by space [example: task/mctruth_one/hPt task/mctruth_one/hPt]", |
| 62 | + required=True, |
| 63 | +) |
| 64 | +parser.add_argument( |
| 65 | + "--ccdb-path", |
| 66 | + type=str, |
| 67 | + help="location in CCDB to where objects will be uploaded", |
| 68 | + required=True, |
| 69 | +) |
| 70 | +parser.add_argument( |
| 71 | + "--ccdb-url", |
| 72 | + type=str, |
| 73 | + help="URL to CCDB", |
| 74 | + default="http://ccdb-test.cern.ch:8080", |
| 75 | +) |
| 76 | +parser.add_argument( |
| 77 | + "--ccdb-labels", |
| 78 | + type=str, |
| 79 | + nargs="+", |
| 80 | + help="custom labels to add to objects' metadata in CCDB [example: label1 label2]", |
| 81 | + default=[], |
| 82 | +) |
| 83 | +parser.add_argument( |
| 84 | + "--ccdb-lifetime", |
| 85 | + type=int, |
| 86 | + help="how long should objects in CCDB remain valid (milliseconds)", |
| 87 | + default=365 * 24 * 60 * 60 * 1000, # one year |
| 88 | +) |
| 89 | +args = parser.parse_args() |
| 90 | + |
| 91 | +if len(args.mc_reco) != len(args.mc_truth): |
| 92 | + print("[!] Provided number of histograms with MC Reco must match MC Truth", file=sys.stderr) |
| 93 | + sys.exit(1) |
| 94 | + |
| 95 | +if len(args.ccdb_labels) > 0 and len(args.ccdb_labels) != len(args.mc_reco): |
| 96 | + print("[!] You must provide labels for all particles", file=sys.stderr) |
| 97 | + sys.exit(1) |
| 98 | + |
| 99 | +if len(args.ccdb_labels) == 0: |
| 100 | + # if flag is not provided, fill with empty strings to match size |
| 101 | + args.ccdb_labels = [""] * len(args.mc_reco) |
| 102 | + |
| 103 | +ANALYSIS_RESULTS = "AnalysisResults.root" |
| 104 | +results_path = args.alien_path / ANALYSIS_RESULTS |
| 105 | +job_id = results_path.parent.name |
| 106 | +train_number = results_path.parent.parent.name |
| 107 | + |
| 108 | +tmp_dir = Path(tempfile.gettempdir()) |
| 109 | + |
| 110 | +res_dest = tmp_dir / f"{train_number}-{job_id}-{ANALYSIS_RESULTS}" |
| 111 | +eff_dest = tmp_dir / f"{train_number}-{job_id}-Efficiency.root" |
| 112 | + |
| 113 | +# get file from alien |
| 114 | +if not res_dest.is_file(): |
| 115 | + print(f"[↓] Downloading analysis results from Alien to '{res_dest}' ...", file=sys.stderr) |
| 116 | + ROOT.TGrid.Connect("alien://") |
| 117 | + try: |
| 118 | + subprocess.run( |
| 119 | + ["alien_cp", results_path, "file://" + str(res_dest)], |
| 120 | + capture_output=True, |
| 121 | + check=True, |
| 122 | + ) |
| 123 | + print("[-] Download complete!", file=sys.stderr) |
| 124 | + except subprocess.CalledProcessError as error: |
| 125 | + print(f"[!] Error while downloading results file: {error.stderr}", file=sys.stderr) |
| 126 | + sys.exit(1) |
| 127 | +else: |
| 128 | + print( |
| 129 | + f"[-] Skipping download from Alien, since '{res_dest}' is already present", |
| 130 | + file=sys.stderr, |
| 131 | + ) |
| 132 | + |
| 133 | +print() |
| 134 | + |
| 135 | +histos_to_upload = [] |
| 136 | + |
| 137 | +# get reco & truth histos |
| 138 | +with ( |
| 139 | + ROOT.TFile.Open(res_dest.as_uri()) as res_file, |
| 140 | + ROOT.TFile.Open(eff_dest.as_uri(), "recreate") as eff_file, |
| 141 | +): |
| 142 | + |
| 143 | + for idx, (mc_reco, mc_truth) in enumerate(zip(args.mc_reco, args.mc_truth)): |
| 144 | + hist_reco = res_file.Get(mc_reco) |
| 145 | + if not hist_reco: |
| 146 | + print(f"[!] Cannot find MC Reco histogram in '{mc_reco}', aborting", file=sys.stderr) |
| 147 | + sys.exit(1) |
| 148 | + |
| 149 | + hist_truth = res_file.Get(mc_truth) |
| 150 | + if not hist_truth: |
| 151 | + print(f"[!] Cannot find MC Truth histogram in '{mc_truth}', aborting", file=sys.stderr) |
| 152 | + sys.exit(1) |
| 153 | + |
| 154 | + hist_reco.Rebin(4) |
| 155 | + hist_truth.Rebin(4) |
| 156 | + |
| 157 | + num_bins = hist_reco.GetNbinsX() |
| 158 | + x_max = hist_reco.GetXaxis().GetBinLowEdge(num_bins) |
| 159 | + x_max += hist_reco.GetXaxis().GetBinWidth(num_bins) |
| 160 | + |
| 161 | + hist_name = f"Efficiency_part{idx + 1}" |
| 162 | + |
| 163 | + # calculate efficiency |
| 164 | + eff = ROOT.TH1F(hist_name, "", num_bins, 0, x_max) |
| 165 | + for bin_idx in range(1, num_bins + 1): # Bins start at 1 in ROOT |
| 166 | + denom = hist_truth.GetBinContent(bin_idx) |
| 167 | + if idx == 0: |
| 168 | + denom *= 0.489 |
| 169 | + eff.SetBinContent(bin_idx, hist_reco.GetBinContent(bin_idx) / denom if denom > 0 else 0) |
| 170 | + |
| 171 | + # save efficiency object to file |
| 172 | + eff_file.WriteObject(eff, hist_name) |
| 173 | + histos_to_upload.append(hist_name) |
| 174 | + |
| 175 | +if len(histos_to_upload) == 0: |
| 176 | + print("[-] Exiting, since there is nothing to upload", file=sys.stderr) |
| 177 | + sys.exit(1) |
| 178 | + |
| 179 | +# upload objects to ccdb |
| 180 | +try: |
| 181 | + for idx, key in enumerate(histos_to_upload): |
| 182 | + timestamp_start = int(time.time() * 1000) |
| 183 | + timestamp_end = timestamp_start + args.ccdb_lifetime |
| 184 | + |
| 185 | + print(f"[↑] Uploading {key} to {args.ccdb_url} ... ", file=sys.stderr, end="") |
| 186 | + upload_cmd = [ |
| 187 | + "o2-ccdb-upload", |
| 188 | + "--file", |
| 189 | + eff_dest.as_uri(), |
| 190 | + "--host", |
| 191 | + args.ccdb_url, |
| 192 | + "--key", |
| 193 | + key, |
| 194 | + "--path", |
| 195 | + args.ccdb_path, |
| 196 | + "--starttimestamp", |
| 197 | + str(timestamp_start), |
| 198 | + "--endtimestamp", |
| 199 | + str(timestamp_end), |
| 200 | + "--meta", |
| 201 | + f"trainNumber={train_number};label={args.ccdb_labels[idx]}", |
| 202 | + ] |
| 203 | + result = subprocess.run(upload_cmd, capture_output=True, check=True) |
| 204 | + |
| 205 | + if "html" in result.stdout.decode("utf-8"): |
| 206 | + print( |
| 207 | + f"\n[!] Something went wrong with upload request: {result.stdout.decode('utf-8')}", |
| 208 | + file=sys.stderr, |
| 209 | + ) |
| 210 | + sys.exit(1) |
| 211 | + |
| 212 | + if result.stderr: |
| 213 | + print(f"\n[!] Error while uploading: {result.stderr.decode('utf-8')}", file=sys.stderr) |
| 214 | + sys.exit(1) |
| 215 | + |
| 216 | + print("complete!", file=sys.stderr) |
| 217 | + |
| 218 | +except subprocess.CalledProcessError as error: |
| 219 | + print(f"\n[!] Error while uploading: {error.stderr.decode('utf-8')}", file=sys.stderr) |
| 220 | + sys.exit(1) |
| 221 | + |
| 222 | +print() |
| 223 | +print("[✓] Success!", file=sys.stderr) |
0 commit comments