lstosa 0.10.18__py3-none-any.whl → 0.11.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.
- {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/METADATA +4 -5
- lstosa-0.11.0.dist-info/RECORD +84 -0
- {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/WHEEL +1 -1
- {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/entry_points.txt +1 -0
- osa/_version.py +9 -4
- osa/configs/options.py +2 -0
- osa/configs/sequencer.cfg +21 -7
- osa/conftest.py +146 -6
- osa/high_level/significance.py +5 -3
- osa/high_level/tests/test_significance.py +3 -0
- osa/job.py +52 -26
- osa/nightsummary/extract.py +12 -3
- osa/nightsummary/tests/test_extract.py +5 -0
- osa/paths.py +111 -28
- 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 +9 -2
- osa/scripts/closer.py +136 -55
- osa/scripts/copy_datacheck.py +5 -3
- osa/scripts/datasequence.py +45 -71
- osa/scripts/gain_selection.py +14 -15
- osa/scripts/provprocess.py +16 -7
- osa/scripts/sequencer.py +49 -34
- osa/scripts/sequencer_catB_tailcuts.py +239 -0
- osa/scripts/sequencer_webmaker.py +4 -0
- osa/scripts/show_run_summary.py +2 -2
- osa/scripts/simulate_processing.py +4 -7
- osa/scripts/tests/test_osa_scripts.py +67 -22
- osa/scripts/update_source_catalog.py +45 -22
- osa/tests/test_jobs.py +28 -11
- osa/tests/test_paths.py +6 -6
- osa/tests/test_raw.py +4 -4
- osa/utils/cliopts.py +37 -32
- osa/utils/register.py +18 -13
- osa/utils/tests/test_utils.py +14 -0
- osa/utils/utils.py +186 -56
- osa/veto.py +1 -1
- 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 -5
- lstosa-0.10.18.dist-info/RECORD +0 -83
- {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/LICENSE +0 -0
- {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/top_level.txt +0 -0
osa/utils/cliopts.py
CHANGED
|
@@ -6,15 +6,11 @@ from argparse import ArgumentParser
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
from osa.configs import options
|
|
9
|
-
from osa.configs.config import cfg
|
|
10
9
|
from osa.paths import analysis_path, DEFAULT_CFG
|
|
11
10
|
from osa.utils.logging import myLogger
|
|
12
11
|
from osa.utils.utils import (
|
|
13
|
-
get_dl1_prod_id,
|
|
14
|
-
get_dl2_prod_id,
|
|
15
12
|
get_prod_id,
|
|
16
13
|
is_defined,
|
|
17
|
-
set_prod_ids,
|
|
18
14
|
YESTERDAY,
|
|
19
15
|
)
|
|
20
16
|
|
|
@@ -32,8 +28,6 @@ __all__ = [
|
|
|
32
28
|
"sequencer_webmaker_argparser",
|
|
33
29
|
"valid_date",
|
|
34
30
|
"get_prod_id",
|
|
35
|
-
"get_dl1_prod_id",
|
|
36
|
-
"get_dl2_prod_id",
|
|
37
31
|
"calibration_pipeline_cliparsing",
|
|
38
32
|
"calibration_pipeline_argparser",
|
|
39
33
|
"autocloser_cli_parser",
|
|
@@ -130,7 +124,7 @@ def closercliparsing():
|
|
|
130
124
|
# setting the default date and directory if needed
|
|
131
125
|
options.date = set_default_date_if_needed()
|
|
132
126
|
options.directory = analysis_path(options.tel_id)
|
|
133
|
-
|
|
127
|
+
options.prod_id = get_prod_id()
|
|
134
128
|
|
|
135
129
|
|
|
136
130
|
def calibration_pipeline_argparser():
|
|
@@ -184,10 +178,10 @@ def data_sequence_argparser():
|
|
|
184
178
|
help="Set the prod ID to define data directories",
|
|
185
179
|
)
|
|
186
180
|
parser.add_argument(
|
|
187
|
-
"--no-
|
|
181
|
+
"--no-dl1ab",
|
|
188
182
|
action="store_true",
|
|
189
183
|
default=False,
|
|
190
|
-
help="Do not
|
|
184
|
+
help="Do not launch the script lstchain_dl1ab (default False)",
|
|
191
185
|
)
|
|
192
186
|
parser.add_argument("--pedcal-file", type=Path, help="Path of the calibration file")
|
|
193
187
|
parser.add_argument("--drs4-pedestal-file", type=Path, help="Path of the DRS4 pedestal file")
|
|
@@ -210,6 +204,18 @@ def data_sequence_argparser():
|
|
|
210
204
|
type=Path,
|
|
211
205
|
help="Path to a file containing the ids of the interleaved pedestal events",
|
|
212
206
|
)
|
|
207
|
+
parser.add_argument(
|
|
208
|
+
"--dl1b-config",
|
|
209
|
+
type=Path,
|
|
210
|
+
default=None,
|
|
211
|
+
help="Configuration file for the production of DL1b files"
|
|
212
|
+
)
|
|
213
|
+
parser.add_argument(
|
|
214
|
+
"--dl1-prod-id",
|
|
215
|
+
type=str,
|
|
216
|
+
default=None,
|
|
217
|
+
help="Production id of the DL1b files"
|
|
218
|
+
)
|
|
213
219
|
parser.add_argument("run_number", help="Number of the run to be processed")
|
|
214
220
|
parser.add_argument("tel_id", choices=["ST", "LST1", "LST2"])
|
|
215
221
|
return parser
|
|
@@ -226,7 +232,7 @@ def data_sequence_cli_parsing():
|
|
|
226
232
|
options.verbose = opts.verbose
|
|
227
233
|
options.simulate = opts.simulate
|
|
228
234
|
options.prod_id = opts.prod_id
|
|
229
|
-
options.
|
|
235
|
+
options.no_dl1ab = opts.no_dl1ab
|
|
230
236
|
options.tel_id = opts.tel_id
|
|
231
237
|
|
|
232
238
|
log.debug(f"The options and arguments are {opts}")
|
|
@@ -234,8 +240,7 @@ def data_sequence_cli_parsing():
|
|
|
234
240
|
# setting the default date and directory if needed
|
|
235
241
|
options.date = set_default_date_if_needed()
|
|
236
242
|
options.directory = analysis_path(options.tel_id)
|
|
237
|
-
|
|
238
|
-
set_prod_ids()
|
|
243
|
+
options.prod_id = get_prod_id()
|
|
239
244
|
|
|
240
245
|
return (
|
|
241
246
|
opts.pedcal_file,
|
|
@@ -246,6 +251,8 @@ def data_sequence_cli_parsing():
|
|
|
246
251
|
opts.run_summary,
|
|
247
252
|
opts.pedestal_ids_file,
|
|
248
253
|
opts.run_number,
|
|
254
|
+
opts.dl1b_config,
|
|
255
|
+
opts.dl1_prod_id,
|
|
249
256
|
)
|
|
250
257
|
|
|
251
258
|
|
|
@@ -269,10 +276,10 @@ def sequencer_argparser():
|
|
|
269
276
|
"calibration products already produced (default False)",
|
|
270
277
|
)
|
|
271
278
|
parser.add_argument(
|
|
272
|
-
"--no-
|
|
279
|
+
"--no-dl1ab",
|
|
273
280
|
action="store_true",
|
|
274
281
|
default=False,
|
|
275
|
-
help="Do not
|
|
282
|
+
help="Do not launch the script lstchain_dl1ab (default False)",
|
|
276
283
|
)
|
|
277
284
|
parser.add_argument(
|
|
278
285
|
"--no-gainsel",
|
|
@@ -304,13 +311,13 @@ def sequencer_cli_parsing():
|
|
|
304
311
|
set_common_globals(opts)
|
|
305
312
|
options.no_submit = opts.no_submit
|
|
306
313
|
options.no_calib = opts.no_calib
|
|
307
|
-
options.
|
|
314
|
+
options.no_dl1ab = opts.no_dl1ab
|
|
308
315
|
options.no_gainsel = opts.no_gainsel
|
|
309
316
|
options.force_submit = opts.force_submit
|
|
310
317
|
|
|
311
318
|
log.debug(f"the options are {opts}")
|
|
312
319
|
|
|
313
|
-
|
|
320
|
+
options.prod_id = get_prod_id()
|
|
314
321
|
|
|
315
322
|
# setting the default date and directory if needed
|
|
316
323
|
options.date = set_default_date_if_needed()
|
|
@@ -352,7 +359,7 @@ def provprocess_argparser():
|
|
|
352
359
|
)
|
|
353
360
|
parser.add_argument("pedcal_run_id", help="Number of the used pedcal used in the calibration")
|
|
354
361
|
parser.add_argument("run", help="Number of the run whose provenance is to be extracted")
|
|
355
|
-
parser.add_argument("date", action="store", type=
|
|
362
|
+
parser.add_argument("date", action="store", type=valid_date, help="Date (YYYY-MM-DD) of the start of the night")
|
|
356
363
|
parser.add_argument("prod_id", action="store", type=str, help="Production ID")
|
|
357
364
|
|
|
358
365
|
return parser
|
|
@@ -376,7 +383,8 @@ def provprocessparsing():
|
|
|
376
383
|
options.filter = opts.filter
|
|
377
384
|
options.quit = opts.quit
|
|
378
385
|
options.no_dl2 = opts.no_dl2
|
|
379
|
-
|
|
386
|
+
options.prod_id = get_prod_id()
|
|
387
|
+
options.tel_id = "LST1"
|
|
380
388
|
|
|
381
389
|
|
|
382
390
|
def simproc_argparser():
|
|
@@ -402,15 +410,15 @@ def simproc_argparser():
|
|
|
402
410
|
dest="append",
|
|
403
411
|
help="append provenance capture to existing prov.log file",
|
|
404
412
|
)
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
413
|
+
parser.add_argument(
|
|
414
|
+
"-d",
|
|
415
|
+
"--date",
|
|
416
|
+
action="store",
|
|
417
|
+
type=valid_date,
|
|
418
|
+
dest="date",
|
|
419
|
+
help="observation ending date YYYY-MM-DD [default today]",
|
|
420
|
+
)
|
|
421
|
+
parser.add_argument("tel_id", choices=["ST", "LST1", "LST2"])
|
|
414
422
|
|
|
415
423
|
return parser
|
|
416
424
|
|
|
@@ -424,6 +432,8 @@ def simprocparsing():
|
|
|
424
432
|
options.provenance = opts.provenance
|
|
425
433
|
options.force = opts.force
|
|
426
434
|
options.append = opts.append
|
|
435
|
+
options.date = opts.date
|
|
436
|
+
options.tel_id = opts.tel_id
|
|
427
437
|
|
|
428
438
|
|
|
429
439
|
def copy_datacheck_argparser():
|
|
@@ -445,11 +455,6 @@ def copy_datacheck_parsing():
|
|
|
445
455
|
options.directory = analysis_path(options.tel_id)
|
|
446
456
|
options.prod_id = get_prod_id()
|
|
447
457
|
|
|
448
|
-
if cfg.get("LST1", "DL1_PROD_ID") is not None:
|
|
449
|
-
options.dl1_prod_id = get_dl1_prod_id()
|
|
450
|
-
else:
|
|
451
|
-
options.dl1_prod_id = options.prod_id
|
|
452
|
-
|
|
453
458
|
|
|
454
459
|
def sequencer_webmaker_argparser():
|
|
455
460
|
parser = ArgumentParser(
|
osa/utils/register.py
CHANGED
|
@@ -7,7 +7,11 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
from osa.configs import options
|
|
9
9
|
from osa.configs.config import cfg
|
|
10
|
-
from osa.paths import
|
|
10
|
+
from osa.paths import (
|
|
11
|
+
destination_dir,
|
|
12
|
+
get_dl1_prod_id_and_config,
|
|
13
|
+
get_dl2_prod_id
|
|
14
|
+
)
|
|
11
15
|
from osa.utils.logging import myLogger
|
|
12
16
|
from osa.veto import set_closed_sequence
|
|
13
17
|
|
|
@@ -70,11 +74,9 @@ def create_symlinks(input_file, output_file, prefix, suffix):
|
|
|
70
74
|
"""
|
|
71
75
|
|
|
72
76
|
analysis_dir = Path(options.directory)
|
|
73
|
-
dl1ab_dir = analysis_dir / options.dl1_prod_id
|
|
74
77
|
|
|
75
78
|
if prefix == "dl1_LST-1" and suffix == ".h5":
|
|
76
79
|
dl1_filepath_analysis_dir = analysis_dir / input_file.name
|
|
77
|
-
dl1_filepath_dl1_dir = dl1ab_dir / input_file.name
|
|
78
80
|
# Remove the original DL1 files pre DL1ab stage and keep only symlinks
|
|
79
81
|
if dl1_filepath_analysis_dir.is_file() and not dl1_filepath_analysis_dir.is_symlink():
|
|
80
82
|
dl1_filepath_analysis_dir.unlink()
|
|
@@ -83,8 +85,8 @@ def create_symlinks(input_file, output_file, prefix, suffix):
|
|
|
83
85
|
dl1_filepath_analysis_dir.symlink_to(output_file.resolve())
|
|
84
86
|
|
|
85
87
|
# Also set the symlink in the DL1ab subdirectory
|
|
86
|
-
if not
|
|
87
|
-
|
|
88
|
+
if not input_file.is_symlink():
|
|
89
|
+
input_file.symlink_to(output_file.resolve())
|
|
88
90
|
|
|
89
91
|
if prefix == "muons_LST-1" and suffix == ".fits":
|
|
90
92
|
input_file.symlink_to(output_file.resolve())
|
|
@@ -93,7 +95,7 @@ def create_symlinks(input_file, output_file, prefix, suffix):
|
|
|
93
95
|
input_file.symlink_to(output_file.resolve())
|
|
94
96
|
|
|
95
97
|
|
|
96
|
-
def register_run_concept_files(run_string, concept):
|
|
98
|
+
def register_run_concept_files(run_string, sequence_type, concept):
|
|
97
99
|
"""
|
|
98
100
|
Prepare files to be moved to final destination directories
|
|
99
101
|
from the running_analysis original directory.
|
|
@@ -107,17 +109,20 @@ def register_run_concept_files(run_string, concept):
|
|
|
107
109
|
initial_dir = Path(options.directory) # running_analysis
|
|
108
110
|
|
|
109
111
|
# For MUON and INTERLEAVED data products, the initial directory is running_analysis
|
|
112
|
+
if sequence_type=="DATA":
|
|
113
|
+
dl1_prod_id = get_dl1_prod_id_and_config(int(run_string))[0]
|
|
114
|
+
dl2_prod_id = get_dl2_prod_id(int(run_string))
|
|
110
115
|
|
|
111
116
|
if concept == "DL2":
|
|
112
|
-
initial_dir = initial_dir /
|
|
117
|
+
initial_dir = initial_dir / dl2_prod_id
|
|
113
118
|
|
|
114
119
|
elif concept == "DL1AB":
|
|
115
|
-
initial_dir = initial_dir /
|
|
120
|
+
initial_dir = initial_dir / dl1_prod_id
|
|
116
121
|
|
|
117
122
|
elif concept == "DATACHECK":
|
|
118
|
-
initial_dir = initial_dir /
|
|
123
|
+
initial_dir = initial_dir / dl1_prod_id
|
|
119
124
|
|
|
120
|
-
output_dir = destination_dir(concept, create_dir=False)
|
|
125
|
+
output_dir = destination_dir(concept, create_dir=False, dl1_prod_id=dl1_prod_id, dl2_prod_id=dl2_prod_id)
|
|
121
126
|
data_level = cfg.get("PATTERN", f"{concept}TYPE")
|
|
122
127
|
prefix = cfg.get("PATTERN", f"{concept}PREFIX")
|
|
123
128
|
suffix = cfg.get("PATTERN", f"{concept}SUFFIX")
|
|
@@ -167,7 +172,7 @@ def register_non_existing_file(file_path, concept, seq_list):
|
|
|
167
172
|
|
|
168
173
|
if run_str_found is not None:
|
|
169
174
|
log.debug(f"Registering file {run_str_found}")
|
|
170
|
-
register_run_concept_files(sequence.run_str, concept)
|
|
175
|
+
register_run_concept_files(sequence.run_str, sequence.type, concept)
|
|
171
176
|
if options.seqtoclose is None and not file_path.exists():
|
|
172
177
|
log.debug("File does not exists")
|
|
173
178
|
|
|
@@ -177,13 +182,13 @@ def register_non_existing_file(file_path, concept, seq_list):
|
|
|
177
182
|
|
|
178
183
|
if calib_run_str_found is not None:
|
|
179
184
|
log.debug(f"Registering file {calib_run_str_found}")
|
|
180
|
-
register_run_concept_files(str(sequence.run), concept)
|
|
185
|
+
register_run_concept_files(str(sequence.run), sequence.type, concept)
|
|
181
186
|
if options.seqtoclose is None and not file_path.exists():
|
|
182
187
|
log.debug("File does not exists")
|
|
183
188
|
|
|
184
189
|
if drs4_run_str_found is not None:
|
|
185
190
|
log.debug(f"Registering file {drs4_run_str_found}")
|
|
186
|
-
register_run_concept_files(str(sequence.previousrun), concept)
|
|
191
|
+
register_run_concept_files(str(sequence.previousrun), sequence.type, concept)
|
|
187
192
|
if options.seqtoclose is None and not file_path.exists():
|
|
188
193
|
log.debug("File does not exists")
|
|
189
194
|
|
osa/utils/tests/test_utils.py
CHANGED
|
@@ -75,3 +75,17 @@ def test_create_lock(base_test_dir):
|
|
|
75
75
|
lock_path = base_test_dir / "test_lock.closed"
|
|
76
76
|
is_closed = create_lock(lock_path)
|
|
77
77
|
assert is_closed is False
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_get_RF_model(
|
|
81
|
+
run_catalog_dir,
|
|
82
|
+
run_catalog,
|
|
83
|
+
rf_models,
|
|
84
|
+
dl1b_config_files,
|
|
85
|
+
tailcuts_log_files,
|
|
86
|
+
):
|
|
87
|
+
from osa.utils.utils import get_RF_model
|
|
88
|
+
from pathlib import Path
|
|
89
|
+
|
|
90
|
+
expected_model = Path("test_osa/test_files0/models/AllSky/20240918_v0.10.12_allsky_nsb_tuning_0.14/dec_2276")
|
|
91
|
+
assert get_RF_model(1807) == expected_model.resolve()
|
osa/utils/utils.py
CHANGED
|
@@ -4,10 +4,18 @@
|
|
|
4
4
|
import inspect
|
|
5
5
|
import logging
|
|
6
6
|
import os
|
|
7
|
+
import re
|
|
8
|
+
import json
|
|
7
9
|
import time
|
|
10
|
+
import numpy as np
|
|
8
11
|
from datetime import datetime, timedelta
|
|
9
12
|
from pathlib import Path
|
|
10
13
|
from socket import gethostname
|
|
14
|
+
import subprocess as sp
|
|
15
|
+
from gammapy.data import observatory_locations
|
|
16
|
+
from astropy import units as u
|
|
17
|
+
from astropy.table import Table
|
|
18
|
+
from lstchain.onsite import find_filter_wheels
|
|
11
19
|
|
|
12
20
|
import osa.paths
|
|
13
21
|
from osa.configs import options
|
|
@@ -15,6 +23,7 @@ from osa.configs.config import cfg
|
|
|
15
23
|
from osa.utils.iofile import write_to_file
|
|
16
24
|
from osa.utils.logging import myLogger
|
|
17
25
|
|
|
26
|
+
|
|
18
27
|
__all__ = [
|
|
19
28
|
"get_lstchain_version",
|
|
20
29
|
"date_to_dir",
|
|
@@ -26,12 +35,9 @@ __all__ = [
|
|
|
26
35
|
"create_lock",
|
|
27
36
|
"stringify",
|
|
28
37
|
"gettag",
|
|
29
|
-
"get_dl1_prod_id",
|
|
30
|
-
"get_dl2_prod_id",
|
|
31
38
|
"time_to_seconds",
|
|
32
39
|
"DATACHECK_FILE_PATTERNS",
|
|
33
40
|
"YESTERDAY",
|
|
34
|
-
"set_prod_ids",
|
|
35
41
|
"is_night_time",
|
|
36
42
|
"cron_lock",
|
|
37
43
|
"example_seq",
|
|
@@ -85,44 +91,6 @@ def get_prod_id():
|
|
|
85
91
|
return options.prod_id
|
|
86
92
|
|
|
87
93
|
|
|
88
|
-
def get_dl1_prod_id():
|
|
89
|
-
"""
|
|
90
|
-
Get the prod ID for the dl1 products provided
|
|
91
|
-
it is defined in the configuration file.
|
|
92
|
-
|
|
93
|
-
Returns
|
|
94
|
-
-------
|
|
95
|
-
dl1_prod_id: string
|
|
96
|
-
"""
|
|
97
|
-
if not options.dl1_prod_id:
|
|
98
|
-
if cfg.get("LST1", "DL1_PROD_ID") is not None:
|
|
99
|
-
options.dl1_prod_id = cfg.get("LST1", "DL1_PROD_ID")
|
|
100
|
-
else:
|
|
101
|
-
options.dl1_prod_id = get_lstchain_version()
|
|
102
|
-
|
|
103
|
-
log.debug(f"Getting prod ID for DL1 products: {options.dl1_prod_id}")
|
|
104
|
-
|
|
105
|
-
return options.dl1_prod_id
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def get_dl2_prod_id():
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
Returns
|
|
112
|
-
-------
|
|
113
|
-
|
|
114
|
-
"""
|
|
115
|
-
if not options.dl2_prod_id:
|
|
116
|
-
if cfg.get("LST1", "DL2_PROD_ID") is not None:
|
|
117
|
-
options.dl2_prod_id = cfg.get("LST1", "DL2_PROD_ID")
|
|
118
|
-
else:
|
|
119
|
-
options.dl2_prod_id = get_lstchain_version()
|
|
120
|
-
|
|
121
|
-
log.debug(f"Getting prod ID for DL2 products: {options.dl2_prod_id}")
|
|
122
|
-
|
|
123
|
-
return options.dl2_prod_id
|
|
124
|
-
|
|
125
|
-
|
|
126
94
|
def create_lock(lockfile) -> bool:
|
|
127
95
|
"""
|
|
128
96
|
Create a lock file to prevent multiple instances of the same analysis.
|
|
@@ -244,21 +212,6 @@ def time_to_seconds(timestring):
|
|
|
244
212
|
return int(hours) * 3600 + int(minutes) * 60 + int(seconds)
|
|
245
213
|
|
|
246
214
|
|
|
247
|
-
def set_prod_ids():
|
|
248
|
-
"""Set the product IDs."""
|
|
249
|
-
options.prod_id = get_prod_id()
|
|
250
|
-
|
|
251
|
-
if cfg.get("LST1", "DL1_PROD_ID") is not None:
|
|
252
|
-
options.dl1_prod_id = get_dl1_prod_id()
|
|
253
|
-
else:
|
|
254
|
-
options.dl1_prod_id = options.prod_id
|
|
255
|
-
|
|
256
|
-
if cfg.get("LST1", "DL2_PROD_ID") is not None:
|
|
257
|
-
options.dl2_prod_id = get_dl2_prod_id()
|
|
258
|
-
else:
|
|
259
|
-
options.dl2_prod_id = options.prod_id
|
|
260
|
-
|
|
261
|
-
|
|
262
215
|
def is_night_time(hour):
|
|
263
216
|
"""Check if it is nighttime."""
|
|
264
217
|
if 8 <= hour <= 18:
|
|
@@ -285,3 +238,180 @@ def wait_for_daytime(start=8, end=18):
|
|
|
285
238
|
while time.localtime().tm_hour <= start or time.localtime().tm_hour >= end:
|
|
286
239
|
log.info("Waiting for sunrise to not interfere with the data-taking. Sleeping.")
|
|
287
240
|
time.sleep(3600)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def get_calib_filters(run_id):
|
|
244
|
+
"""Get the filters used for the calibration."""
|
|
245
|
+
if options.test: # Run tests avoiding the access to the database
|
|
246
|
+
return 52
|
|
247
|
+
|
|
248
|
+
else:
|
|
249
|
+
mongodb = cfg.get("database", "caco_db")
|
|
250
|
+
try:
|
|
251
|
+
# Cast run_id to int to avoid problems with numpy int64 encoding in MongoDB
|
|
252
|
+
return find_filter_wheels(int(run_id), mongodb)
|
|
253
|
+
except IOError:
|
|
254
|
+
log.warning("No filter information found in database. Assuming positions 52.")
|
|
255
|
+
return 52
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def culmination_angle(dec: u.Quantity) -> u.Quantity:
|
|
259
|
+
"""
|
|
260
|
+
Calculate culmination angle for a given declination.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
dec: Quantity
|
|
265
|
+
declination coordinate in degrees
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
Culmination angle in degrees
|
|
270
|
+
"""
|
|
271
|
+
location = observatory_locations["cta_north"]
|
|
272
|
+
Lat = location.lat # latitude of the LST1 site
|
|
273
|
+
return abs(Lat - dec)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def convert_dec_string(dec_str: str) -> u.Quantity:
|
|
277
|
+
"""Return the declination angle in degrees corresponding to a
|
|
278
|
+
given string of the form "dec_XXXX" or "dec_min_XXXX"."""
|
|
279
|
+
|
|
280
|
+
# Check if dec_str has a valid format
|
|
281
|
+
pattern = r'^dec_(\d{3,4})$|^dec_min_(\d{3,4})$'
|
|
282
|
+
if re.match(pattern, dec_str):
|
|
283
|
+
|
|
284
|
+
# Split the string into parts
|
|
285
|
+
parts = dec_str.split('_')
|
|
286
|
+
|
|
287
|
+
# Extract the sign, degrees, and minutes
|
|
288
|
+
sign = 1 if 'min' not in parts else -1
|
|
289
|
+
degrees = int(parts[-1])
|
|
290
|
+
|
|
291
|
+
# Calculate the numerical value
|
|
292
|
+
dec_value = sign * (degrees / 100)
|
|
293
|
+
|
|
294
|
+
return dec_value*u.deg
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def get_declinations_dict(list1: list, list2: list) -> dict:
|
|
298
|
+
"""Return a dictionary created from two given lists."""
|
|
299
|
+
corresponding_dict = {}
|
|
300
|
+
for index, element in enumerate(list2):
|
|
301
|
+
corresponding_dict[element] = list1[index]
|
|
302
|
+
return corresponding_dict
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def get_nsb_dict(rf_models_dir: Path, rf_models_prefix: str) -> dict:
|
|
306
|
+
"""Return a dictionary with the NSB level of the RF models and the path to each model."""
|
|
307
|
+
rf_models = sorted(rf_models_dir.glob(f"{rf_models_prefix}*"))
|
|
308
|
+
pattern = r"nsb_tuning_([\d.]+)"
|
|
309
|
+
nsb_dict = {
|
|
310
|
+
float(re.search(pattern, str(rf_model)).group(1)): rf_model
|
|
311
|
+
for rf_model in rf_models if re.search(pattern, str(rf_model))
|
|
312
|
+
}
|
|
313
|
+
return nsb_dict
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_mc_nsb_dir(run_id: int, rf_models_dir: Path) -> Path:
|
|
317
|
+
"""
|
|
318
|
+
Return the path of the RF models directory with the NSB level
|
|
319
|
+
closest to that of the data for a given run.
|
|
320
|
+
"""
|
|
321
|
+
additional_nsb = get_nsb_level(run_id)
|
|
322
|
+
rf_models_prefix = cfg.get("lstchain", "mc_prod")
|
|
323
|
+
nsb_dict = get_nsb_dict(rf_models_dir, rf_models_prefix)
|
|
324
|
+
closest_nsb_value = min(nsb_dict.keys(), key=lambda x: abs(float(x) - additional_nsb))
|
|
325
|
+
|
|
326
|
+
return nsb_dict[closest_nsb_value]
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def get_nsb_level(run_id):
|
|
330
|
+
"""Choose the closest NSB among those that are processed with the same cleaning level."""
|
|
331
|
+
tailcuts_finder_dir = Path(cfg.get(options.tel_id, "TAILCUTS_FINDER_DIR"))
|
|
332
|
+
log_file = tailcuts_finder_dir / f"log_find_tailcuts_Run{run_id:05d}.log"
|
|
333
|
+
with open(log_file, "r") as file:
|
|
334
|
+
log_content = file.read()
|
|
335
|
+
match = re.search(r"Additional NSB rate \(over dark MC\): ([\d.]+)", log_content)
|
|
336
|
+
nsb = float(match.group(1))
|
|
337
|
+
|
|
338
|
+
dl1b_config_filename = tailcuts_finder_dir / f"dl1ab_Run{run_id:05d}.json"
|
|
339
|
+
with open(dl1b_config_filename) as json_file:
|
|
340
|
+
dl1b_config = json.load(json_file)
|
|
341
|
+
picture_th = dl1b_config["tailcuts_clean_with_pedestal_threshold"]["picture_thresh"]
|
|
342
|
+
|
|
343
|
+
nsb_levels = np.array([0.00, 0.07, 0.14, 0.22, 0.38, 0.50, 0.81, 1.25, 1.76, 2.34])
|
|
344
|
+
pth = np.array([8, 8, 8, 8, 10, 10, 12, 14, 16, 18])
|
|
345
|
+
candidate_nsbs = nsb_levels[pth==picture_th]
|
|
346
|
+
|
|
347
|
+
diff = abs(candidate_nsbs - nsb)
|
|
348
|
+
return candidate_nsbs[np.argsort(diff)][0]
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_RF_model(run_id: int) -> Path:
|
|
352
|
+
"""Get the path of the RF models to be used in the DL2 production for a given run.
|
|
353
|
+
|
|
354
|
+
The choice of the models is based on the adequate additional NSB level
|
|
355
|
+
and the proper declination line of the MC used for the training.
|
|
356
|
+
"""
|
|
357
|
+
run_catalog_dir = Path(cfg.get(options.tel_id, "RUN_CATALOG"))
|
|
358
|
+
run_catalog_file = run_catalog_dir / f"RunCatalog_{date_to_dir(options.date)}.ecsv"
|
|
359
|
+
run_catalog = Table.read(run_catalog_file)
|
|
360
|
+
pointing_dec = run_catalog[run_catalog["run_id"]==run_id]["source_dec"]*u.deg
|
|
361
|
+
# the "source_dec" given in the run catalogs is not actually the source declination, but the pointing declination
|
|
362
|
+
pointing_culmination = culmination_angle(pointing_dec)
|
|
363
|
+
|
|
364
|
+
rf_models_base_dir = Path(cfg.get(options.tel_id, "RF_MODELS"))
|
|
365
|
+
rf_models_dir = get_mc_nsb_dir(run_id, rf_models_base_dir)
|
|
366
|
+
dec_list = os.listdir(rf_models_dir)
|
|
367
|
+
dec_list = [i for i in dec_list if i.startswith("dec")]
|
|
368
|
+
|
|
369
|
+
# Convert each string in the list to numerical values
|
|
370
|
+
dec_values = [convert_dec_string(dec) for dec in dec_list]
|
|
371
|
+
dec_values = [dec for dec in dec_values if dec is not None]
|
|
372
|
+
|
|
373
|
+
closest_declination = min(dec_values, key=lambda x: abs(x - pointing_dec))
|
|
374
|
+
closest_dec_culmination = culmination_angle(closest_declination)
|
|
375
|
+
|
|
376
|
+
lst_location = observatory_locations["cta_north"]
|
|
377
|
+
lst_latitude = lst_location.lat # latitude of the LST1 site
|
|
378
|
+
closest_lines = sorted(sorted(dec_values, key=lambda x: abs(x - lst_latitude))[:2])
|
|
379
|
+
|
|
380
|
+
if pointing_dec < closest_lines[0] or pointing_dec > closest_lines[1]:
|
|
381
|
+
# If the pointing declination is between the two MC lines closest to the latitude of
|
|
382
|
+
# the LST1 site, this check is not necessary.
|
|
383
|
+
log.debug(
|
|
384
|
+
f"The declination closest to {pointing_dec} is: {closest_declination}."
|
|
385
|
+
"Checking if the culmination angle is larger than the one of the pointing."
|
|
386
|
+
)
|
|
387
|
+
while closest_dec_culmination > pointing_culmination:
|
|
388
|
+
# If the culmination angle of the closest declination line is larger than for
|
|
389
|
+
# the pointing declination, remove it from the declination lines list and
|
|
390
|
+
# look for the second closest declination line.
|
|
391
|
+
declinations_dict = get_declinations_dict(dec_list, dec_values)
|
|
392
|
+
declination_str = declinations_dict[closest_declination]
|
|
393
|
+
dec_values.remove(closest_declination)
|
|
394
|
+
dec_list.remove(declination_str)
|
|
395
|
+
closest_declination = min(dec_values, key=lambda x: abs(x - pointing_dec))
|
|
396
|
+
closest_dec_culmination = culmination_angle(closest_declination)
|
|
397
|
+
|
|
398
|
+
log.debug(f"The declination line to use for the DL2 production is: {closest_declination}")
|
|
399
|
+
|
|
400
|
+
declinations_dict = get_declinations_dict(dec_list, dec_values)
|
|
401
|
+
declination_str = declinations_dict[closest_declination]
|
|
402
|
+
|
|
403
|
+
rf_model_path = rf_models_dir / declination_str
|
|
404
|
+
|
|
405
|
+
return rf_model_path.resolve()
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def get_lstcam_calib_version(env_path: Path) -> str:
|
|
409
|
+
"""Get the version of the lstcam_calib package installed in the given environment."""
|
|
410
|
+
if options.test or options.simulate:
|
|
411
|
+
return "0.1.1"
|
|
412
|
+
python_exe = f"{str(env_path)}/bin/python"
|
|
413
|
+
cmd = [python_exe, "-m", "pip", "show", "lstcam_calib"]
|
|
414
|
+
result = sp.run(cmd, capture_output=True, text=True, check=True)
|
|
415
|
+
for line in result.stdout.split('\n'):
|
|
416
|
+
if line.startswith('Version:'):
|
|
417
|
+
return line.split(':', 1)[1].strip()
|
osa/veto.py
CHANGED
|
@@ -81,7 +81,7 @@ def set_closed_sequence(sequence):
|
|
|
81
81
|
def get_closed_list(sequence_list) -> list:
|
|
82
82
|
"""Get the list of closed sequences."""
|
|
83
83
|
analysis_dir = Path(options.directory)
|
|
84
|
-
closed_ls = analysis_dir.glob("*.closed")
|
|
84
|
+
closed_ls = analysis_dir.glob("sequence_*.closed")
|
|
85
85
|
closed_list = []
|
|
86
86
|
for file in closed_ls:
|
|
87
87
|
# Extract the job name: LST1_XXXXX
|
osa/workflow/dl3.py
CHANGED
|
@@ -21,7 +21,7 @@ from osa.configs import options
|
|
|
21
21
|
from osa.configs.config import cfg
|
|
22
22
|
from osa.nightsummary.extract import build_sequences, get_source_list
|
|
23
23
|
from osa.paths import destination_dir, DEFAULT_CFG, create_source_directories, analysis_path
|
|
24
|
-
from osa.utils.cliopts import get_prod_id
|
|
24
|
+
from osa.utils.cliopts import get_prod_id
|
|
25
25
|
from osa.utils.logging import myLogger
|
|
26
26
|
from osa.utils.utils import stringify, YESTERDAY
|
|
27
27
|
|
|
@@ -250,7 +250,6 @@ def setup_global_options(date_obs, telescope):
|
|
|
250
250
|
options.date = date_obs
|
|
251
251
|
options.tel_id = telescope
|
|
252
252
|
options.prod_id = get_prod_id()
|
|
253
|
-
options.dl2_prod_id = get_dl2_prod_id()
|
|
254
253
|
options.directory = analysis_path(options.tel_id)
|
|
255
254
|
|
|
256
255
|
|
osa/workflow/stages.py
CHANGED
|
@@ -18,7 +18,7 @@ from osa.configs.config import cfg
|
|
|
18
18
|
from osa.report import history
|
|
19
19
|
from osa.utils.logging import myLogger
|
|
20
20
|
from osa.utils.utils import stringify, date_to_dir
|
|
21
|
-
from osa.paths import get_run_date
|
|
21
|
+
from osa.paths import get_run_date, get_dl1_prod_id_and_config
|
|
22
22
|
|
|
23
23
|
log = myLogger(logging.getLogger(__name__))
|
|
24
24
|
|
|
@@ -87,7 +87,7 @@ class AnalysisStage:
|
|
|
87
87
|
self._remove_drs4_baseline()
|
|
88
88
|
|
|
89
89
|
def _remove_drs4_baseline(self):
|
|
90
|
-
drs4_pedestal_basedir = Path(cfg.get("LST1", "
|
|
90
|
+
drs4_pedestal_basedir = Path(cfg.get("LST1", "CAT_A_PEDESTAL_DIR"))
|
|
91
91
|
date = date_to_dir(get_run_date(self.run))
|
|
92
92
|
drs4_pedestal_dir = drs4_pedestal_basedir / date / lstchain.__version__
|
|
93
93
|
file = drs4_pedestal_dir / "drs4_pedestal.Run{self.run}.0000.h5"
|
|
@@ -97,7 +97,7 @@ class AnalysisStage:
|
|
|
97
97
|
drs4_pedestal_dir_pro.unlink(missing_ok=True)
|
|
98
98
|
|
|
99
99
|
def _remove_calibration(self):
|
|
100
|
-
calib_basedir = Path(cfg.get("LST1", "
|
|
100
|
+
calib_basedir = Path(cfg.get("LST1", "CAT_A_CALIB_DIR"))
|
|
101
101
|
date = date_to_dir(get_run_date(self.run))
|
|
102
102
|
calib_dir = file = calib_basedir / date / lstchain.__version__
|
|
103
103
|
file = calib_dir / f"calibration_filters_{options.filters}.Run{self.run}.0000.h5"
|
|
@@ -115,19 +115,24 @@ class AnalysisStage:
|
|
|
115
115
|
interleaved_output_file.unlink(missing_ok=True)
|
|
116
116
|
|
|
117
117
|
def _remove_dl1b_output(self, file_prefix):
|
|
118
|
-
|
|
118
|
+
dl1_prod_id = get_dl1_prod_id_and_config(int(self.run[:5]))[0]
|
|
119
|
+
dl1ab_subdirectory = options.directory / dl1_prod_id
|
|
119
120
|
output_file = dl1ab_subdirectory / f"{file_prefix}{self.run}.h5"
|
|
120
121
|
output_file.unlink(missing_ok=True)
|
|
121
122
|
|
|
122
123
|
def _write_checkpoint(self):
|
|
123
124
|
"""Write the checkpoint in the history file."""
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
if self.command==cfg.get("lstchain", "r0_to_dl1"):
|
|
126
|
+
prod_id = options.prod_id
|
|
127
|
+
elif self.command==cfg.get("lstchain", "dl1ab"):
|
|
128
|
+
dl1_prod_id = get_dl1_prod_id_and_config(int(self.run[:5]))[0]
|
|
129
|
+
prod_id = dl1_prod_id
|
|
130
|
+
elif self.command==cfg.get("lstchain", "check_dl1"):
|
|
131
|
+
dl1_prod_id = get_dl1_prod_id_and_config(int(self.run[:5]))[0]
|
|
132
|
+
prod_id = dl1_prod_id
|
|
133
|
+
#elif self.command==cfg.get("lstchain", "dl1_to_dl2"):
|
|
134
|
+
# dl2_prod_id = get_dl2_prod_id(int(self.run[:5]))
|
|
135
|
+
# prod_id = dl2_prod_id
|
|
131
136
|
history(
|
|
132
137
|
run=self.run,
|
|
133
138
|
prod_id=prod_id,
|
osa/workflow/tests/test_dl3.py
CHANGED