lstosa 0.10.17__py3-none-any.whl → 0.10.19__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.
- {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/METADATA +4 -4
- {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/RECORD +41 -40
- {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/WHEEL +1 -1
- {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/entry_points.txt +1 -0
- osa/_version.py +9 -4
- osa/configs/options.py +2 -0
- osa/configs/sequencer.cfg +16 -6
- osa/conftest.py +127 -3
- osa/high_level/significance.py +3 -3
- osa/high_level/tests/test_significance.py +3 -0
- osa/job.py +48 -25
- osa/nightsummary/extract.py +11 -2
- osa/nightsummary/tests/test_extract.py +3 -0
- osa/paths.py +102 -23
- osa/provenance/capture.py +1 -1
- osa/provenance/config/definition.yaml +7 -0
- osa/provenance/utils.py +22 -7
- osa/scripts/autocloser.py +0 -10
- osa/scripts/calibration_pipeline.py +13 -8
- osa/scripts/closer.py +132 -53
- osa/scripts/copy_datacheck.py +5 -3
- osa/scripts/datasequence.py +45 -71
- osa/scripts/provprocess.py +16 -7
- osa/scripts/sequencer.py +34 -26
- osa/scripts/sequencer_catB_tailcuts.py +223 -0
- osa/scripts/sequencer_webmaker.py +4 -0
- osa/scripts/simulate_processing.py +4 -7
- osa/scripts/tests/test_osa_scripts.py +76 -27
- osa/scripts/update_source_catalog.py +5 -2
- osa/tests/test_jobs.py +29 -12
- osa/tests/test_paths.py +6 -6
- osa/utils/cliopts.py +37 -32
- osa/utils/register.py +18 -13
- osa/utils/tests/test_utils.py +14 -0
- osa/utils/utils.py +173 -56
- osa/workflow/dl3.py +1 -2
- osa/workflow/stages.py +16 -11
- osa/workflow/tests/test_dl3.py +2 -1
- osa/workflow/tests/test_stages.py +7 -4
- {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/LICENSE +0 -0
- {lstosa-0.10.17.dist-info → lstosa-0.10.19.dist-info}/top_level.txt +0 -0
osa/scripts/copy_datacheck.py
CHANGED
|
@@ -5,12 +5,13 @@ directories whenever they are needed.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
+
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
from osa.configs import options
|
|
11
|
+
from osa.configs.config import cfg
|
|
10
12
|
from osa.paths import (
|
|
11
13
|
datacheck_directory,
|
|
12
14
|
get_datacheck_files,
|
|
13
|
-
destination_dir,
|
|
14
15
|
)
|
|
15
16
|
from osa.utils.cliopts import copy_datacheck_parsing
|
|
16
17
|
from osa.utils.logging import myLogger
|
|
@@ -90,8 +91,9 @@ def get_number_of_runs():
|
|
|
90
91
|
Get the run sequence processed list for the given date by globbing the
|
|
91
92
|
run-wise DL1 files.
|
|
92
93
|
"""
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
nightdir = date_to_dir(options.date)
|
|
95
|
+
dl1_directory = Path(cfg.get("LST1", "DL1_DIR")) / nightdir / options.prod_id
|
|
96
|
+
list_files = list(dl1_directory.glob("tailcut*/dl1_LST-1.Run?????.h5"))
|
|
95
97
|
return len(list_files)
|
|
96
98
|
|
|
97
99
|
|
osa/scripts/datasequence.py
CHANGED
|
@@ -9,11 +9,13 @@ from osa.configs.config import cfg
|
|
|
9
9
|
from osa.job import historylevel
|
|
10
10
|
from osa.workflow.stages import AnalysisStage
|
|
11
11
|
from osa.provenance.capture import trace
|
|
12
|
+
from osa.paths import get_catB_calibration_filename
|
|
12
13
|
from osa.utils.cliopts import data_sequence_cli_parsing
|
|
13
14
|
from osa.utils.logging import myLogger
|
|
14
15
|
from osa.utils.utils import date_to_dir
|
|
16
|
+
from osa.paths import catB_closed_file_exists
|
|
15
17
|
|
|
16
|
-
__all__ = ["data_sequence", "r0_to_dl1", "
|
|
18
|
+
__all__ = ["data_sequence", "r0_to_dl1", "dl1ab", "dl1_datacheck"]
|
|
17
19
|
|
|
18
20
|
log = myLogger(logging.getLogger())
|
|
19
21
|
|
|
@@ -27,6 +29,8 @@ def data_sequence(
|
|
|
27
29
|
run_summary: Path,
|
|
28
30
|
pedestal_ids_file: Path,
|
|
29
31
|
run_str: str,
|
|
32
|
+
dl1b_config: Path,
|
|
33
|
+
dl1_prod_id: str,
|
|
30
34
|
):
|
|
31
35
|
"""
|
|
32
36
|
Performs all the steps to process a whole run.
|
|
@@ -50,10 +54,10 @@ def data_sequence(
|
|
|
50
54
|
history_file = Path(options.directory) / f"sequence_{options.tel_id}_{run_str}.history"
|
|
51
55
|
# Set the starting level and corresponding return code from last analysis step
|
|
52
56
|
# registered in the history file.
|
|
53
|
-
level, rc = (
|
|
57
|
+
level, rc = (3, 0) if options.simulate else historylevel(history_file, "DATA")
|
|
54
58
|
log.info(f"Going to level {level}")
|
|
55
59
|
|
|
56
|
-
if level ==
|
|
60
|
+
if level == 3:
|
|
57
61
|
rc = r0_to_dl1(
|
|
58
62
|
calibration_file,
|
|
59
63
|
pedestal_file,
|
|
@@ -67,32 +71,23 @@ def data_sequence(
|
|
|
67
71
|
level -= 1
|
|
68
72
|
log.info(f"Going to level {level}")
|
|
69
73
|
|
|
70
|
-
if level == 3:
|
|
71
|
-
rc = dl1ab(run_str)
|
|
72
|
-
if cfg.getboolean("lstchain", "store_image_dl1ab"):
|
|
73
|
-
level -= 1
|
|
74
|
-
log.info(f"Going to level {level}")
|
|
75
|
-
else:
|
|
76
|
-
level -= 2
|
|
77
|
-
log.info(f"No images stored in dl1ab. Producing DL2. Going to level {level}")
|
|
78
|
-
|
|
79
74
|
if level == 2:
|
|
80
|
-
|
|
81
|
-
if options.no_dl2:
|
|
75
|
+
if options.no_dl1ab:
|
|
82
76
|
level = 0
|
|
83
|
-
log.info(f"No
|
|
77
|
+
log.info(f"No DL1B are going to be produced. Going to level {level}")
|
|
84
78
|
else:
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
rc = dl1ab(run_str, dl1b_config, dl1_prod_id)
|
|
80
|
+
if cfg.getboolean("lstchain", "store_image_dl1ab"):
|
|
81
|
+
level -= 1
|
|
82
|
+
log.info(f"Going to level {level}")
|
|
83
|
+
else:
|
|
84
|
+
level -= 2
|
|
85
|
+
log.info(f"No images stored in dl1ab. Going to level {level}")
|
|
87
86
|
|
|
88
87
|
if level == 1:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
else:
|
|
93
|
-
rc = dl1_to_dl2(run_str)
|
|
94
|
-
level -= 1
|
|
95
|
-
log.info(f"Going to level {level}")
|
|
88
|
+
rc = dl1_datacheck(run_str, dl1_prod_id)
|
|
89
|
+
level -= 1
|
|
90
|
+
log.info(f"Going to level {level}")
|
|
96
91
|
|
|
97
92
|
if level == 0:
|
|
98
93
|
log.info(f"Job for sequence {run_str} finished without fatal errors")
|
|
@@ -166,7 +161,7 @@ def r0_to_dl1(
|
|
|
166
161
|
|
|
167
162
|
|
|
168
163
|
@trace
|
|
169
|
-
def dl1ab(run_str: str) -> int:
|
|
164
|
+
def dl1ab(run_str: str, dl1b_config: Path, dl1_prod_id: str) -> int:
|
|
170
165
|
"""
|
|
171
166
|
Prepare and launch the actual lstchain script that is performing
|
|
172
167
|
the image cleaning considering the interleaved pedestal information
|
|
@@ -181,26 +176,39 @@ def dl1ab(run_str: str) -> int:
|
|
|
181
176
|
rc: int
|
|
182
177
|
Return code of the executed command.
|
|
183
178
|
"""
|
|
179
|
+
|
|
180
|
+
# Prepare and launch the actual lstchain script
|
|
181
|
+
command = cfg.get("lstchain", "dl1ab")
|
|
182
|
+
|
|
184
183
|
# Create a new subdirectory for the dl1ab output
|
|
185
|
-
dl1ab_subdirectory = Path(options.directory) /
|
|
184
|
+
dl1ab_subdirectory = Path(options.directory) / dl1_prod_id
|
|
186
185
|
dl1ab_subdirectory.mkdir(parents=True, exist_ok=True)
|
|
187
|
-
dl1b_config = Path(cfg.get("lstchain", "dl1b_config"))
|
|
188
186
|
# DL1a input file from base running_analysis directory
|
|
189
187
|
input_dl1_datafile = Path(options.directory) / f"dl1_LST-1.Run{run_str}.h5"
|
|
190
188
|
# DL1b output file to be stored in the dl1ab subdirectory
|
|
191
189
|
output_dl1_datafile = dl1ab_subdirectory / f"dl1_LST-1.Run{run_str}.h5"
|
|
192
190
|
|
|
193
|
-
# Prepare and launch the actual lstchain script
|
|
194
|
-
command = cfg.get("lstchain", "dl1ab")
|
|
195
191
|
cmd = [
|
|
196
192
|
command,
|
|
197
193
|
f"--input-file={input_dl1_datafile}",
|
|
198
194
|
f"--output-file={output_dl1_datafile}",
|
|
199
195
|
f"--config={dl1b_config}",
|
|
200
196
|
]
|
|
197
|
+
|
|
201
198
|
if not cfg.getboolean("lstchain", "store_image_dl1ab"):
|
|
202
199
|
cmd.append("--no-image=True")
|
|
203
200
|
|
|
201
|
+
if cfg.getboolean("lstchain", "apply_catB_calibration"):
|
|
202
|
+
if catB_closed_file_exists(int(run_str[:5])):
|
|
203
|
+
catB_calibration_file = get_catB_calibration_filename(int(run_str[:5]))
|
|
204
|
+
cmd.append(f"--catB-calibration-file={catB_calibration_file}")
|
|
205
|
+
else:
|
|
206
|
+
log.info(
|
|
207
|
+
f"Cat-B calibration did not finish yet for run {run_str[:5]}. "
|
|
208
|
+
"Please try again later."
|
|
209
|
+
)
|
|
210
|
+
sys.exit(1)
|
|
211
|
+
|
|
204
212
|
if options.simulate:
|
|
205
213
|
return 0
|
|
206
214
|
|
|
@@ -210,7 +218,7 @@ def dl1ab(run_str: str) -> int:
|
|
|
210
218
|
|
|
211
219
|
|
|
212
220
|
@trace
|
|
213
|
-
def dl1_datacheck(run_str: str) -> int:
|
|
221
|
+
def dl1_datacheck(run_str: str, dl1_prod_id: str) -> int:
|
|
214
222
|
"""
|
|
215
223
|
Run datacheck script
|
|
216
224
|
|
|
@@ -223,9 +231,9 @@ def dl1_datacheck(run_str: str) -> int:
|
|
|
223
231
|
rc: int
|
|
224
232
|
"""
|
|
225
233
|
# Create a new subdirectory for the dl1ab output
|
|
226
|
-
dl1ab_subdirectory = Path(options.directory) /
|
|
234
|
+
dl1ab_subdirectory = Path(options.directory) / dl1_prod_id
|
|
227
235
|
input_dl1_datafile = dl1ab_subdirectory / f"dl1_LST-1.Run{run_str}.h5"
|
|
228
|
-
output_directory = Path(options.directory) /
|
|
236
|
+
output_directory = Path(options.directory) / dl1_prod_id
|
|
229
237
|
output_directory.mkdir(parents=True, exist_ok=True)
|
|
230
238
|
|
|
231
239
|
# Prepare and launch the actual lstchain script
|
|
@@ -247,46 +255,8 @@ def dl1_datacheck(run_str: str) -> int:
|
|
|
247
255
|
return analysis_step.rc
|
|
248
256
|
|
|
249
257
|
|
|
250
|
-
@trace
|
|
251
|
-
def dl1_to_dl2(run_str: str) -> int:
|
|
252
|
-
"""
|
|
253
|
-
It prepares and execute the dl1 to dl2 lstchain scripts that applies
|
|
254
|
-
the already trained RFs models to DL1 files. It identifies the
|
|
255
|
-
primary particle, reconstructs its energy and direction.
|
|
256
|
-
|
|
257
|
-
Parameters
|
|
258
|
-
----------
|
|
259
|
-
run_str: str
|
|
260
|
-
|
|
261
|
-
Returns
|
|
262
|
-
-------
|
|
263
|
-
rc: int
|
|
264
|
-
"""
|
|
265
|
-
dl1ab_subdirectory = Path(options.directory) / options.dl1_prod_id
|
|
266
|
-
dl2_subdirectory = Path(options.directory) / options.dl2_prod_id
|
|
267
|
-
dl2_config = Path(cfg.get("lstchain", "dl2_config"))
|
|
268
|
-
rf_models_directory = Path(cfg.get("lstchain", "RF_MODELS"))
|
|
269
|
-
dl1_file = dl1ab_subdirectory / f"dl1_LST-1.Run{run_str}.h5"
|
|
270
|
-
|
|
271
|
-
command = cfg.get("lstchain", "dl1_to_dl2")
|
|
272
|
-
cmd = [
|
|
273
|
-
command,
|
|
274
|
-
f"--input-file={dl1_file}",
|
|
275
|
-
f"--output-dir={dl2_subdirectory}",
|
|
276
|
-
f"--path-models={rf_models_directory}",
|
|
277
|
-
f"--config={dl2_config}",
|
|
278
|
-
]
|
|
279
|
-
|
|
280
|
-
if options.simulate:
|
|
281
|
-
return 0
|
|
282
|
-
|
|
283
|
-
analysis_step = AnalysisStage(run=run_str, command_args=cmd, config_file=dl2_config.name)
|
|
284
|
-
analysis_step.execute()
|
|
285
|
-
return analysis_step.rc
|
|
286
|
-
|
|
287
|
-
|
|
288
258
|
def main():
|
|
289
|
-
"""Performs the analysis steps to convert raw data into
|
|
259
|
+
"""Performs the analysis steps to convert raw data into DL1b files."""
|
|
290
260
|
(
|
|
291
261
|
calibration_file,
|
|
292
262
|
drs4_ped_file,
|
|
@@ -296,6 +266,8 @@ def main():
|
|
|
296
266
|
run_summary_file,
|
|
297
267
|
pedestal_ids_file,
|
|
298
268
|
run_number,
|
|
269
|
+
dl1b_config,
|
|
270
|
+
dl1_prod_id,
|
|
299
271
|
) = data_sequence_cli_parsing()
|
|
300
272
|
|
|
301
273
|
if options.verbose:
|
|
@@ -313,6 +285,8 @@ def main():
|
|
|
313
285
|
run_summary_file,
|
|
314
286
|
pedestal_ids_file,
|
|
315
287
|
run_number,
|
|
288
|
+
dl1b_config,
|
|
289
|
+
dl1_prod_id,
|
|
316
290
|
)
|
|
317
291
|
sys.exit(rc)
|
|
318
292
|
|
osa/scripts/provprocess.py
CHANGED
|
@@ -17,6 +17,8 @@ from osa.provenance.io import provdoc2graph, provdoc2json, provlist2provdoc, rea
|
|
|
17
17
|
from osa.provenance.utils import get_log_config
|
|
18
18
|
from osa.utils.cliopts import provprocessparsing
|
|
19
19
|
from osa.utils.logging import myLogger
|
|
20
|
+
from osa.utils.utils import date_to_dir
|
|
21
|
+
from osa.paths import get_dl1_prod_id_and_config, get_dl2_prod_id
|
|
20
22
|
|
|
21
23
|
__all__ = ["copy_used_file", "parse_lines_log", "parse_lines_run", "produce_provenance"]
|
|
22
24
|
|
|
@@ -110,7 +112,8 @@ def parse_lines_log(filter_cut, calib_runs, run_number):
|
|
|
110
112
|
keep = True
|
|
111
113
|
# make session starts with calibration
|
|
112
114
|
if session_id and filter_cut == "all" and not filtered:
|
|
113
|
-
|
|
115
|
+
nightdir = date_to_dir(options.date)
|
|
116
|
+
prov_dict["session_id"] = f"{nightdir}{run_number}"
|
|
114
117
|
prov_dict["name"] = run_number
|
|
115
118
|
prov_dict["observation_run"] = run_number
|
|
116
119
|
line = f"{ll[0]}{PROV_PREFIX}{ll[1]}{PROV_PREFIX}{prov_dict}\n"
|
|
@@ -336,7 +339,8 @@ def define_paths(grain, start_path, end_path, base_filename):
|
|
|
336
339
|
paths = {}
|
|
337
340
|
|
|
338
341
|
# check destination folder exists
|
|
339
|
-
|
|
342
|
+
nightdir = date_to_dir(options.date)
|
|
343
|
+
step_path = Path(start_path) / nightdir / options.prod_id / end_path
|
|
340
344
|
if not step_path.exists():
|
|
341
345
|
log.error(f"Path {step_path} does not exist")
|
|
342
346
|
|
|
@@ -381,8 +385,9 @@ def produce_provenance(session_log_filename, base_filename):
|
|
|
381
385
|
"""
|
|
382
386
|
|
|
383
387
|
if options.filter == "calibration" or not options.filter:
|
|
388
|
+
dl1_prod_id = get_dl1_prod_id_and_config(int(options.run))[0]
|
|
384
389
|
paths_calibration = define_paths(
|
|
385
|
-
"calibration_to_dl1", PATH_DL1,
|
|
390
|
+
"calibration_to_dl1", PATH_DL1, dl1_prod_id, base_filename
|
|
386
391
|
)
|
|
387
392
|
plines_drs4 = parse_lines_run(
|
|
388
393
|
"drs4_pedestal",
|
|
@@ -402,7 +407,8 @@ def produce_provenance(session_log_filename, base_filename):
|
|
|
402
407
|
pass
|
|
403
408
|
|
|
404
409
|
if options.filter == "r0_to_dl1" or not options.filter:
|
|
405
|
-
|
|
410
|
+
dl1_prod_id = get_dl1_prod_id_and_config(int(options.run))[0]
|
|
411
|
+
paths_r0_dl1 = define_paths("r0_to_dl1", PATH_DL1, dl1_prod_id, base_filename)
|
|
406
412
|
plines_r0 = parse_lines_run(
|
|
407
413
|
"r0_to_dl1",
|
|
408
414
|
read_prov(filename=session_log_filename),
|
|
@@ -425,8 +431,9 @@ def produce_provenance(session_log_filename, base_filename):
|
|
|
425
431
|
produce_provenance_files(plines_r0 + plines_ab[1:] + plines_check[1:], paths_r0_dl1)
|
|
426
432
|
|
|
427
433
|
if options.filter == "dl1_to_dl2" or not options.filter:
|
|
434
|
+
dl2_prod_id = get_dl2_prod_id(int(options.run))
|
|
428
435
|
if not options.no_dl2:
|
|
429
|
-
paths_dl1_dl2 = define_paths("dl1_to_dl2", PATH_DL2,
|
|
436
|
+
paths_dl1_dl2 = define_paths("dl1_to_dl2", PATH_DL2, dl2_prod_id, base_filename)
|
|
430
437
|
plines_dl2 = parse_lines_run(
|
|
431
438
|
"dl1_to_dl2",
|
|
432
439
|
read_prov(filename=session_log_filename),
|
|
@@ -441,16 +448,18 @@ def produce_provenance(session_log_filename, base_filename):
|
|
|
441
448
|
|
|
442
449
|
# create calibration_to_dl1 and calibration_to_dl2 prov files
|
|
443
450
|
if not options.filter:
|
|
451
|
+
dl1_prod_id = get_dl1_prod_id_and_config(int(options.run))[0]
|
|
444
452
|
calibration_to_dl1 = define_paths(
|
|
445
|
-
"calibration_to_dl1", PATH_DL1,
|
|
453
|
+
"calibration_to_dl1", PATH_DL1, dl1_prod_id, base_filename
|
|
446
454
|
)
|
|
447
455
|
calibration_to_dl1_lines = calibration_lines + dl1_lines[1:]
|
|
448
456
|
lines_dl1 = copy.deepcopy(calibration_to_dl1_lines)
|
|
449
457
|
produce_provenance_files(lines_dl1, calibration_to_dl1)
|
|
450
458
|
|
|
451
459
|
if not options.no_dl2:
|
|
460
|
+
dl2_prod_id = get_dl2_prod_id(int(options.run))
|
|
452
461
|
calibration_to_dl2 = define_paths(
|
|
453
|
-
"calibration_to_dl2", PATH_DL2,
|
|
462
|
+
"calibration_to_dl2", PATH_DL2, dl2_prod_id, base_filename
|
|
454
463
|
)
|
|
455
464
|
calibration_to_dl2_lines = calibration_to_dl1_lines + dl1_dl2_lines[1:]
|
|
456
465
|
lines_dl2 = copy.deepcopy(calibration_to_dl2_lines)
|
osa/scripts/sequencer.py
CHANGED
|
@@ -22,10 +22,15 @@ from osa.job import (
|
|
|
22
22
|
get_squeue_output,
|
|
23
23
|
run_sacct,
|
|
24
24
|
run_squeue,
|
|
25
|
+
are_all_jobs_correctly_finished,
|
|
26
|
+
)
|
|
27
|
+
from osa.nightsummary.extract import (
|
|
28
|
+
build_sequences,
|
|
29
|
+
extract_runs,
|
|
30
|
+
extract_sequences
|
|
25
31
|
)
|
|
26
|
-
from osa.nightsummary.extract import build_sequences
|
|
27
32
|
from osa.nightsummary.nightsummary import run_summary_table
|
|
28
|
-
from osa.paths import analysis_path
|
|
33
|
+
from osa.paths import analysis_path, destination_dir
|
|
29
34
|
from osa.report import start
|
|
30
35
|
from osa.utils.cliopts import sequencer_cli_parsing
|
|
31
36
|
from osa.utils.logging import myLogger
|
|
@@ -115,7 +120,7 @@ def single_process(telescope):
|
|
|
115
120
|
log.info(f"Sequencer is still running for date {date_to_iso(options.date)}. Try again later.")
|
|
116
121
|
sys.exit(0)
|
|
117
122
|
|
|
118
|
-
|
|
123
|
+
if is_sequencer_completed(options.date) and not options.force_submit:
|
|
119
124
|
log.info(f"Sequencer already finished for date {date_to_iso(options.date)}. Exiting")
|
|
120
125
|
sys.exit(0)
|
|
121
126
|
|
|
@@ -192,7 +197,7 @@ def update_sequence_status(seq_list):
|
|
|
192
197
|
Decimal(get_status_for_sequence(seq, "DATACHECK") * 100) / seq.subruns
|
|
193
198
|
)
|
|
194
199
|
seq.muonstatus = int(Decimal(get_status_for_sequence(seq, "MUON") * 100) / seq.subruns)
|
|
195
|
-
seq.dl2status = int(Decimal(get_status_for_sequence(seq, "DL2") * 100)
|
|
200
|
+
seq.dl2status = int(Decimal(get_status_for_sequence(seq, "DL2") * 100))
|
|
196
201
|
|
|
197
202
|
|
|
198
203
|
def get_status_for_sequence(sequence, data_level) -> int:
|
|
@@ -210,17 +215,26 @@ def get_status_for_sequence(sequence, data_level) -> int:
|
|
|
210
215
|
number_of_files : int
|
|
211
216
|
"""
|
|
212
217
|
if data_level == "DL1AB":
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
try:
|
|
219
|
+
directory = options.directory / sequence.dl1_prod_id
|
|
220
|
+
files = list(directory.glob(f"dl1_LST-1*{sequence.run}*.h5"))
|
|
221
|
+
except AttributeError:
|
|
222
|
+
return 0
|
|
223
|
+
|
|
216
224
|
elif data_level == "DL2":
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
225
|
+
try:
|
|
226
|
+
directory = destination_dir(concept="DL2", create_dir=False, dl2_prod_id=sequence.dl2_prod_id)
|
|
227
|
+
files = list(directory.glob(f"dl2_LST-1*{sequence.run}*.h5"))
|
|
228
|
+
except AttributeError:
|
|
229
|
+
return 0
|
|
230
|
+
|
|
220
231
|
elif data_level == "DATACHECK":
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
232
|
+
try:
|
|
233
|
+
directory = options.directory / sequence.dl1_prod_id
|
|
234
|
+
files = list(directory.glob(f"datacheck_dl1_LST-1*{sequence.run}*.h5"))
|
|
235
|
+
except AttributeError:
|
|
236
|
+
return 0
|
|
237
|
+
|
|
224
238
|
else:
|
|
225
239
|
prefix = cfg.get("PATTERN", f"{data_level}PREFIX")
|
|
226
240
|
suffix = cfg.get("PATTERN", f"{data_level}SUFFIX")
|
|
@@ -340,20 +354,14 @@ def is_sequencer_completed(date: datetime.datetime) -> bool:
|
|
|
340
354
|
"""Check if the jobs launched by sequencer are already completed."""
|
|
341
355
|
summary_table = run_summary_table(date)
|
|
342
356
|
data_runs = summary_table[summary_table["run_type"] == "DATA"]
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
for run in data_runs["run_id"]:
|
|
347
|
-
jobs_run = sacct_info[sacct_info["JobName"]==f"LST1_{run:05d}"]
|
|
348
|
-
if len(jobs_run["JobID"].unique())>1:
|
|
349
|
-
last_job_id = sorted(jobs_run["JobID"].unique())[-1]
|
|
350
|
-
jobs_run = sacct_info[sacct_info["JobID"]==last_job_id]
|
|
351
|
-
incomplete_jobs = jobs_run[(jobs_run["State"] != "COMPLETED")]
|
|
352
|
-
if len(jobs_run) == 0 or len(incomplete_jobs) != 0:
|
|
353
|
-
return False
|
|
354
|
-
|
|
355
|
-
return True
|
|
357
|
+
run_list = extract_runs(data_runs)
|
|
358
|
+
sequence_list = extract_sequences(options.date, run_list)
|
|
356
359
|
|
|
360
|
+
if are_all_jobs_correctly_finished(sequence_list):
|
|
361
|
+
return True
|
|
362
|
+
else:
|
|
363
|
+
log.info("Jobs did not correctly/yet finish")
|
|
364
|
+
return False
|
|
357
365
|
|
|
358
366
|
def timeout_in_sequencer(date: datetime.datetime) -> bool:
|
|
359
367
|
"""Check if any of the jobs launched by sequencer finished in timeout."""
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import re
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from astropy.table import Table
|
|
7
|
+
import subprocess as sp
|
|
8
|
+
|
|
9
|
+
from osa.configs import options
|
|
10
|
+
from osa.configs.config import cfg
|
|
11
|
+
from osa.nightsummary.extract import get_last_pedcalib
|
|
12
|
+
from osa.utils.cliopts import valid_date, set_default_date_if_needed
|
|
13
|
+
from osa.utils.logging import myLogger
|
|
14
|
+
from osa.job import run_sacct, get_sacct_output
|
|
15
|
+
from osa.utils.utils import date_to_dir, get_calib_filters
|
|
16
|
+
from osa.paths import catB_closed_file_exists, catB_calibration_file_exists, analysis_path
|
|
17
|
+
|
|
18
|
+
log = myLogger(logging.getLogger())
|
|
19
|
+
|
|
20
|
+
parser = argparse.ArgumentParser()
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"-c",
|
|
23
|
+
"--config",
|
|
24
|
+
action="store",
|
|
25
|
+
type=Path,
|
|
26
|
+
help="Configuration file",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"-d",
|
|
30
|
+
"--date",
|
|
31
|
+
type=valid_date,
|
|
32
|
+
default=None,
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"-v",
|
|
36
|
+
"--verbose",
|
|
37
|
+
action="store_true",
|
|
38
|
+
default=False,
|
|
39
|
+
help="Activate debugging mode.",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"-s",
|
|
43
|
+
"--simulate",
|
|
44
|
+
action="store_true",
|
|
45
|
+
default=False,
|
|
46
|
+
help="Simulate launching of the sequencer_catB_tailcuts script.",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"tel_id",
|
|
50
|
+
choices=["ST", "LST1", "LST2", "all"],
|
|
51
|
+
help="telescope identifier LST1, LST2, ST or all.",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def are_all_history_files_created(run_id: int) -> bool:
|
|
55
|
+
"""Check if all the history files (one per subrun) were created for a given run."""
|
|
56
|
+
run_summary_dir = Path(cfg.get(options.tel_id, "RUN_SUMMARY_DIR"))
|
|
57
|
+
run_summary_file = run_summary_dir / f"RunSummary_{date_to_dir(options.date)}.ecsv"
|
|
58
|
+
run_summary = Table.read(run_summary_file)
|
|
59
|
+
n_subruns = run_summary[run_summary["run_id"] == run_id]["n_subruns"]
|
|
60
|
+
analysis_dir = Path(options.directory)
|
|
61
|
+
history_files = glob.glob(f"{str(analysis_dir)}/sequence_LST1_{run_id:05d}.????.history")
|
|
62
|
+
if len(history_files) == n_subruns:
|
|
63
|
+
return True
|
|
64
|
+
else:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def r0_to_dl1_step_finished_for_run(run_id: int) -> bool:
|
|
69
|
+
"""
|
|
70
|
+
Check if the step r0_to_dl1 finished successfully
|
|
71
|
+
for a given run by looking the history files.
|
|
72
|
+
"""
|
|
73
|
+
if not are_all_history_files_created(run_id):
|
|
74
|
+
log.debug(f"All history files for run {run_id:05d} were not created yet.")
|
|
75
|
+
return False
|
|
76
|
+
analysis_dir = Path(options.directory)
|
|
77
|
+
history_files = glob.glob(f"{str(analysis_dir)}/sequence_LST1_{run_id:05d}.????.history")
|
|
78
|
+
for file in history_files:
|
|
79
|
+
rc = Path(file).read_text().splitlines()[-1][-1]
|
|
80
|
+
if rc != "0":
|
|
81
|
+
print(f"r0_to_dl1 step did not finish successfully (check file {file})")
|
|
82
|
+
return False
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_catB_last_job_id(run_id: int) -> int:
|
|
87
|
+
"""Get job id of the last Cat-B calibration job that was launched for a given run."""
|
|
88
|
+
log_dir = Path(options.directory) / "log"
|
|
89
|
+
filenames = glob.glob(f"{log_dir}/catB_calibration_{run_id:05d}_*.err")
|
|
90
|
+
if filenames:
|
|
91
|
+
match = re.search(f"catB_calibration_{run_id:05d}_(\d+).err", sorted(filenames)[-1])
|
|
92
|
+
job_id = match.group(1)
|
|
93
|
+
return job_id
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def launch_catB_calibration(run_id: int):
|
|
97
|
+
"""
|
|
98
|
+
Launch the Cat-B calibration script for a given run if the Cat-B calibration
|
|
99
|
+
file has not been created yet. If the Cat-B calibration script was launched
|
|
100
|
+
before and it finished successfully, it creates a catB_{run}.closed file.
|
|
101
|
+
"""
|
|
102
|
+
job_id = get_catB_last_job_id(run_id)
|
|
103
|
+
if job_id:
|
|
104
|
+
job_status = get_sacct_output(run_sacct(job_id=job_id))["State"]
|
|
105
|
+
if job_status.item() in ["RUNNING", "PENDING"]:
|
|
106
|
+
log.debug(f"Job {job_id} (corresponding to run {run_id:05d}) is still running.")
|
|
107
|
+
|
|
108
|
+
elif job_status.item() == "COMPLETED":
|
|
109
|
+
catB_closed_file = Path(options.directory) / f"catB_{run_id:05d}.closed"
|
|
110
|
+
catB_closed_file.touch()
|
|
111
|
+
log.debug(
|
|
112
|
+
f"Cat-B job {job_id} (corresponding to run {run_id:05d}) finished "
|
|
113
|
+
f"successfully. Creating file {catB_closed_file}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
else:
|
|
117
|
+
log.warning(f"Cat-B job {job_id} (corresponding to run {run_id:05d}) failed.")
|
|
118
|
+
|
|
119
|
+
else:
|
|
120
|
+
if catB_calibration_file_exists(run_id):
|
|
121
|
+
log.info(f"Cat-B calibration file already produced for run {run_id:05d}.")
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
command = cfg.get("lstchain", "catB_calibration")
|
|
125
|
+
options.filters = get_calib_filters(run_id)
|
|
126
|
+
base_dir = Path(cfg.get(options.tel_id, "BASE")).resolve()
|
|
127
|
+
r0_dir = Path(cfg.get(options.tel_id, "R0_DIR")).resolve()
|
|
128
|
+
interleaved_dir = Path(options.directory) / "interleaved"
|
|
129
|
+
log_dir = Path(options.directory) / "log"
|
|
130
|
+
catA_calib_run = get_last_pedcalib(options.date)
|
|
131
|
+
slurm_account = cfg.get("SLURM", "ACCOUNT")
|
|
132
|
+
cmd = ["sbatch", f"--account={slurm_account}", "--parsable",
|
|
133
|
+
"-o", f"{log_dir}/catB_calibration_{run_id:05d}_%j.out",
|
|
134
|
+
"-e", f"{log_dir}/catB_calibration_{run_id:05d}_%j.err",
|
|
135
|
+
command,
|
|
136
|
+
f"--run_number={run_id}",
|
|
137
|
+
f"--catA_calibration_run={catA_calib_run}",
|
|
138
|
+
f"--base_dir={base_dir}",
|
|
139
|
+
f"--r0-dir={r0_dir}",
|
|
140
|
+
f"--interleaved-dir={interleaved_dir}",
|
|
141
|
+
f"--filters={options.filters}",
|
|
142
|
+
]
|
|
143
|
+
if not options.simulate:
|
|
144
|
+
job = sp.run(cmd, encoding="utf-8", capture_output=True, text=True, check=True)
|
|
145
|
+
job_id = job.stdout.strip()
|
|
146
|
+
log.debug(f"Launched Cat-B calibration job {job_id} for run {run_id}!")
|
|
147
|
+
|
|
148
|
+
else:
|
|
149
|
+
log.info(f"Simulate launching of the {command} script.")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def launch_tailcuts_finder(run_id: int):
|
|
153
|
+
"""
|
|
154
|
+
Launch the lstchain script to calculate the correct
|
|
155
|
+
tailcuts to use for a given run.
|
|
156
|
+
"""
|
|
157
|
+
command = cfg.get("lstchain", "tailcuts_finder")
|
|
158
|
+
slurm_account = cfg.get("SLURM", "ACCOUNT")
|
|
159
|
+
input_dir = Path(options.directory)
|
|
160
|
+
output_dir = Path(cfg.get(options.tel_id, "TAILCUTS_FINDER_DIR"))
|
|
161
|
+
log_dir = Path(options.directory) / "log"
|
|
162
|
+
log_file = log_dir / f"tailcuts_finder_{run_id:05d}_%j.log"
|
|
163
|
+
cmd = [
|
|
164
|
+
"sbatch", "--parsable",
|
|
165
|
+
f"--account={slurm_account}",
|
|
166
|
+
"-o", log_file,
|
|
167
|
+
command,
|
|
168
|
+
f"--input-dir={input_dir}",
|
|
169
|
+
f"--run={run_id}",
|
|
170
|
+
f"--output-dir={output_dir}",
|
|
171
|
+
]
|
|
172
|
+
if not options.simulate:
|
|
173
|
+
job = sp.run(cmd, encoding="utf-8", capture_output=True, text=True, check=True)
|
|
174
|
+
job_id = job.stdout.strip()
|
|
175
|
+
log.debug(f"Launched lstchain_find_tailcuts job {job_id} for run {run_id}!")
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
log.info(f"Simulate launching of the {command} script.")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def tailcuts_config_file_exists(run_id: int) -> bool:
|
|
183
|
+
"""Check if the config file created by the tailcuts finder script already exists."""
|
|
184
|
+
tailcuts_config_file = Path(cfg.get(options.tel_id, "TAILCUTS_FINDER_DIR")) / f"dl1ab_Run{run_id:05d}.json"
|
|
185
|
+
return tailcuts_config_file.exists()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main():
|
|
189
|
+
"""
|
|
190
|
+
Main script to be called as cron job. It launches the Cat-B calibration script
|
|
191
|
+
and the tailcuts finder script for each run of the corresponding date, and creates
|
|
192
|
+
the catB_{run}.closed files if Cat-B calibration has finished successfully.
|
|
193
|
+
"""
|
|
194
|
+
opts = parser.parse_args()
|
|
195
|
+
options.tel_id = opts.tel_id
|
|
196
|
+
options.simulate = opts.simulate
|
|
197
|
+
options.date = opts.date
|
|
198
|
+
options.date = set_default_date_if_needed()
|
|
199
|
+
options.configfile = opts.config.resolve()
|
|
200
|
+
options.directory = analysis_path(options.tel_id)
|
|
201
|
+
|
|
202
|
+
if opts.verbose:
|
|
203
|
+
log.setLevel(logging.DEBUG)
|
|
204
|
+
else:
|
|
205
|
+
log.setLevel(logging.INFO)
|
|
206
|
+
|
|
207
|
+
run_summary_dir = Path(cfg.get(options.tel_id, "RUN_SUMMARY_DIR"))
|
|
208
|
+
run_summary = Table.read(run_summary_dir / f"RunSummary_{date_to_dir(options.date)}.ecsv")
|
|
209
|
+
data_runs = run_summary[run_summary["run_type"]=="DATA"]
|
|
210
|
+
for run_id in data_runs["run_id"]:
|
|
211
|
+
# first check if the dl1a files are produced
|
|
212
|
+
if not r0_to_dl1_step_finished_for_run(run_id):
|
|
213
|
+
log.info(f"The r0_to_dl1 step did not finish yet for run {run_id:05d}. Please try again later.")
|
|
214
|
+
else:
|
|
215
|
+
# launch catB calibration and tailcut finder in parallel
|
|
216
|
+
if cfg.getboolean("lstchain", "apply_catB_calibration") and not catB_closed_file_exists(run_id):
|
|
217
|
+
launch_catB_calibration(run_id)
|
|
218
|
+
if not cfg.getboolean("lstchain", "apply_standard_dl1b_config") and not tailcuts_config_file_exists(run_id):
|
|
219
|
+
launch_tailcuts_finder(run_id)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
if __name__ == "__main__":
|
|
223
|
+
main()
|
|
@@ -16,6 +16,7 @@ from osa.configs.config import cfg
|
|
|
16
16
|
from osa.utils.cliopts import sequencer_webmaker_argparser
|
|
17
17
|
from osa.utils.logging import myLogger
|
|
18
18
|
from osa.utils.utils import is_day_closed, date_to_iso, date_to_dir
|
|
19
|
+
from osa.paths import all_dl1ab_config_files_exist
|
|
19
20
|
|
|
20
21
|
log = myLogger(logging.getLogger())
|
|
21
22
|
|
|
@@ -88,6 +89,9 @@ def get_sequencer_output(date: str, config: str, test=False, no_gainsel=False) -
|
|
|
88
89
|
if test:
|
|
89
90
|
commandargs.insert(-1, "-t")
|
|
90
91
|
|
|
92
|
+
if not all_dl1ab_config_files_exist(date):
|
|
93
|
+
commandargs.insert(-1, "--no-dl1ab")
|
|
94
|
+
|
|
91
95
|
try:
|
|
92
96
|
output = sp.run(commandargs, stdout=sp.PIPE, stderr=sp.STDOUT, encoding="utf-8", check=True)
|
|
93
97
|
except sp.CalledProcessError as error:
|