legend-dataflow-scripts 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. legend_dataflow_scripts-0.1.0.dist-info/METADATA +57 -0
  2. legend_dataflow_scripts-0.1.0.dist-info/RECORD +36 -0
  3. legend_dataflow_scripts-0.1.0.dist-info/WHEEL +5 -0
  4. legend_dataflow_scripts-0.1.0.dist-info/entry_points.txt +18 -0
  5. legend_dataflow_scripts-0.1.0.dist-info/top_level.txt +1 -0
  6. legenddataflowscripts/__init__.py +17 -0
  7. legenddataflowscripts/_version.py +21 -0
  8. legenddataflowscripts/par/__init__.py +0 -0
  9. legenddataflowscripts/par/geds/__init__.py +0 -0
  10. legenddataflowscripts/par/geds/dsp/__init__.py +0 -0
  11. legenddataflowscripts/par/geds/dsp/dplms.py +145 -0
  12. legenddataflowscripts/par/geds/dsp/eopt.py +398 -0
  13. legenddataflowscripts/par/geds/dsp/evtsel.py +400 -0
  14. legenddataflowscripts/par/geds/dsp/nopt.py +120 -0
  15. legenddataflowscripts/par/geds/dsp/pz.py +217 -0
  16. legenddataflowscripts/par/geds/dsp/svm.py +28 -0
  17. legenddataflowscripts/par/geds/dsp/svm_build.py +69 -0
  18. legenddataflowscripts/par/geds/hit/__init__.py +0 -0
  19. legenddataflowscripts/par/geds/hit/aoe.py +245 -0
  20. legenddataflowscripts/par/geds/hit/ecal.py +778 -0
  21. legenddataflowscripts/par/geds/hit/lq.py +213 -0
  22. legenddataflowscripts/par/geds/hit/qc.py +326 -0
  23. legenddataflowscripts/tier/__init__.py +0 -0
  24. legenddataflowscripts/tier/dsp.py +263 -0
  25. legenddataflowscripts/tier/hit.py +148 -0
  26. legenddataflowscripts/utils/__init__.py +15 -0
  27. legenddataflowscripts/utils/alias_table.py +28 -0
  28. legenddataflowscripts/utils/cfgtools.py +14 -0
  29. legenddataflowscripts/utils/convert_np.py +31 -0
  30. legenddataflowscripts/utils/log.py +77 -0
  31. legenddataflowscripts/utils/pulser_removal.py +16 -0
  32. legenddataflowscripts/workflow/__init__.py +20 -0
  33. legenddataflowscripts/workflow/execenv.py +327 -0
  34. legenddataflowscripts/workflow/filedb.py +107 -0
  35. legenddataflowscripts/workflow/pre_compile_catalog.py +24 -0
  36. legenddataflowscripts/workflow/utils.py +113 -0
@@ -0,0 +1,213 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import pickle as pkl
5
+ import warnings
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+ from dbetto import TextDB
10
+ from dbetto.catalog import Props
11
+ from pygama.math.distributions import gaussian
12
+ from pygama.pargen.AoE_cal import * # noqa: F403
13
+ from pygama.pargen.lq_cal import * # noqa: F403
14
+ from pygama.pargen.lq_cal import LQCal
15
+ from pygama.pargen.utils import load_data
16
+
17
+ from ....utils import (
18
+ build_log,
19
+ convert_dict_np_to_float,
20
+ get_pulser_mask,
21
+ )
22
+
23
+ warnings.filterwarnings(action="ignore", category=RuntimeWarning)
24
+
25
+
26
+ def get_results_dict(lq_class):
27
+ return {
28
+ "cal_energy_param": lq_class.cal_energy_param,
29
+ "DEP_means": lq_class.timecorr_df.to_dict("index"),
30
+ "rt_correction": lq_class.dt_fit_pars,
31
+ "cut_fit_pars": lq_class.cut_fit_pars.to_dict(),
32
+ "cut_value": lq_class.cut_val,
33
+ "sfs": lq_class.low_side_sf.to_dict("index"),
34
+ }
35
+
36
+
37
+ def fill_plot_dict(lq_class, data, plot_options, plot_dict=None):
38
+ if plot_dict is not None:
39
+ for key, item in plot_options.items():
40
+ if item["options"] is not None:
41
+ plot_dict[key] = item["function"](lq_class, data, **item["options"])
42
+ else:
43
+ plot_dict[key] = item["function"](lq_class, data)
44
+ else:
45
+ plot_dict = {}
46
+ return plot_dict
47
+
48
+
49
+ def par_geds_hit_lq() -> None:
50
+ argparser = argparse.ArgumentParser()
51
+ argparser.add_argument("files", help="files", nargs="*", type=str)
52
+ argparser.add_argument(
53
+ "--pulser-file", help="pulser_file", type=str, required=False
54
+ )
55
+ argparser.add_argument(
56
+ "--tcm-filelist", help="tcm_filelist", type=str, required=False
57
+ )
58
+
59
+ argparser.add_argument("--ecal-file", help="ecal_file", type=str, required=True)
60
+ argparser.add_argument("--eres-file", help="eres_file", type=str, required=True)
61
+ argparser.add_argument("--inplots", help="in_plot_path", type=str, required=False)
62
+
63
+ argparser.add_argument("--configs", help="configs", type=str, required=True)
64
+ argparser.add_argument("--log", help="log_file", type=str)
65
+
66
+ argparser.add_argument("--datatype", help="Datatype", type=str, required=True)
67
+ argparser.add_argument("--timestamp", help="Timestamp", type=str, required=True)
68
+ argparser.add_argument("--channel", help="Channel", type=str, required=True)
69
+ argparser.add_argument("--table-name", help="table name", type=str, required=True)
70
+
71
+ argparser.add_argument("--plot-file", help="plot_file", type=str, required=False)
72
+ argparser.add_argument("--hit-pars", help="hit_pars", type=str)
73
+ argparser.add_argument("--lq-results", help="lq_results", type=str)
74
+
75
+ argparser.add_argument("-d", "--debug", help="debug_mode", action="store_true")
76
+ args = argparser.parse_args()
77
+
78
+ configs = TextDB(args.configs, lazy=True).on(args.timestamp, system=args.datatype)
79
+ config_dict = configs["snakemake_rules"]["pars_hit_lqcal"]
80
+
81
+ log = build_log(config_dict, args.log)
82
+
83
+ channel_dict = config_dict["inputs"]["lqcal_config"][args.channel]
84
+ kwarg_dict = Props.read_from(channel_dict)
85
+
86
+ ecal_dict = Props.read_from(args.ecal_file)
87
+ cal_dict = ecal_dict["pars"]["operations"]
88
+ eres_dict = ecal_dict["results"]["ecal"]
89
+
90
+ with Path(args.eres_file).open("rb") as o:
91
+ object_dict = pkl.load(o)
92
+
93
+ if kwarg_dict["run_lq"] is True:
94
+ kwarg_dict.pop("run_lq")
95
+
96
+ cdf = eval(kwarg_dict.pop("cdf")) if "cdf" in kwarg_dict else gaussian
97
+
98
+ if "plot_options" in kwarg_dict:
99
+ for field, item in kwarg_dict["plot_options"].items():
100
+ kwarg_dict["plot_options"][field]["function"] = eval(item["function"])
101
+
102
+ with Path(args.files[0]).open() as f:
103
+ files = f.read().splitlines()
104
+ files = sorted(files)
105
+
106
+ try:
107
+ eres = eres_dict[kwarg_dict["cal_energy_param"]]["eres_linear"].copy()
108
+
109
+ def eres_func(x):
110
+ return eval(eres["expression"], dict(x=x, **eres["parameters"]))
111
+
112
+ except KeyError:
113
+
114
+ def eres_func(x):
115
+ return x * np.nan
116
+
117
+ params = [
118
+ "lq80",
119
+ "dt_eff",
120
+ kwarg_dict["energy_param"],
121
+ kwarg_dict["cal_energy_param"],
122
+ kwarg_dict["cut_field"],
123
+ ]
124
+
125
+ # load data in
126
+ data, threshold_mask = load_data(
127
+ files,
128
+ args.table_name,
129
+ cal_dict,
130
+ params=params,
131
+ threshold=kwarg_dict.pop("threshold"),
132
+ return_selection_mask=True,
133
+ )
134
+
135
+ mask = get_pulser_mask(
136
+ pulser_file=args.pulser_file,
137
+ )
138
+
139
+ data["is_pulser"] = mask[threshold_mask]
140
+
141
+ lq = LQCal(
142
+ cal_dict,
143
+ kwarg_dict["cal_energy_param"],
144
+ kwarg_dict["dt_param"],
145
+ eres_func,
146
+ cdf,
147
+ selection_string=f"{kwarg_dict.pop('cut_field')}&(~is_pulser)",
148
+ debug_mode=args.debug | kwarg_dict.get("debug_mode", False),
149
+ )
150
+
151
+ data["LQ_Ecorr"] = np.divide(data["lq80"], data[kwarg_dict["energy_param"]])
152
+
153
+ lq.update_cal_dicts(
154
+ {
155
+ "LQ_Ecorr": {
156
+ "expression": f"lq80/{kwarg_dict['energy_param']}",
157
+ "parameters": {},
158
+ }
159
+ }
160
+ )
161
+
162
+ lq.calibrate(data, "LQ_Ecorr")
163
+ log.info("Calibrated LQ")
164
+
165
+ out_dict = get_results_dict(lq)
166
+ plot_dict = fill_plot_dict(lq, data, kwarg_dict.get("plot_options", None))
167
+
168
+ # need to change eres func as can't pickle lambdas
169
+ try:
170
+ lq.eres_func = eres_dict[kwarg_dict["cal_energy_param"]][
171
+ "eres_linear"
172
+ ].copy()
173
+ except KeyError:
174
+ lq.eres_func = {}
175
+ else:
176
+ out_dict = {}
177
+ plot_dict = {}
178
+ lq = None
179
+
180
+ if args.plot_file:
181
+ common_dict = plot_dict.pop("common") if "common" in list(plot_dict) else None
182
+ if args.inplots:
183
+ with Path(args.inplots).open("rb") as r:
184
+ out_plot_dict = pkl.load(r)
185
+ out_plot_dict.update({"lq": plot_dict})
186
+ else:
187
+ out_plot_dict = {"lq": plot_dict}
188
+
189
+ if "common" in list(out_plot_dict) and common_dict is not None:
190
+ out_plot_dict["common"].update(common_dict)
191
+ elif common_dict is not None:
192
+ out_plot_dict["common"] = common_dict
193
+
194
+ Path(args.plot_file).parent.mkdir(parents=True, exist_ok=True)
195
+ with Path(args.plot_file).open("wb") as w:
196
+ pkl.dump(out_plot_dict, w, protocol=pkl.HIGHEST_PROTOCOL)
197
+
198
+ final_hit_dict = convert_dict_np_to_float(
199
+ {
200
+ "pars": {"operations": cal_dict},
201
+ "results": dict(**ecal_dict["results"], lq=out_dict),
202
+ }
203
+ )
204
+ Path(args.hit_pars).parent.mkdir(parents=True, exist_ok=True)
205
+ Props.write_to(args.hit_pars, final_hit_dict)
206
+
207
+ final_object_dict = dict(
208
+ **object_dict,
209
+ lq=lq,
210
+ )
211
+ Path(args.lq_results).parent.mkdir(parents=True, exist_ok=True)
212
+ with Path(args.lq_results).open("wb") as w:
213
+ pkl.dump(final_object_dict, w, protocol=pkl.HIGHEST_PROTOCOL)
@@ -0,0 +1,326 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import pickle as pkl
6
+ import re
7
+ import warnings
8
+ from pathlib import Path
9
+
10
+ import numpy as np
11
+ from dbetto import TextDB
12
+ from dbetto.catalog import Props
13
+ from lgdo.lh5 import ls
14
+ from pygama.pargen.data_cleaning import (
15
+ generate_cut_classifiers,
16
+ get_keys,
17
+ )
18
+ from pygama.pargen.utils import load_data
19
+
20
+ from ....utils import (
21
+ build_log,
22
+ convert_dict_np_to_float,
23
+ get_pulser_mask,
24
+ )
25
+
26
+ warnings.filterwarnings(action="ignore", category=RuntimeWarning)
27
+
28
+
29
+ def par_geds_hit_qc() -> None:
30
+ argparser = argparse.ArgumentParser()
31
+ argparser.add_argument("--cal-files", help="cal_files", nargs="*", type=str)
32
+ argparser.add_argument("--fft-files", help="fft_files", nargs="*", type=str)
33
+
34
+ argparser.add_argument(
35
+ "--tcm-filelist", help="tcm_filelist", type=str, required=False
36
+ )
37
+ argparser.add_argument(
38
+ "--pulser-file", help="pulser_file", type=str, required=False
39
+ )
40
+ argparser.add_argument(
41
+ "--overwrite-files",
42
+ help="overwrite_files",
43
+ type=str,
44
+ required=False,
45
+ nargs="*",
46
+ )
47
+
48
+ argparser.add_argument("--configs", help="config", type=str, required=True)
49
+ argparser.add_argument("--log", help="log_file", type=str)
50
+
51
+ argparser.add_argument("--datatype", help="Datatype", type=str, required=True)
52
+ argparser.add_argument("--timestamp", help="Timestamp", type=str, required=True)
53
+ argparser.add_argument("--channel", help="Channel", type=str, required=True)
54
+ argparser.add_argument("--table-name", help="table name", type=str, required=True)
55
+ argparser.add_argument("--tier", help="tier", type=str, default="hit")
56
+
57
+ argparser.add_argument("--plot-path", help="plot_path", type=str, required=False)
58
+ argparser.add_argument("--save-path", help="save_path", type=str)
59
+ args = argparser.parse_args()
60
+
61
+ configs = TextDB(args.configs, lazy=True).on(args.timestamp, system=args.datatype)
62
+ if args.tier == "hit":
63
+ config_dict = configs["snakemake_rules"]["pars_hit_qc"]
64
+ elif args.tier == "pht":
65
+ config_dict = configs["snakemake_rules"]["pars_pht_qc"]
66
+ else:
67
+ msg = f"tier {args.tier} not recognized"
68
+ raise ValueError(msg)
69
+
70
+ log = build_log(config_dict, args.log)
71
+
72
+ # get metadata dictionary
73
+ channel_dict = config_dict["inputs"]["qc_config"][args.channel]
74
+ kwarg_dict = Props.read_from(channel_dict)
75
+
76
+ if args.overwrite_files:
77
+ overwrite = Props.read_from(args.overwrite_files)
78
+ if args.channel in overwrite:
79
+ overwrite = overwrite[args.channel]["pars"]["operations"]
80
+ else:
81
+ overwrite = None
82
+ else:
83
+ overwrite = None
84
+
85
+ if len(args.fft_files) == 1 and Path(args.fft_files[0]).suffix == ".filelist":
86
+ with Path(args.fft_files[0]).open() as f:
87
+ fft_files = f.read().splitlines()
88
+ else:
89
+ fft_files = args.fft_files
90
+
91
+ if len(args.cal_files) == 1 and Path(args.cal_files[0]).suffix == ".filelist":
92
+ with Path(args.cal_files[0]).open() as f:
93
+ cal_files = f.read().splitlines()
94
+ else:
95
+ cal_files = args.fft_files
96
+
97
+ search_name = (
98
+ args.table_name if args.table_name[-1] == "/" else args.table_name + "/"
99
+ )
100
+
101
+ kwarg_dict_fft = kwarg_dict["fft_fields"]
102
+ kwarg_dict_cal = kwarg_dict["cal_fields"]
103
+
104
+ cut_fields = get_keys(
105
+ [key.replace(search_name, "") for key in ls(cal_files[0], search_name)],
106
+ kwarg_dict_cal["cut_parameters"],
107
+ )
108
+ cut_fields += get_keys(
109
+ [key.replace(search_name, "") for key in ls(cal_files[0], search_name)],
110
+ kwarg_dict_fft["cut_parameters"],
111
+ )
112
+
113
+ if "initial_cal_cuts" in kwarg_dict:
114
+ init_cal = kwarg_dict["initial_cal_cuts"]
115
+ cut_fields += get_keys(
116
+ [key.replace(search_name, "") for key in ls(cal_files[0], search_name)],
117
+ init_cal["cut_parameters"],
118
+ )
119
+
120
+ if len(fft_files) > 0:
121
+ fft_data = load_data(
122
+ fft_files,
123
+ args.table_name,
124
+ {},
125
+ [*cut_fields, "t_sat_lo", "timestamp", "trapTmax"],
126
+ )
127
+
128
+ discharges = fft_data["t_sat_lo"] > 0
129
+ discharge_timestamps = np.where(fft_data["timestamp"][discharges])[0]
130
+ is_recovering = np.full(len(fft_data), False, dtype=bool)
131
+ for tstamp in discharge_timestamps:
132
+ is_recovering = is_recovering | np.where(
133
+ (
134
+ ((fft_data["timestamp"] - tstamp) < 0.01)
135
+ & ((fft_data["timestamp"] - tstamp) > 0)
136
+ ),
137
+ True,
138
+ False,
139
+ )
140
+ fft_data["is_recovering"] = is_recovering
141
+
142
+ hit_dict_fft = {}
143
+ plot_dict_fft = {}
144
+ cut_data = fft_data.query("is_recovering==0")
145
+ msg = f"cut_data shape: {len(cut_data)}"
146
+ log.debug(msg)
147
+ for name, cut in kwarg_dict_fft["cut_parameters"].items():
148
+ cut_dict, cut_plots = generate_cut_classifiers(
149
+ cut_data,
150
+ {name: cut},
151
+ kwarg_dict.get("rounding", 4),
152
+ display=1 if args.plot_path else 0,
153
+ )
154
+ hit_dict_fft.update(cut_dict)
155
+ plot_dict_fft.update(cut_plots)
156
+
157
+ msg = f"{name} calculated cut_dict is: {json.dumps(convert_dict_np_to_float(cut_dict), indent=2)}"
158
+ log.debug(msg)
159
+
160
+ ct_mask = np.full(len(fft_data), True, dtype=bool)
161
+ for outname, info in cut_dict.items():
162
+ # convert to pandas eval
163
+ exp = info["expression"]
164
+ for key in info.get("parameters", None):
165
+ exp = re.sub(
166
+ f"(?<![a-zA-Z0-9]){key}(?![a-zA-Z0-9])", f"@{key}", exp
167
+ )
168
+ fft_data[outname] = fft_data.eval(
169
+ exp, local_dict=info.get("parameters", None)
170
+ )
171
+ if "_classifier" not in outname:
172
+ ct_mask = ct_mask & fft_data[outname]
173
+ cut_data = fft_data[ct_mask]
174
+
175
+ log.debug("fft cuts applied")
176
+ msg = f"cut_dict is: {json.dumps(convert_dict_np_to_float(hit_dict_fft), indent=2)}"
177
+ log.debug(msg)
178
+
179
+ else:
180
+ hit_dict_fft = {}
181
+ plot_dict_fft = {}
182
+
183
+ if overwrite is not None:
184
+ for name in kwarg_dict_fft["cut_parameters"]:
185
+ for cut_name, cut_dict in overwrite.items():
186
+ if name in cut_name:
187
+ hit_dict_fft.update({cut_name: cut_dict})
188
+
189
+ # load data in
190
+ data, threshold_mask = load_data(
191
+ cal_files,
192
+ args.table_name,
193
+ {},
194
+ [*cut_fields, "timestamp", "trapTmax", "t_sat_lo"],
195
+ threshold=kwarg_dict_cal.get("threshold", 0),
196
+ return_selection_mask=True,
197
+ cal_energy_param="trapTmax",
198
+ )
199
+
200
+ mask = get_pulser_mask(
201
+ pulser_file=args.pulser_file,
202
+ )
203
+
204
+ data["is_pulser"] = mask[threshold_mask]
205
+
206
+ discharges = data["t_sat_lo"] > 0
207
+ discharge_timestamps = np.where(data["timestamp"][discharges])[0]
208
+ is_recovering = np.full(len(data), False, dtype=bool)
209
+ for tstamp in discharge_timestamps:
210
+ is_recovering = is_recovering | np.where(
211
+ (
212
+ ((data["timestamp"] - tstamp) < 0.01)
213
+ & ((data["timestamp"] - tstamp) > 0)
214
+ ),
215
+ True,
216
+ False,
217
+ )
218
+ data["is_recovering"] = is_recovering
219
+
220
+ rng = np.random.default_rng()
221
+ mask = np.full(len(data.query("~is_pulser & ~is_recovering")), False, dtype=bool)
222
+ mask[
223
+ rng.choice(len(data.query("~is_pulser & ~is_recovering")), 4000, replace=False)
224
+ ] = True
225
+
226
+ if "initial_cal_cuts" in kwarg_dict:
227
+ init_cal = kwarg_dict["initial_cal_cuts"]
228
+ hit_dict_init_cal, plot_dict_init_cal = generate_cut_classifiers(
229
+ data.query("~is_pulser & ~is_recovering")[mask],
230
+ init_cal["cut_parameters"],
231
+ init_cal.get("rounding", 4),
232
+ display=1 if args.plot_path else 0,
233
+ )
234
+ ct_mask = np.full(len(data), True, dtype=bool)
235
+ for outname, info in hit_dict_init_cal.items():
236
+ # convert to pandas eval
237
+ exp = info["expression"]
238
+ for key in info.get("parameters", None):
239
+ exp = re.sub(f"(?<![a-zA-Z0-9]){key}(?![a-zA-Z0-9])", f"@{key}", exp)
240
+ data[outname] = data.eval(exp, local_dict=info.get("parameters", None))
241
+ if "classifier" not in outname:
242
+ ct_mask = ct_mask & data[outname]
243
+
244
+ mask = mask[ct_mask[(~data["is_pulser"] & ~data["is_recovering"]).to_numpy()]]
245
+ data = data[ct_mask]
246
+ log.debug("initial cal cuts applied")
247
+ msg = f"cut_dict is: {json.dumps(convert_dict_np_to_float(hit_dict_init_cal), indent=2)}"
248
+ log.debug(msg)
249
+
250
+ else:
251
+ hit_dict_init_cal = {}
252
+ plot_dict_init_cal = {}
253
+
254
+ if len(data.query("~is_pulser & ~is_recovering")) < 500:
255
+ log.info("Less than 500 pulser events")
256
+ cal_data = data.query("~is_pulser & ~is_recovering")
257
+ else:
258
+ cal_data = data.query("~is_pulser & ~is_recovering")[mask]
259
+
260
+ hit_dict_cal, plot_dict_cal = generate_cut_classifiers(
261
+ cal_data,
262
+ kwarg_dict_cal["cut_parameters"],
263
+ kwarg_dict.get("rounding", 4),
264
+ display=1 if args.plot_path else 0,
265
+ )
266
+
267
+ if overwrite is not None:
268
+ for name in kwarg_dict_cal["cut_parameters"]:
269
+ for cut_name, cut_dict in overwrite.items():
270
+ if name in cut_name:
271
+ hit_dict_cal.update({cut_name: cut_dict})
272
+
273
+ hit_dict = {**hit_dict_fft, **hit_dict_init_cal, **hit_dict_cal}
274
+ plot_dict = {**plot_dict_fft, **plot_dict_init_cal, **plot_dict_cal}
275
+
276
+ hit_dict = convert_dict_np_to_float(hit_dict)
277
+
278
+ for outname, info in hit_dict.items():
279
+ # convert to pandas eval
280
+ exp = info["expression"]
281
+ for key in info.get("parameters", None):
282
+ exp = re.sub(f"(?<![a-zA-Z0-9]){key}(?![a-zA-Z0-9])", f"@{key}", exp)
283
+ if outname not in fft_data:
284
+ fft_data[outname] = fft_data.eval(
285
+ exp, local_dict=info.get("parameters", None)
286
+ )
287
+ if outname not in data:
288
+ data[outname] = data.eval(exp, local_dict=info.get("parameters", None))
289
+
290
+ qc_results = {}
291
+ for entry in hit_dict:
292
+ if "classifier" not in entry:
293
+ sf_cal = len(data.query(f"{entry}& ~is_pulser & ~is_recovering")) / len(
294
+ data.query("~is_pulser & ~is_recovering")
295
+ )
296
+ sf_cal_err = 100 * np.sqrt(
297
+ ((sf_cal) * (1 - sf_cal))
298
+ / len(data.query("~is_pulser & ~is_recovering"))
299
+ )
300
+ sf_fft = len(fft_data.query(f"{entry} & ~is_recovering")) / len(
301
+ fft_data.query("~is_recovering")
302
+ )
303
+ sf_fft_err = 100 * np.sqrt(
304
+ ((sf_fft) * (1 - sf_fft)) / len(fft_data.query("~is_recovering"))
305
+ )
306
+ sf_cal *= 100
307
+ sf_fft *= 100
308
+ msg = f"{entry} cut applied: {sf_cal:.2f}% of events passed the cut for cal data, {sf_fft:.2f}% for fft data"
309
+ log.info(msg)
310
+ qc_results[entry] = {
311
+ "sf_cal": sf_cal,
312
+ "sf_cal_err": sf_cal_err,
313
+ "sf_fft": sf_fft,
314
+ "sf_fft_err": sf_fft_err,
315
+ }
316
+ qc_results = convert_dict_np_to_float(qc_results)
317
+
318
+ Path(args.save_path).parent.mkdir(parents=True, exist_ok=True)
319
+ Props.write_to(
320
+ args.save_path, {"operations": hit_dict, "results": {"qc": qc_results}}
321
+ )
322
+
323
+ if args.plot_path:
324
+ Path(args.plot_path).parent.mkdir(parents=True, exist_ok=True)
325
+ with Path(args.plot_path).open("wb") as f:
326
+ pkl.dump({"qc": plot_dict}, f, protocol=pkl.HIGHEST_PROTOCOL)
File without changes