legend-dataflow-scripts 0.1.2__py3-none-any.whl → 0.1.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: legend-dataflow-scripts
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Python package for the processing scripts for LEGEND-200 data
5
5
  Author-email: George Marshall <ggmarsh@uw.edu>, Luigi Pertoldi <gipert@pm.me>
6
6
  Maintainer: The LEGEND Collaboration
@@ -23,7 +23,7 @@ Requires-Dist: pygama>=2.1
23
23
  Requires-Dist: dspeed>=1.6
24
24
  Requires-Dist: pylegendmeta>=1.2.5
25
25
  Requires-Dist: legend-pydataobj>=1.11
26
- Requires-Dist: legend-daq2lh5>=1.5.0
26
+ Requires-Dist: legend-daq2lh5>=1.6.1
27
27
  Requires-Dist: pip
28
28
  Provides-Extra: test
29
29
  Requires-Dist: legend-dataflow-scripts; extra == "test"
@@ -1,5 +1,5 @@
1
1
  legenddataflowscripts/__init__.py,sha256=hlpvTxSBjOyXlZUyOyYx3VwT5LS6zNzhAZnTmfT3NjU,303
2
- legenddataflowscripts/_version.py,sha256=bSmADqydH8nBu-J4lG8UVuR7hnU_zcwhnSav2oQ0W0A,511
2
+ legenddataflowscripts/_version.py,sha256=NIzzV8ZM0W-CSLuEs1weG4zPrn_-8yr1AwwI1iuS6yo,511
3
3
  legenddataflowscripts/par/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  legenddataflowscripts/par/geds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  legenddataflowscripts/par/geds/dsp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -7,30 +7,31 @@ legenddataflowscripts/par/geds/dsp/dplms.py,sha256=OOvnN_OcU046zhuyNTGxcmGYLCZ7Y
7
7
  legenddataflowscripts/par/geds/dsp/eopt.py,sha256=UklMozVw57X8EhOVjOxwKSXtuHkX45iJ2YMLx69TQps,13680
8
8
  legenddataflowscripts/par/geds/dsp/evtsel.py,sha256=qheFqGezjRxW9UNf9DV1n8VuZjCLGZT-WaWyhrfgPJ4,16485
9
9
  legenddataflowscripts/par/geds/dsp/nopt.py,sha256=K7R4OBfO0wek03VvhBoYXDP7lURi_Xoph9Kb3iMkfx4,4263
10
- legenddataflowscripts/par/geds/dsp/pz.py,sha256=6tkXvL1ND-j4ulxfsRJadiNXPWqqadT7k99eti9MK-k,8265
10
+ legenddataflowscripts/par/geds/dsp/pz.py,sha256=sucYUtsgWKYtR-GbmRh5b61EYHqNRMWGckD3DJdHUvs,8273
11
11
  legenddataflowscripts/par/geds/dsp/svm.py,sha256=eDneRB_PQZp8Q4n2VheTX3kbu4ufZQ-jnuCCjvtwFpk,826
12
12
  legenddataflowscripts/par/geds/dsp/svm_build.py,sha256=8K0NUpQqL2HUWRKjMpM5H-TienVPUzIuKED94ZZnIzA,2227
13
13
  legenddataflowscripts/par/geds/hit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- legenddataflowscripts/par/geds/hit/aoe.py,sha256=0reLWzYxau09DKyu_KrywC56njSLOVGAT6QfDBSNfvM,8499
14
+ legenddataflowscripts/par/geds/hit/aoe.py,sha256=4sXRz6UaPD7phihAO3_C42l7fJT8UrhhOZswzO8rAqw,11387
15
15
  legenddataflowscripts/par/geds/hit/ecal.py,sha256=tsIL9FP-aXgM2B1K8ygjcsLeNZ9Yv7je84fT-K7m3vQ,26194
16
- legenddataflowscripts/par/geds/hit/lq.py,sha256=CWasjmdaBNVk7AFNrV-RZ8UcvUzfww7wxvhrRdGSobc,7379
17
- legenddataflowscripts/par/geds/hit/qc.py,sha256=yT922UcNdw9X0yQ-JqoABeDuT-NMpKE2dvKO7qUGLes,11821
16
+ legenddataflowscripts/par/geds/hit/lq.py,sha256=td0qVvVRwYbhiA-IhAZYLgTdW6xpTeeE3mrSoWeLw2U,11307
17
+ legenddataflowscripts/par/geds/hit/qc.py,sha256=6PpA6aVjd9Gysfz_up7cuWw-ftwDA8F8gNKBcPPq4lc,12409
18
18
  legenddataflowscripts/tier/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  legenddataflowscripts/tier/dsp.py,sha256=J_ABEqbQurZHJOg8LV2aporNjZQpPg2bDZG6DkmuAL4,8633
20
20
  legenddataflowscripts/tier/hit.py,sha256=-LCfsqYAYPAuN2OKbPuh4G2P5B4uA4YmQGcqUtbZxis,4989
21
- legenddataflowscripts/utils/__init__.py,sha256=_eYTjqTMgjeQGO4HKmiqVAkyW9fh9FEz-_lQtcM-Lfg,366
21
+ legenddataflowscripts/utils/__init__.py,sha256=NPpxqapio8CeQIbG8gYnz-OPoUbOlDT6tjXV-lJrKWc,426
22
22
  legenddataflowscripts/utils/alias_table.py,sha256=e0wRiDcpje8VVr_PlcbkA0kPz0qSGpGX4vJ3tfGefMA,742
23
23
  legenddataflowscripts/utils/cfgtools.py,sha256=_1yxw_eJ08AihONhJ9sWet5HQZpOagj8Yg8y9LS3zX4,381
24
24
  legenddataflowscripts/utils/convert_np.py,sha256=8q_K3w3jAREG-9CJlErP3Vca0OYTYAYJIK4g_TClvBU,819
25
25
  legenddataflowscripts/utils/log.py,sha256=5v8J7PZJHHoSy12w9e9DkYmqdPvYXj6YfVaMbhvX614,2230
26
+ legenddataflowscripts/utils/plot_dict.py,sha256=-wfJC5U9OTE1Asazz64kT5Ta008w6UJmDSsr3YAubRM,449
26
27
  legenddataflowscripts/utils/pulser_removal.py,sha256=kuARdp1jf-lsUWcb0_KRDp-ZXzkHNrDCXUc3h7TJm7Q,424
27
28
  legenddataflowscripts/workflow/__init__.py,sha256=p-57OklNpVYqwMaLctLO8zMvhpXaTILkd3j4CaS_Juk,394
28
29
  legenddataflowscripts/workflow/execenv.py,sha256=O6Z6EH6Yp0JXnwX2wTzduHC9Q9gu-_d1RuuE2pQ5caE,9061
29
30
  legenddataflowscripts/workflow/filedb.py,sha256=rbvOcXUxLbHz177QuDIDAL3aysz-bZDjHOiMsRHssZo,3434
30
31
  legenddataflowscripts/workflow/pre_compile_catalog.py,sha256=cEK0KXh-ClSE2Bo9MK471o79XG22bMY5r-2tIihtCfk,790
31
32
  legenddataflowscripts/workflow/utils.py,sha256=eKE8KIG2ffynZt9fTbI1SVQV85i3aW9GFGh1Nio1iDo,3118
32
- legend_dataflow_scripts-0.1.2.dist-info/METADATA,sha256=FsY5VWfz1t6KmRXwG2iRBnawVy5_yqy3_rNpT50FOHo,3122
33
- legend_dataflow_scripts-0.1.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
34
- legend_dataflow_scripts-0.1.2.dist-info/entry_points.txt,sha256=B197waSm-orA_ZS-9rkxNDsmOHdCn8CbWodnlqXQKRg,1313
35
- legend_dataflow_scripts-0.1.2.dist-info/top_level.txt,sha256=s8E2chjJNYUbrN6whFG_VCsJKySFp1IOXLcUefA7DB0,22
36
- legend_dataflow_scripts-0.1.2.dist-info/RECORD,,
33
+ legend_dataflow_scripts-0.1.3.dist-info/METADATA,sha256=ajopD8KYE0gSC2mRkgv3Dc4SCkb5MFc8vhRMLh9WH9A,3122
34
+ legend_dataflow_scripts-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
35
+ legend_dataflow_scripts-0.1.3.dist-info/entry_points.txt,sha256=B197waSm-orA_ZS-9rkxNDsmOHdCn8CbWodnlqXQKRg,1313
36
+ legend_dataflow_scripts-0.1.3.dist-info/top_level.txt,sha256=s8E2chjJNYUbrN6whFG_VCsJKySFp1IOXLcUefA7DB0,22
37
+ legend_dataflow_scripts-0.1.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.2'
21
- __version_tuple__ = version_tuple = (0, 1, 2)
20
+ __version__ = version = '0.1.3'
21
+ __version_tuple__ = version_tuple = (0, 1, 3)
@@ -181,14 +181,14 @@ def par_geds_dsp_pz() -> None:
181
181
 
182
182
  plot_dict = tau.plot_waveforms_after_correction(
183
183
  tb_data,
184
- kwarg_dict("wf_pz_field", "wf_pz"),
184
+ kwarg_dict.get("wf_pz_field", "wf_pz"),
185
185
  norm_param=kwarg_dict.get("norm_param", "pz_mean"),
186
186
  xlim=[0, len(tb_data[kwarg_dict["wf_field"]]["values"].nda[0])],
187
187
  )
188
188
 
189
189
  zoomed = tau.plot_waveforms_after_correction(
190
190
  tb_data,
191
- kwarg_dict("wf_pz_field", "wf_pz"),
191
+ kwarg_dict.get("wf_pz_field", "wf_pz"),
192
192
  norm_param=kwarg_dict.get("norm_param", "pz_mean"),
193
193
  xlim=[400, len(tb_data[kwarg_dict["wf_field"]]["values"].nda[0])],
194
194
  ylim=[0.8, 1.1],
@@ -1,7 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ import copy
5
+ import logging
4
6
  import pickle as pkl
7
+ import re
8
+ import time
5
9
  import warnings
6
10
  from pathlib import Path
7
11
 
@@ -9,42 +13,172 @@ import numpy as np
9
13
  from dbetto import TextDB
10
14
  from dbetto.catalog import Props
11
15
  from pygama.pargen.AoE_cal import * # noqa: F403
12
- from pygama.pargen.AoE_cal import CalAoE, Pol1, SigmaFit, aoe_peak
16
+ from pygama.pargen.AoE_cal import CalAoE
13
17
  from pygama.pargen.utils import load_data
14
18
 
15
19
  from ....utils import (
16
20
  build_log,
17
21
  convert_dict_np_to_float,
22
+ fill_plot_dict,
18
23
  get_pulser_mask,
19
24
  )
20
25
 
26
+ log = logging.getLogger(__name__)
27
+
21
28
  warnings.filterwarnings(action="ignore", category=RuntimeWarning)
22
29
 
23
30
 
24
31
  def get_results_dict(aoe_class):
25
- return {
26
- "cal_energy_param": aoe_class.cal_energy_param,
27
- "dt_param": aoe_class.dt_param,
28
- "rt_correction": aoe_class.dt_corr,
29
- "1000-1300keV": aoe_class.timecorr_df.to_dict("index"),
30
- "correction_fit_results": aoe_class.energy_corr_res_dict,
31
- "low_cut": aoe_class.low_cut_val,
32
- "high_cut": aoe_class.high_cut_val,
33
- "low_side_sfs": aoe_class.low_side_sfs.to_dict("index"),
34
- "2_side_sfs": aoe_class.two_side_sfs.to_dict("index"),
35
- }
32
+ result_dict = {}
33
+ for tstamp in aoe_class.low_side_sfs_by_run:
34
+ result_dict[tstamp] = {
35
+ "cal_energy_param": aoe_class.cal_energy_param,
36
+ "dt_param": aoe_class.dt_param,
37
+ "rt_correction": aoe_class.dt_corr,
38
+ "1000-1300keV": aoe_class.timecorr_df.to_dict("index"),
39
+ "correction_fit_results": aoe_class.energy_corr_res_dict,
40
+ "low_cut": aoe_class.low_cut_val,
41
+ "high_cut": aoe_class.high_cut_val,
42
+ "low_side_sfs": aoe_class.low_side_sfs.to_dict("index"),
43
+ "2_side_sfs": aoe_class.two_side_sfs.to_dict("index"),
44
+ "low_side_sfs_by_run": aoe_class.low_side_sfs_by_run[tstamp].to_dict(
45
+ "index"
46
+ ),
47
+ "2_side_sfs_by_run": aoe_class.two_side_sfs_by_run[tstamp].to_dict("index"),
48
+ }
49
+ return result_dict
50
+
51
+
52
+ def run_aoe_calibration(
53
+ data,
54
+ cal_dicts,
55
+ results_dicts,
56
+ object_dicts,
57
+ plot_dicts,
58
+ config,
59
+ debug_mode=False,
60
+ ):
61
+ if isinstance(config, str | list):
62
+ config = Props.read_from(config)
63
+
64
+ if config.get("run_aoe", True) is True:
65
+ if "plot_options" in config:
66
+ for field, item in config["plot_options"].items():
67
+ config["plot_options"][field]["function"] = eval(item["function"])
68
+
69
+ if "dt_cut" in config and config["dt_cut"] is not None:
70
+ cut_dict = config["dt_cut"]["cut"]
71
+ for tstamp in cal_dicts:
72
+ cal_dicts[tstamp].update(cut_dict)
73
+
74
+ exp = cut_dict[next(iter(cut_dict))]["expression"]
75
+ for key in cut_dict[next(iter(cut_dict))]["parameters"]:
76
+ exp = re.sub(f"(?<![a-zA-Z0-9]){key}(?![a-zA-Z0-9])", f"@{key}", exp)
77
+ data[next(iter(cut_dict))] = data.eval(
78
+ exp, local_dict=cut_dict[next(iter(cut_dict))]["parameters"]
79
+ )
80
+
81
+ try:
82
+ eres = copy.deepcopy(
83
+ results_dicts[next(iter(results_dicts))]["partition_ecal"][
84
+ config["cal_energy_param"]
85
+ ]["eres_linear"]
86
+ )
87
+
88
+ def eres_func(x):
89
+ return eval(eres["expression"], dict(x=x, **eres["parameters"]))
90
+
91
+ if np.isnan(eres_func(2000)):
92
+ raise RuntimeError
93
+ except (KeyError, RuntimeError):
94
+ try:
95
+ eres = copy.deepcopy(
96
+ results_dicts[next(iter(results_dicts))]["ecal"][
97
+ config["cal_energy_param"]
98
+ ]["eres_linear"]
99
+ )
100
+
101
+ def eres_func(x):
102
+ return eval(eres["expression"], dict(x=x, **eres["parameters"]))
103
+
104
+ except KeyError:
105
+
106
+ def eres_func(x):
107
+ return x * np.nan
108
+
109
+ data["AoE_Uncorr"] = (
110
+ data[config["current_param"]] / data[config["energy_param"]]
111
+ )
112
+
113
+ start = time.time()
114
+ log.info("calibrating A/E")
115
+
116
+ aoe = CalAoE(
117
+ cal_dicts=cal_dicts,
118
+ cal_energy_param=config["cal_energy_param"],
119
+ eres_func=eres_func,
120
+ pdf=eval(config.get("pdf", "aoe_peak")),
121
+ mean_func=eval(config.get("mean_func", "Pol1")),
122
+ sigma_func=eval(config.get("sigma_func", "SigmaFit")),
123
+ selection_string=f"{config['cut_field']}&(~is_pulser)",
124
+ dt_corr=config.get("dt_corr", False),
125
+ dep_correct=config.get("dep_correct", False),
126
+ dt_cut=config.get("dt_cut", None),
127
+ dt_param=config.get("dt_param", 3),
128
+ high_cut_val=config.get("high_cut_val", 3),
129
+ compt_bands_width=config.get("debug_mode", 20),
130
+ debug_mode=debug_mode | config.get("debug_mode", False),
131
+ )
132
+ aoe.update_cal_dicts(
133
+ {
134
+ "AoE_Uncorr": {
135
+ "expression": f"{config['current_param']}/{config['energy_param']}",
136
+ "parameters": {},
137
+ }
138
+ }
139
+ )
140
+ aoe.calibrate(data, "AoE_Uncorr")
36
141
 
142
+ msg = f"A/E calibration completed in {time.time() - start:.2f} seconds"
143
+ log.info(msg)
37
144
 
38
- def fill_plot_dict(aoe_class, data, plot_options, plot_dict=None):
39
- if plot_dict is not None:
40
- for key, item in plot_options.items():
41
- if item["options"] is not None:
42
- plot_dict[key] = item["function"](aoe_class, data, **item["options"])
43
- else:
44
- plot_dict[key] = item["function"](aoe_class, data)
145
+ out_dict = get_results_dict(aoe)
146
+ aoe_plot_dict = fill_plot_dict(aoe, data, config.get("plot_options", None))
147
+
148
+ aoe.pdf = aoe.pdf.name
149
+ # need to change eres func as can't pickle lambdas
150
+ try:
151
+ aoe.eres_func = results_dicts[next(iter(results_dicts))]["partition_ecal"][
152
+ config["cal_energy_param"]
153
+ ]["eres_linear"]
154
+ except KeyError:
155
+ aoe.eres_func = {}
45
156
  else:
46
- plot_dict = {}
47
- return plot_dict
157
+ out_dict = dict.fromkeys(cal_dicts)
158
+ aoe_plot_dict = {}
159
+ aoe = None
160
+
161
+ out_result_dicts = {}
162
+ for tstamp, result_dict in results_dicts.items():
163
+ out_result_dicts[tstamp] = dict(**result_dict, aoe=out_dict[tstamp])
164
+
165
+ out_object_dicts = {}
166
+ for tstamp, object_dict in object_dicts.items():
167
+ out_object_dicts[tstamp] = dict(**object_dict, aoe=aoe)
168
+
169
+ common_dict = (
170
+ aoe_plot_dict.pop("common") if "common" in list(aoe_plot_dict) else None
171
+ )
172
+ out_plot_dicts = {}
173
+ for tstamp, plot_dict in plot_dicts.items():
174
+ if "common" in list(plot_dict) and common_dict is not None:
175
+ plot_dict["common"].update(common_dict)
176
+ elif common_dict is not None:
177
+ plot_dict["common"] = common_dict
178
+ plot_dict.update({"aoe": aoe_plot_dict})
179
+ out_plot_dicts[tstamp] = plot_dict
180
+
181
+ return cal_dicts, out_result_dicts, out_object_dicts, out_plot_dicts
48
182
 
49
183
 
50
184
  def par_geds_hit_aoe() -> None:
@@ -79,7 +213,7 @@ def par_geds_hit_aoe() -> None:
79
213
  configs = TextDB(args.configs, lazy=True).on(args.timestamp, system=args.datatype)
80
214
  config_dict = configs["snakemake_rules"]["pars_hit_aoecal"]
81
215
 
82
- log = build_log(config_dict, args.log)
216
+ build_log(config_dict, args.log)
83
217
 
84
218
  channel_dict = config_dict["inputs"]["aoecal_config"][args.channel]
85
219
  kwarg_dict = Props.read_from(channel_dict)
@@ -91,40 +225,16 @@ def par_geds_hit_aoe() -> None:
91
225
  with Path(args.eres_file).open("rb") as o:
92
226
  object_dict = pkl.load(o)
93
227
 
94
- if kwarg_dict["run_aoe"] is True:
95
- kwarg_dict.pop("run_aoe")
96
-
97
- pdf = eval(kwarg_dict.pop("pdf")) if "pdf" in kwarg_dict else aoe_peak
98
-
99
- sigma_func = (
100
- eval(kwarg_dict.pop("sigma_func"))
101
- if "sigma_func" in kwarg_dict
102
- else SigmaFit
103
- )
104
-
105
- mean_func = (
106
- eval(kwarg_dict.pop("mean_func")) if "mean_func" in kwarg_dict else Pol1
107
- )
108
-
109
- if "plot_options" in kwarg_dict:
110
- for field, item in kwarg_dict["plot_options"].items():
111
- kwarg_dict["plot_options"][field]["function"] = eval(item["function"])
112
-
113
- with Path(args.files[0]).open() as f:
114
- files = f.read().splitlines()
115
- files = sorted(files)
116
-
117
- try:
118
- eres = eres_dict[kwarg_dict["cal_energy_param"]]["eres_linear"].copy()
119
-
120
- def eres_func(x):
121
- return eval(eres["expression"], dict(x=x, **eres["parameters"]))
122
-
123
- except KeyError:
228
+ if args.inplots:
229
+ with Path(args.inplots).open("rb") as r:
230
+ out_plot_dict = pkl.load(r)
231
+ else:
232
+ out_plot_dict = {}
124
233
 
125
- def eres_func(x):
126
- return x * np.nan
234
+ with Path(args.files[0]).open() as f:
235
+ files = sorted(f.read().splitlines())
127
236
 
237
+ if kwarg_dict["run_aoe"] is True:
128
238
  params = [
129
239
  kwarg_dict["current_param"],
130
240
  "tp_0_est",
@@ -150,9 +260,11 @@ def par_geds_hit_aoe() -> None:
150
260
  args.table_name,
151
261
  cal_dict,
152
262
  params=params,
153
- threshold=kwarg_dict.pop("threshold"),
263
+ threshold=kwarg_dict["threshold"],
154
264
  return_selection_mask=True,
155
265
  )
266
+ msg = f"Loaded {len(data)} events"
267
+ log.info(msg)
156
268
 
157
269
  mask = get_pulser_mask(
158
270
  pulser_file=args.pulser_file,
@@ -160,61 +272,35 @@ def par_geds_hit_aoe() -> None:
160
272
 
161
273
  data["is_pulser"] = mask[threshold_mask]
162
274
 
163
- data["AoE_Uncorr"] = (
164
- data[kwarg_dict["current_param"]] / data[kwarg_dict["energy_param"]]
165
- )
166
- aoe = CalAoE(
167
- cal_dicts=cal_dict,
168
- cal_energy_param=kwarg_dict["cal_energy_param"],
169
- eres_func=eres_func,
170
- pdf=pdf,
171
- mean_func=mean_func,
172
- sigma_func=sigma_func,
173
- selection_string=f"{kwarg_dict.pop('cut_field')}&(~is_pulser)",
174
- dt_corr=kwarg_dict.get("dt_corr", False),
175
- dep_correct=kwarg_dict.get("dep_correct", False),
176
- dt_cut=kwarg_dict.get("dt_cut", None),
177
- dt_param=kwarg_dict.get("dt_param", 3),
178
- high_cut_val=kwarg_dict.get("high_cut_val", 3),
179
- compt_bands_width=kwarg_dict.get("debug_mode", 20),
180
- debug_mode=args.debug | kwarg_dict.get("debug_mode", False),
181
- )
182
- aoe.update_cal_dicts(
183
- {
184
- "AoE_Uncorr": {
185
- "expression": f"{kwarg_dict['current_param']}/{kwarg_dict['energy_param']}",
186
- "parameters": {},
187
- }
188
- }
189
- )
190
- aoe.calibrate(data, "AoE_Uncorr")
275
+ msg = f"{len(data.query('~is_pulser'))} non pulser events"
276
+ log.info(msg)
191
277
 
192
- log.info("Calibrated A/E")
193
- out_dict = get_results_dict(aoe)
194
- plot_dict = fill_plot_dict(aoe, data, kwarg_dict.get("plot_options", None))
278
+ with Path(args.files[0]).open() as f:
279
+ files = f.read().splitlines()
280
+ files = sorted(files)
195
281
 
196
- aoe.pdf = aoe.pdf.name
282
+ data["run_timestamp"] = args.timestamp
197
283
 
198
- # need to change eres func as can't pickle lambdas
199
- try:
200
- aoe.eres_func = eres_dict[kwarg_dict["cal_energy_param"]][
201
- "eres_linear"
202
- ].copy()
203
- except KeyError:
204
- aoe.eres_func = {}
284
+ cal_dict, results_dicts, object_dicts, plot_dicts = run_aoe_calibration(
285
+ data,
286
+ {args.timestamp: cal_dict},
287
+ {args.timestamp: eres_dict},
288
+ {args.timestamp: object_dict},
289
+ {args.timestamp: out_plot_dict},
290
+ kwarg_dict,
291
+ debug_mode=args.debug,
292
+ )
293
+ cal_dict = cal_dict[args.timestamp]
294
+ results_dict = results_dicts[args.timestamp]
295
+ aoe = object_dicts[args.timestamp]
296
+ plot_dict = plot_dicts[args.timestamp]
205
297
  else:
206
- out_dict = {}
207
- plot_dict = {}
208
298
  aoe = None
299
+ plot_dict = out_plot_dict
209
300
 
210
301
  if args.plot_file:
211
302
  common_dict = plot_dict.pop("common") if "common" in list(plot_dict) else None
212
- if args.inplots:
213
- with Path(args.inplots).open("rb") as r:
214
- out_plot_dict = pkl.load(r)
215
- out_plot_dict.update({"aoe": plot_dict})
216
- else:
217
- out_plot_dict = {"aoe": plot_dict}
303
+ out_plot_dict.update({"aoe": plot_dict})
218
304
 
219
305
  if "common" in list(out_plot_dict) and common_dict is not None:
220
306
  out_plot_dict["common"].update(common_dict)
@@ -226,10 +312,9 @@ def par_geds_hit_aoe() -> None:
226
312
  pkl.dump(out_plot_dict, w, protocol=pkl.HIGHEST_PROTOCOL)
227
313
 
228
314
  Path(args.hit_pars).parent.mkdir(parents=True, exist_ok=True)
229
- results_dict = dict(**ecal_dict["results"], aoe=out_dict)
230
315
  final_hit_dict = {
231
316
  "pars": {"operations": cal_dict},
232
- "results": results_dict,
317
+ "results": dict(**ecal_dict["results"], aoe=results_dict),
233
318
  }
234
319
 
235
320
  final_hit_dict = convert_dict_np_to_float(final_hit_dict)
@@ -237,9 +322,5 @@ def par_geds_hit_aoe() -> None:
237
322
  Props.write_to(args.hit_pars, final_hit_dict)
238
323
 
239
324
  Path(args.aoe_results).parent.mkdir(parents=True, exist_ok=True)
240
- final_object_dict = dict(
241
- **object_dict,
242
- aoe=aoe,
243
- )
244
325
  with Path(args.aoe_results).open("wb") as w:
245
- pkl.dump(final_object_dict, w, protocol=pkl.HIGHEST_PROTOCOL)
326
+ pkl.dump(dict(**object_dict, aoe=aoe), w, protocol=pkl.HIGHEST_PROTOCOL)
@@ -1,11 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ import copy
5
+ import logging
4
6
  import pickle as pkl
7
+ import time
5
8
  import warnings
6
9
  from pathlib import Path
7
10
 
8
11
  import numpy as np
12
+ import pandas as pd
9
13
  from dbetto import TextDB
10
14
  from dbetto.catalog import Props
11
15
  from pygama.math.distributions import gaussian
@@ -17,9 +21,11 @@ from pygama.pargen.utils import load_data
17
21
  from ....utils import (
18
22
  build_log,
19
23
  convert_dict_np_to_float,
24
+ fill_plot_dict,
20
25
  get_pulser_mask,
21
26
  )
22
27
 
28
+ log = logging.getLogger(__name__)
23
29
  warnings.filterwarnings(action="ignore", category=RuntimeWarning)
24
30
 
25
31
 
@@ -34,16 +40,172 @@ def get_results_dict(lq_class):
34
40
  }
35
41
 
36
42
 
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)
43
+ def lq_calibration(
44
+ data: pd.DataFrame,
45
+ cal_dicts: dict,
46
+ energy_param: str,
47
+ cal_energy_param: str,
48
+ dt_param: str,
49
+ eres_func: callable,
50
+ cdf: callable = gaussian,
51
+ selection_string: str = "",
52
+ plot_options: dict | None = None,
53
+ debug_mode: bool = False,
54
+ ):
55
+ """Loads in data from the provided files and runs the LQ calibration on said files
56
+
57
+ Parameters
58
+ ----------
59
+ data: pd.DataFrame
60
+ A dataframe containing the data used for calibrating LQ
61
+ cal_dicts: dict
62
+ A dict of hit-level operations to apply to the data
63
+ energy_param: string
64
+ The energy parameter of choice. Used for normalizing the
65
+ raw lq values
66
+ cal_energy_param: string
67
+ The calibrated energy parameter of choice
68
+ dt_param: string
69
+ The drift-time parameter of choice
70
+ eres_func: callable
71
+ The energy resolution functions
72
+ cdf: callable
73
+ The CDF used for the binned fitting of LQ distributions
74
+ cut_field: string
75
+ A string of flags to apply to the data when running the calibration
76
+ plot_options: dict
77
+ A dict containing the plot functions the user wants to run,and any
78
+ user options to provide those plot functions
79
+ Returns
80
+ -------
81
+ cal_dicts: dict
82
+ The user provided dict, updated with hit-level operations for LQ
83
+ results_dict: dict
84
+ A dict containing the results of the LQ calibration
85
+ plot_dict: dict
86
+ A dict containing all the figures specified by the plot options
87
+ lq: cal_lq class
88
+ The cal_lq object used for the LQ calibration
89
+ """
90
+
91
+ lq = LQCal(
92
+ cal_dicts,
93
+ cal_energy_param,
94
+ dt_param,
95
+ eres_func,
96
+ cdf,
97
+ selection_string,
98
+ debug_mode=debug_mode,
99
+ )
100
+
101
+ data["LQ_Ecorr"] = np.divide(data["lq80"], data[energy_param])
102
+
103
+ lq.update_cal_dicts(
104
+ {
105
+ "LQ_Ecorr": {
106
+ "expression": f"lq80/{energy_param}",
107
+ "parameters": {},
108
+ }
109
+ }
110
+ )
111
+
112
+ lq.calibrate(data, "LQ_Ecorr")
113
+ return cal_dicts, get_results_dict(lq), fill_plot_dict(lq, data, plot_options), lq
114
+
115
+
116
+ def run_lq_calibration(
117
+ data,
118
+ cal_dicts,
119
+ results_dicts,
120
+ object_dicts,
121
+ plot_dicts,
122
+ configs,
123
+ debug_mode=False,
124
+ # gen_plots=True,
125
+ ):
126
+ if isinstance(configs, str | list):
127
+ configs = Props.read_from(configs)
128
+
129
+ if configs.pop("run_lq") is True:
130
+ if "plot_options" in configs:
131
+ for field, item in configs["plot_options"].items():
132
+ configs["plot_options"][field]["function"] = eval(item["function"])
133
+
134
+ try:
135
+ eres = copy.deepcopy(
136
+ results_dicts[next(iter(results_dicts))]["partition_ecal"][
137
+ configs["cal_energy_param"]
138
+ ]["eres_linear"]
139
+ )
140
+
141
+ def eres_func(x):
142
+ return eval(eres["expression"], dict(x=x, **eres["parameters"]))
143
+
144
+ if np.isnan(eres_func(2000)):
145
+ raise RuntimeError
146
+ except (KeyError, RuntimeError):
147
+ try:
148
+ eres = copy.deepcopy(
149
+ results_dicts[next(iter(results_dicts))]["ecal"][
150
+ configs["cal_energy_param"]
151
+ ]["eres_linear"]
152
+ )
153
+
154
+ def eres_func(x):
155
+ return eval(eres["expression"], dict(x=x, **eres["parameters"]))
156
+
157
+ except KeyError:
158
+
159
+ def eres_func(x):
160
+ return x * np.nan
161
+
162
+ log.info("starting lq calibration")
163
+ start = time.time()
164
+ cal_dicts, out_dict, lq_plot_dict, lq_obj = lq_calibration(
165
+ data,
166
+ cal_dicts=cal_dicts,
167
+ energy_param=configs["energy_param"],
168
+ cal_energy_param=configs["cal_energy_param"],
169
+ dt_param=configs["dt_param"],
170
+ eres_func=eres_func,
171
+ cdf=eval(configs.get("cdf", "gaussian")),
172
+ selection_string=f"{configs.pop('cut_field')}&(~is_pulser)",
173
+ plot_options=configs.get("plot_options", None),
174
+ debug_mode=debug_mode | configs.get("debug_mode", False),
175
+ )
176
+ msg = f"lq calibration took {time.time() - start:.2f} seconds"
177
+ log.info(msg)
178
+ # need to change eres func as can't pickle lambdas
179
+ try:
180
+ lq_obj.eres_func = results_dicts[next(iter(results_dicts))][
181
+ "partition_ecal"
182
+ ][configs["cal_energy_param"]]["eres_linear"]
183
+ except KeyError:
184
+ lq_obj.eres_func = {}
44
185
  else:
45
- plot_dict = {}
46
- return plot_dict
186
+ out_dict = dict.fromkeys(cal_dicts)
187
+ lq_plot_dict = {}
188
+ lq_obj = None
189
+
190
+ out_result_dicts = {}
191
+ for tstamp, result_dict in results_dicts.items():
192
+ out_result_dicts[tstamp] = dict(**result_dict, lq=out_dict)
193
+
194
+ out_object_dicts = {}
195
+ for tstamp, object_dict in object_dicts.items():
196
+ out_object_dicts[tstamp] = dict(**object_dict, lq=lq_obj)
197
+
198
+ common_dict = lq_plot_dict.pop("common") if "common" in list(lq_plot_dict) else None
199
+ out_plot_dicts = {}
200
+ for tstamp, plot_dict in plot_dicts.items():
201
+ if "common" in list(plot_dict) and common_dict is not None:
202
+ plot_dict["common"].update(common_dict)
203
+ elif common_dict is not None:
204
+ plot_dict["common"] = common_dict
205
+ plot_dict.update({"lq": lq_plot_dict})
206
+ out_plot_dicts[tstamp] = plot_dict
207
+
208
+ return cal_dicts, out_result_dicts, out_object_dicts, out_plot_dicts
47
209
 
48
210
 
49
211
  def par_geds_hit_lq() -> None:
@@ -78,7 +240,7 @@ def par_geds_hit_lq() -> None:
78
240
  configs = TextDB(args.configs, lazy=True).on(args.timestamp, system=args.datatype)
79
241
  config_dict = configs["snakemake_rules"]["pars_hit_lqcal"]
80
242
 
81
- log = build_log(config_dict, args.log)
243
+ build_log(config_dict, args.log)
82
244
 
83
245
  channel_dict = config_dict["inputs"]["lqcal_config"][args.channel]
84
246
  kwarg_dict = Props.read_from(channel_dict)
@@ -87,33 +249,20 @@ def par_geds_hit_lq() -> None:
87
249
  cal_dict = ecal_dict["pars"]["operations"]
88
250
  eres_dict = ecal_dict["results"]["ecal"]
89
251
 
252
+ if args.inplots:
253
+ with Path(args.inplots).open("rb") as r:
254
+ plot_dict = pkl.load(r)
255
+ else:
256
+ plot_dict = {}
257
+
90
258
  with Path(args.eres_file).open("rb") as o:
91
259
  object_dict = pkl.load(o)
92
260
 
93
261
  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
262
  with Path(args.files[0]).open() as f:
103
263
  files = f.read().splitlines()
104
264
  files = sorted(files)
105
265
 
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
266
  params = [
118
267
  "lq80",
119
268
  "dt_eff",
@@ -132,73 +281,46 @@ def par_geds_hit_lq() -> None:
132
281
  return_selection_mask=True,
133
282
  )
134
283
 
284
+ msg = f"Loaded {len(data)} events"
285
+ log.info(msg)
286
+
135
287
  mask = get_pulser_mask(
136
288
  pulser_file=args.pulser_file,
137
289
  )
138
290
 
139
291
  data["is_pulser"] = mask[threshold_mask]
140
292
 
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
- )
293
+ msg = f"{len(data.query('~is_pulser'))} non pulser events"
294
+ log.info(msg)
150
295
 
151
- data["LQ_Ecorr"] = np.divide(data["lq80"], data[kwarg_dict["energy_param"]])
296
+ data["run_timestamp"] = args.timestamp
152
297
 
153
- lq.update_cal_dicts(
154
- {
155
- "LQ_Ecorr": {
156
- "expression": f"lq80/{kwarg_dict['energy_param']}",
157
- "parameters": {},
158
- }
159
- }
298
+ out_dicts, eres_dicts, plot_dicts, lq_dict = run_lq_calibration(
299
+ data,
300
+ cal_dicts={args.timestamp: cal_dict},
301
+ results_dicts={args.timestamp: eres_dict},
302
+ object_dicts={args.timestamp: object_dict},
303
+ plot_dicts={args.timestamp: plot_dict},
304
+ configs=kwarg_dict,
305
+ debug_mode=args.debug,
160
306
  )
307
+ cal_dict = out_dicts[args.timestamp]
308
+ eres_dict = eres_dicts[args.timestamp]
309
+ plot_dict = plot_dicts[args.timestamp]
310
+ lq = lq_dict[args.timestamp]
161
311
 
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
312
  else:
176
- out_dict = {}
177
- plot_dict = {}
178
313
  lq = None
179
314
 
180
315
  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
316
  Path(args.plot_file).parent.mkdir(parents=True, exist_ok=True)
195
317
  with Path(args.plot_file).open("wb") as w:
196
- pkl.dump(out_plot_dict, w, protocol=pkl.HIGHEST_PROTOCOL)
318
+ pkl.dump(plot_dict, w, protocol=pkl.HIGHEST_PROTOCOL)
197
319
 
198
320
  final_hit_dict = convert_dict_np_to_float(
199
321
  {
200
322
  "pars": {"operations": cal_dict},
201
- "results": dict(**ecal_dict["results"], lq=out_dict),
323
+ "results": dict(**ecal_dict["results"], lq=eres_dict),
202
324
  }
203
325
  )
204
326
  Path(args.hit_pars).parent.mkdir(parents=True, exist_ok=True)
@@ -2,8 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import argparse
4
4
  import json
5
+ import logging
5
6
  import pickle as pkl
6
7
  import re
8
+ import time
7
9
  import warnings
8
10
  from pathlib import Path
9
11
 
@@ -23,83 +25,23 @@ from ....utils import (
23
25
  get_pulser_mask,
24
26
  )
25
27
 
28
+ log = logging.getLogger(__name__)
26
29
  warnings.filterwarnings(action="ignore", category=RuntimeWarning)
27
30
 
28
31
 
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)
32
+ def build_qc(
33
+ config,
34
+ cal_files,
35
+ fft_files,
36
+ table_name,
37
+ overwrite=None,
38
+ pulser_file=None,
39
+ build_plots=False,
40
+ ):
41
+ search_name = table_name if table_name[-1] == "/" else table_name + "/"
71
42
 
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"]
43
+ kwarg_dict_fft = config["fft_fields"]
44
+ kwarg_dict_cal = config["cal_fields"]
103
45
 
104
46
  cut_fields = get_keys(
105
47
  [key.replace(search_name, "") for key in ls(cal_files[0], search_name)],
@@ -110,8 +52,8 @@ def par_geds_hit_qc() -> None:
110
52
  kwarg_dict_fft["cut_parameters"],
111
53
  )
112
54
 
113
- if "initial_cal_cuts" in kwarg_dict:
114
- init_cal = kwarg_dict["initial_cal_cuts"]
55
+ if "initial_cal_cuts" in config:
56
+ init_cal = config["initial_cal_cuts"]
115
57
  cut_fields += get_keys(
116
58
  [key.replace(search_name, "") for key in ls(cal_files[0], search_name)],
117
59
  init_cal["cut_parameters"],
@@ -120,11 +62,14 @@ def par_geds_hit_qc() -> None:
120
62
  if len(fft_files) > 0:
121
63
  fft_data = load_data(
122
64
  fft_files,
123
- args.table_name,
65
+ table_name,
124
66
  {},
125
67
  [*cut_fields, "t_sat_lo", "timestamp", "trapTmax"],
126
68
  )
127
69
 
70
+ msg = f"{len(fft_data)} events"
71
+ log.info(msg)
72
+
128
73
  discharges = fft_data["t_sat_lo"] > 0
129
74
  discharge_timestamps = np.where(fft_data["timestamp"][discharges])[0]
130
75
  is_recovering = np.full(len(fft_data), False, dtype=bool)
@@ -139,17 +84,22 @@ def par_geds_hit_qc() -> None:
139
84
  )
140
85
  fft_data["is_recovering"] = is_recovering
141
86
 
87
+ msg = f"{len(fft_data.query('is_recovering'))} discharge recovery events"
88
+ log.info(msg)
89
+
142
90
  hit_dict_fft = {}
143
91
  plot_dict_fft = {}
144
92
  cut_data = fft_data.query("is_recovering==0")
93
+
145
94
  msg = f"cut_data shape: {len(cut_data)}"
146
95
  log.debug(msg)
96
+
147
97
  for name, cut in kwarg_dict_fft["cut_parameters"].items():
148
98
  cut_dict, cut_plots = generate_cut_classifiers(
149
99
  cut_data,
150
100
  {name: cut},
151
- kwarg_dict.get("rounding", 4),
152
- display=1 if args.plot_path else 0,
101
+ config.get("rounding", 4),
102
+ display=build_plots,
153
103
  )
154
104
  hit_dict_fft.update(cut_dict)
155
105
  plot_dict_fft.update(cut_plots)
@@ -189,7 +139,7 @@ def par_geds_hit_qc() -> None:
189
139
  # load data in
190
140
  data, threshold_mask = load_data(
191
141
  cal_files,
192
- args.table_name,
142
+ table_name,
193
143
  {},
194
144
  [*cut_fields, "timestamp", "trapTmax", "t_sat_lo"],
195
145
  threshold=kwarg_dict_cal.get("threshold", 0),
@@ -198,11 +148,14 @@ def par_geds_hit_qc() -> None:
198
148
  )
199
149
 
200
150
  mask = get_pulser_mask(
201
- pulser_file=args.pulser_file,
151
+ pulser_file=pulser_file,
202
152
  )
203
153
 
204
154
  data["is_pulser"] = mask[threshold_mask]
205
155
 
156
+ msg = f"{len(data.query('~is_pulser'))} non pulser events"
157
+ log.info(msg)
158
+
206
159
  discharges = data["t_sat_lo"] > 0
207
160
  discharge_timestamps = np.where(data["timestamp"][discharges])[0]
208
161
  is_recovering = np.full(len(data), False, dtype=bool)
@@ -217,19 +170,22 @@ def par_geds_hit_qc() -> None:
217
170
  )
218
171
  data["is_recovering"] = is_recovering
219
172
 
173
+ msg = f"{len(data.query('is_recovering'))} discharge recovery events"
174
+ log.info(msg)
175
+
220
176
  rng = np.random.default_rng()
221
177
  mask = np.full(len(data.query("~is_pulser & ~is_recovering")), False, dtype=bool)
222
178
  mask[
223
179
  rng.choice(len(data.query("~is_pulser & ~is_recovering")), 4000, replace=False)
224
180
  ] = True
225
181
 
226
- if "initial_cal_cuts" in kwarg_dict:
227
- init_cal = kwarg_dict["initial_cal_cuts"]
182
+ if "initial_cal_cuts" in config:
183
+ init_cal = config["initial_cal_cuts"]
228
184
  hit_dict_init_cal, plot_dict_init_cal = generate_cut_classifiers(
229
185
  data.query("~is_pulser & ~is_recovering")[mask],
230
186
  init_cal["cut_parameters"],
231
187
  init_cal.get("rounding", 4),
232
- display=1 if args.plot_path else 0,
188
+ display=build_plots,
233
189
  )
234
190
  ct_mask = np.full(len(data), True, dtype=bool)
235
191
  for outname, info in hit_dict_init_cal.items():
@@ -260,8 +216,8 @@ def par_geds_hit_qc() -> None:
260
216
  hit_dict_cal, plot_dict_cal = generate_cut_classifiers(
261
217
  cal_data,
262
218
  kwarg_dict_cal["cut_parameters"],
263
- kwarg_dict.get("rounding", 4),
264
- display=1 if args.plot_path else 0,
219
+ config.get("rounding", 4),
220
+ display=build_plots,
265
221
  )
266
222
 
267
223
  if overwrite is not None:
@@ -273,8 +229,6 @@ def par_geds_hit_qc() -> None:
273
229
  hit_dict = {**hit_dict_fft, **hit_dict_init_cal, **hit_dict_cal}
274
230
  plot_dict = {**plot_dict_fft, **plot_dict_init_cal, **plot_dict_cal}
275
231
 
276
- hit_dict = convert_dict_np_to_float(hit_dict)
277
-
278
232
  for outname, info in hit_dict.items():
279
233
  # convert to pandas eval
280
234
  exp = info["expression"]
@@ -305,6 +259,7 @@ def par_geds_hit_qc() -> None:
305
259
  )
306
260
  sf_cal *= 100
307
261
  sf_fft *= 100
262
+
308
263
  msg = f"{entry} cut applied: {sf_cal:.2f}% of events passed the cut for cal data, {sf_fft:.2f}% for fft data"
309
264
  log.info(msg)
310
265
  qc_results[entry] = {
@@ -313,12 +268,93 @@ def par_geds_hit_qc() -> None:
313
268
  "sf_fft": sf_fft,
314
269
  "sf_fft_err": sf_fft_err,
315
270
  }
316
- qc_results = convert_dict_np_to_float(qc_results)
317
271
 
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}}
272
+ out_dict = convert_dict_np_to_float(
273
+ {"operations": hit_dict, "results": {"qc": qc_results}}
274
+ )
275
+
276
+ return out_dict, plot_dict
277
+
278
+
279
+ def par_geds_hit_qc() -> None:
280
+ argparser = argparse.ArgumentParser()
281
+ argparser.add_argument("--cal-files", help="cal_files", nargs="*", type=str)
282
+ argparser.add_argument("--fft-files", help="fft_files", nargs="*", type=str)
283
+
284
+ argparser.add_argument(
285
+ "--tcm-filelist", help="tcm_filelist", type=str, required=False
286
+ )
287
+ argparser.add_argument(
288
+ "--pulser-file", help="pulser_file", type=str, required=False
289
+ )
290
+ argparser.add_argument(
291
+ "--overwrite-files",
292
+ help="overwrite_files",
293
+ type=str,
294
+ required=False,
295
+ nargs="*",
296
+ )
297
+
298
+ argparser.add_argument("--configs", help="config", type=str, required=True)
299
+ argparser.add_argument("--log", help="log_file", type=str)
300
+
301
+ argparser.add_argument("--datatype", help="Datatype", type=str, required=True)
302
+ argparser.add_argument("--timestamp", help="Timestamp", type=str, required=True)
303
+ argparser.add_argument("--channel", help="Channel", type=str, required=True)
304
+ argparser.add_argument("--table-name", help="table name", type=str, required=True)
305
+ argparser.add_argument("--tier", help="tier", type=str, default="hit")
306
+
307
+ argparser.add_argument("--plot-path", help="plot_path", type=str, required=False)
308
+ argparser.add_argument("--save-path", help="save_path", type=str)
309
+ args = argparser.parse_args()
310
+
311
+ configs = TextDB(args.configs, lazy=True).on(args.timestamp, system=args.datatype)
312
+ config_dict = configs["snakemake_rules"]["pars_hit_qc"]
313
+
314
+ build_log(config_dict, args.log)
315
+
316
+ # get metadata dictionary
317
+ channel_dict = config_dict["inputs"]["qc_config"][args.channel]
318
+ kwarg_dict = Props.read_from(channel_dict)
319
+
320
+ if args.overwrite_files:
321
+ overwrite = Props.read_from(args.overwrite_files)
322
+ if args.channel in overwrite:
323
+ overwrite = overwrite[args.channel]["pars"]["operations"]
324
+ else:
325
+ overwrite = None
326
+ else:
327
+ overwrite = None
328
+
329
+ if len(args.fft_files) == 1 and Path(args.fft_files[0]).suffix == ".filelist":
330
+ with Path(args.fft_files[0]).open() as f:
331
+ fft_files = f.read().splitlines()
332
+ else:
333
+ fft_files = args.fft_files
334
+
335
+ if len(args.cal_files) == 1 and Path(args.cal_files[0]).suffix == ".filelist":
336
+ with Path(args.cal_files[0]).open() as f:
337
+ cal_files = f.read().splitlines()
338
+ else:
339
+ cal_files = args.fft_files
340
+
341
+ start = time.time()
342
+ log.info("starting qc")
343
+
344
+ out_dict, plot_dict = build_qc(
345
+ config=kwarg_dict,
346
+ cal_files=cal_files,
347
+ fft_files=fft_files,
348
+ table_name=args.table_name,
349
+ overwrite=overwrite,
350
+ pulser_file=args.pulser_file,
351
+ build_plots=int(bool(args.plot_path)),
321
352
  )
353
+ msg = f"qc took {time.time() - start:.2f} seconds"
354
+ log.info(msg)
355
+
356
+ Path(args.save_path).parent.mkdir(parents=True, exist_ok=True)
357
+ Props.write_to(args.save_path, out_dict)
322
358
 
323
359
  if args.plot_path:
324
360
  Path(args.plot_path).parent.mkdir(parents=True, exist_ok=True)
@@ -4,12 +4,14 @@ from .alias_table import alias_table
4
4
  from .cfgtools import get_channel_config
5
5
  from .convert_np import convert_dict_np_to_float
6
6
  from .log import build_log
7
+ from .plot_dict import fill_plot_dict
7
8
  from .pulser_removal import get_pulser_mask
8
9
 
9
10
  __all__ = [
10
11
  "alias_table",
11
12
  "build_log",
12
13
  "convert_dict_np_to_float",
14
+ "fill_plot_dict",
13
15
  "get_channel_config",
14
16
  "get_pulser_mask",
15
17
  ]
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ def fill_plot_dict(plot_class, data, plot_options, plot_dict=None):
5
+ if plot_dict is None:
6
+ plot_dict = {}
7
+ else:
8
+ for key, item in plot_options.items():
9
+ if item["options"] is not None:
10
+ plot_dict[key] = item["function"](plot_class, data, **item["options"])
11
+ else:
12
+ plot_dict[key] = item["function"](plot_class, data)
13
+ return plot_dict