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.
Files changed (46) hide show
  1. {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/METADATA +4 -5
  2. lstosa-0.11.0.dist-info/RECORD +84 -0
  3. {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/WHEEL +1 -1
  4. {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/entry_points.txt +1 -0
  5. osa/_version.py +9 -4
  6. osa/configs/options.py +2 -0
  7. osa/configs/sequencer.cfg +21 -7
  8. osa/conftest.py +146 -6
  9. osa/high_level/significance.py +5 -3
  10. osa/high_level/tests/test_significance.py +3 -0
  11. osa/job.py +52 -26
  12. osa/nightsummary/extract.py +12 -3
  13. osa/nightsummary/tests/test_extract.py +5 -0
  14. osa/paths.py +111 -28
  15. osa/provenance/capture.py +1 -1
  16. osa/provenance/config/definition.yaml +7 -0
  17. osa/provenance/utils.py +22 -7
  18. osa/scripts/autocloser.py +0 -10
  19. osa/scripts/calibration_pipeline.py +9 -2
  20. osa/scripts/closer.py +136 -55
  21. osa/scripts/copy_datacheck.py +5 -3
  22. osa/scripts/datasequence.py +45 -71
  23. osa/scripts/gain_selection.py +14 -15
  24. osa/scripts/provprocess.py +16 -7
  25. osa/scripts/sequencer.py +49 -34
  26. osa/scripts/sequencer_catB_tailcuts.py +239 -0
  27. osa/scripts/sequencer_webmaker.py +4 -0
  28. osa/scripts/show_run_summary.py +2 -2
  29. osa/scripts/simulate_processing.py +4 -7
  30. osa/scripts/tests/test_osa_scripts.py +67 -22
  31. osa/scripts/update_source_catalog.py +45 -22
  32. osa/tests/test_jobs.py +28 -11
  33. osa/tests/test_paths.py +6 -6
  34. osa/tests/test_raw.py +4 -4
  35. osa/utils/cliopts.py +37 -32
  36. osa/utils/register.py +18 -13
  37. osa/utils/tests/test_utils.py +14 -0
  38. osa/utils/utils.py +186 -56
  39. osa/veto.py +1 -1
  40. osa/workflow/dl3.py +1 -2
  41. osa/workflow/stages.py +16 -11
  42. osa/workflow/tests/test_dl3.py +2 -1
  43. osa/workflow/tests/test_stages.py +7 -5
  44. lstosa-0.10.18.dist-info/RECORD +0 -83
  45. {lstosa-0.10.18.dist-info → lstosa-0.11.0.dist-info}/LICENSE +0 -0
  46. {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
- set_prod_ids()
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-dl2",
181
+ "--no-dl1ab",
188
182
  action="store_true",
189
183
  default=False,
190
- help="Do not produce DL2 files (default False)",
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.no_dl2 = opts.no_dl2
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-dl2",
279
+ "--no-dl1ab",
273
280
  action="store_true",
274
281
  default=False,
275
- help="Do not produce DL2 files (default False)",
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.no_dl2 = opts.no_dl2
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
- set_prod_ids()
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=str, help="Observation starting date YYYYMMDD")
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
- set_prod_ids()
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
- # parser.add_argument(
406
- # "-d",
407
- # "--date",
408
- # action="store",
409
- # type=str,
410
- # dest="date",
411
- # help="observation ending date YYYY-MM-DD [default today]",
412
- # )
413
- # parser.add_argument("tel_id", choices=["ST", "LST1", "LST2"])
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 destination_dir
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 dl1_filepath_dl1_dir.is_symlink():
87
- dl1_filepath_dl1_dir.symlink_to(output_file.resolve())
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 / options.dl2_prod_id
117
+ initial_dir = initial_dir / dl2_prod_id
113
118
 
114
119
  elif concept == "DL1AB":
115
- initial_dir = initial_dir / options.dl1_prod_id
120
+ initial_dir = initial_dir / dl1_prod_id
116
121
 
117
122
  elif concept == "DATACHECK":
118
- initial_dir = initial_dir / options.dl1_prod_id
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
 
@@ -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, get_dl2_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", "PEDESTAL_DIR"))
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", "CALIB_DIR"))
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
- dl1ab_subdirectory = options.directory / options.dl1_prod_id
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
- command_to_prod_id = {
125
- cfg.get("lstchain", "r0_to_dl1"): options.prod_id,
126
- cfg.get("lstchain", "dl1ab"): options.dl1_prod_id,
127
- cfg.get("lstchain", "check_dl1"): options.dl1_prod_id,
128
- cfg.get("lstchain", "dl1_to_dl2"): options.dl2_prod_id
129
- }
130
- prod_id = command_to_prod_id.get(self.command)
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,
@@ -1,6 +1,7 @@
1
1
  import subprocess as sp
2
+ import pytest
2
3
 
3
-
4
+ @pytest.mark.skip(reason="Currently the DL3 production is not working")
4
5
  def test_dl3_stage():
5
6
  output = sp.run(
6
7
  ["dl3_stage", "-d", "2020-01-17", "-s", "LST1"], text=True, stdout=sp.PIPE, stderr=sp.PIPE