Coverage for src / lstautorta / merge_DL3.py: 0%
163 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 11:56 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 11:56 +0000
1#!/usr/bin/env python
2# S. Caroff
4"""
5Script to merge DL3 and analyse them.
7Usage:
8$> python merge_DL3.py \
9 --input-filter "dl3_LST-1.Run04190.*.fits" \
10 --input-directory "./DL3/" \
11 --output-dir "./DL3/" \
12 --run_id 9864
13"""
15import argparse
16import glob
17import logging
18import os
19import sys
20import warnings
21from pathlib import Path
23import numpy as np
24from astropy.io import fits
26warnings.filterwarnings("ignore")
27warnings.simplefilter("ignore")
30def setup_logging(output_dir, run_id):
31 """
32 Configure logging with one log file per execution.
34 If output_dir does not exist, write the log in a fallback location
35 and raise FileNotFoundError so the script crashes immediately.
36 """
37 logger = logging.getLogger("merge_DL3")
38 logger.setLevel(logging.INFO)
39 logger.handlers.clear()
41 formatter = logging.Formatter(
42 fmt="%(asctime)s | %(levelname)s | %(message)s",
43 datefmt="%Y-%m-%d %H:%M:%S",
44 )
46 # Always keep stdout logging
47 stream_handler = logging.StreamHandler(sys.stdout)
48 stream_handler.setLevel(logging.INFO)
49 stream_handler.setFormatter(formatter)
50 logger.addHandler(stream_handler)
52 output_path = Path(output_dir)
54 if not output_path.exists():
55 fallback_log = Path.cwd() / f"merge_DL3_run{run_id}.log"
57 file_handler = logging.FileHandler(fallback_log)
58 file_handler.setLevel(logging.INFO)
59 file_handler.setFormatter(formatter)
60 logger.addHandler(file_handler)
62 logger.info("============================================================")
63 logger.error("Output directory does not exist: %s", output_path)
64 logger.error("Fallback log file: %s", fallback_log)
65 logger.error("Crashing because output directory is missing.")
66 logger.info("============================================================")
68 raise FileNotFoundError(f"Output directory does not exist: {output_path}")
70 log_dir = output_path / "logs"
71 log_dir.mkdir(exist_ok=True)
73 log_file = log_dir / f"merge_DL3_run{run_id}.log"
75 file_handler = logging.FileHandler(log_file)
76 file_handler.setLevel(logging.INFO)
77 file_handler.setFormatter(formatter)
78 logger.addHandler(file_handler)
80 logger.info("============================================================")
81 logger.info("Starting merge_DL3.py")
82 logger.info("Log file: %s", log_file)
84 return logger, log_file
87def main():
88 parser = argparse.ArgumentParser(description="DL3 merger")
90 parser.add_argument(
91 "--input-filter",
92 "-f",
93 type=str,
94 dest="input_data",
95 help='DL3 filter selection, example: "dl3_LST-1.Run04190.*.fits"',
96 default=None,
97 required=True,
98 )
100 parser.add_argument(
101 "--input-directory",
102 "-d",
103 type=str,
104 dest="input_dir",
105 help="Path to input DL3 files directory",
106 default=None,
107 required=True,
108 )
110 parser.add_argument(
111 "--output-dir",
112 "-o",
113 type=str,
114 dest="output_fits_dir",
115 help="Path to output files",
116 default=None,
117 required=True,
118 )
120 parser.add_argument(
121 "--run_id",
122 "-r",
123 type=int,
124 dest="run_id",
125 help="Run ID to assign to merged file",
126 default=None,
127 required=True,
128 )
130 args = parser.parse_args()
132 logger, log_file = setup_logging(args.output_fits_dir, args.run_id)
134 try:
135 logger.info("Arguments:")
136 logger.info(" input_filter = %s", args.input_data)
137 logger.info(" input_dir = %s", args.input_dir)
138 logger.info(" output_fits_dir= %s", args.output_fits_dir)
139 logger.info(" run_id = %s", args.run_id)
141 file_filter = args.input_data
142 directory = args.input_dir
144 search_pattern = directory + file_filter
145 logger.info("Searching files with pattern: %s", search_pattern)
146 filelist = glob.glob(search_pattern)
147 filelist = sorted(filelist)
149 logger.info("Found %d input files.", len(filelist))
150 for f in filelist:
151 logger.info(" input file: %s", f)
153 if len(filelist) == 0:
154 logger.error("No input files found. Exiting.")
155 sys.exit(1)
157 logger.info("Opening first FITS file: %s", filelist[0])
158 hdu1 = fits.open(filelist[0])
160 for file in filelist[1:]:
161 logger.info("Processing file: %s", file)
162 hdu2 = fits.open(file)
164 nev_hdu1 = len(hdu1["EVENTS"].data)
165 nev_hdu2 = len(hdu2["EVENTS"].data)
167 logger.info(
168 "Current merge status: nev_hdu1=%d, nev_hdu2=%d, RA1=%.6f, RA2=%.6f",
169 nev_hdu1,
170 nev_hdu2,
171 hdu1["EVENTS"].header["RA_PNT"],
172 hdu2["EVENTS"].header["RA_PNT"],
173 )
175 if (hdu1["EVENTS"].header["RA_PNT"] - hdu2["EVENTS"].header["RA_PNT"]) ** 2 < 2**2:
176 logger.info("File accepted for merge: %s", file)
178 hdu1["EVENTS"].header["RA_PNT"] = (
179 hdu1["EVENTS"].header["RA_PNT"] * nev_hdu1 + hdu2["EVENTS"].header["RA_PNT"] * nev_hdu2
180 ) / (nev_hdu1 + nev_hdu2)
182 hdu1["EVENTS"].header["DEC_PNT"] = (
183 hdu1["EVENTS"].header["DEC_PNT"] * nev_hdu1 + hdu2["EVENTS"].header["DEC_PNT"] * nev_hdu2
184 ) / (nev_hdu1 + nev_hdu2)
186 hdu1["EVENTS"].header["ALT_PNT"] = (
187 hdu1["EVENTS"].header["ALT_PNT"] * nev_hdu1 + hdu2["EVENTS"].header["ALT_PNT"] * nev_hdu2
188 ) / (nev_hdu1 + nev_hdu2)
190 hdu1["EVENTS"].header["AZ_PNT"] = (
191 hdu1["EVENTS"].header["AZ_PNT"] * nev_hdu1 + hdu2["EVENTS"].header["AZ_PNT"] * nev_hdu2
192 ) / (nev_hdu1 + nev_hdu2)
194 hdu1["EVENTS"].header["DEADC"] = (
195 hdu1["EVENTS"].header["DEADC"] * nev_hdu1 + hdu2["EVENTS"].header["DEADC"] * nev_hdu2
196 ) / (nev_hdu1 + nev_hdu2)
198 if hdu1["EVENTS"].header["OBS_ID"] != hdu2["EVENTS"].header["OBS_ID"]:
199 logger.warning(
200 "Merging DL3 events with different OBS_ID values: %s vs %s",
201 hdu1["EVENTS"].header["OBS_ID"],
202 hdu2["EVENTS"].header["OBS_ID"],
203 )
205 hdu1["EVENTS"].data = np.append(hdu1["EVENTS"].data, hdu2["EVENTS"].data)
206 logger.info("Merge done. New event count: %d", len(hdu1["EVENTS"].data))
207 else:
208 logger.info("File rejected by RA_PNT criterion: %s", file)
210 hdu2.close()
212 logger.info("Sorting merged EVENTS by TIME...")
213 hdu1["EVENTS"].data = hdu1["EVENTS"].data[np.argsort(hdu1["EVENTS"].data["TIME"])]
215 logger.info("Applying 8-hour time window selection...")
216 initial_count = len(hdu1["EVENTS"].data)
217 hdu1["EVENTS"].data = hdu1["EVENTS"].data[
218 (hdu1["EVENTS"].data["TIME"] - hdu1["EVENTS"].data["TIME"][0]) < 8 * 3600
219 ]
220 logger.info(
221 "Time selection kept %d / %d events.",
222 len(hdu1["EVENTS"].data),
223 initial_count,
224 )
226 logger.info("Updating FITS headers...")
227 hdu1["EVENTS"].header["OBS_ID"] = args.run_id
228 hdu1["EVENTS"].header["N_TELS"] = 1
229 hdu1["EVENTS"].header["TELLIST"] = "LST-1"
230 hdu1["EVENTS"].header["TSTART"] = hdu1["EVENTS"].data["TIME"][0]
231 hdu1["EVENTS"].header["TSTOP"] = hdu1["EVENTS"].data["TIME"][-1]
232 hdu1["EVENTS"].header["ONTIME"] = hdu1["EVENTS"].data["TIME"][-1] - hdu1["EVENTS"].data["TIME"][0]
233 hdu1["EVENTS"].header["LIVETIME"] = hdu1["EVENTS"].header["ONTIME"] * hdu1["EVENTS"].header["DEADC"]
234 hdu1["EVENTS"].header["INSTRUME"] = "LST"
235 hdu1["EVENTS"].header["DATE-OBS"] = 0
236 hdu1["EVENTS"].header["TIME-OBS"] = 0
237 hdu1["EVENTS"].header["DATE-END"] = 0
238 hdu1["EVENTS"].header["TIME-END"] = 0
239 hdu1["EVENTS"].header["TELAPSE"] = 0
241 hdu1["POINTING"].header["ALT_PNT"] = hdu1["EVENTS"].header["ALT_PNT"]
242 hdu1["POINTING"].header["AZ_PNT"] = hdu1["EVENTS"].header["AZ_PNT"]
243 hdu1["POINTING"].header["TIME"] = hdu1["EVENTS"].header["TSTART"]
245 prefix = (5 - len(str(hdu1["EVENTS"].header["OBS_ID"]))) * "0"
247 hdu1["EVENTS"].columns["TIME"].unit = "s"
248 hdu1["EVENTS"].columns["ENERGY"].unit = "TeV"
249 hdu1["EVENTS"].columns["RA"].unit = "deg"
250 hdu1["EVENTS"].columns["DEC"].unit = "deg"
252 hdu1["GTI"].data[0][0] = hdu1["EVENTS"].data[0][1]
253 hdu1["GTI"].data[0][1] = hdu1["EVENTS"].data[-1][1]
255 output_file = args.output_fits_dir + "dl3_LST-1.Run" + prefix + str(hdu1["EVENTS"].header["OBS_ID"]) + ".fits"
257 logger.info("Writing merged FITS file to: %s", output_file)
258 hdu1.writeto(output_file, overwrite=True)
259 logger.info("Merged FITS file written successfully.")
261 plots_dir = directory[:-10] + str(int(args.run_id)) + "/plots"
262 logger.info("Checking plots directory: %s", plots_dir)
263 if not os.path.exists(plots_dir):
264 os.mkdir(plots_dir)
265 logger.info("Created plots directory: %s", plots_dir)
266 else:
267 logger.info("Plots directory already exists.")
269 logger.info("Copying previous DL3 files from the last 150 runs...")
270 for i in range(150):
271 src_cmd = "cp -f " + directory[:-10] + str(int(args.run_id) - i) + "/DL3/dl3_LST* " + directory + "/."
272 logger.info("Executing command: %s", src_cmd)
273 ret = os.system(src_cmd)
274 logger.info("Command exit code: %s", ret)
276 filelist_runs = glob.glob(directory + "dl3_LST*")
277 logger.info("Found %d DL3 files in working directory after copy.", len(filelist_runs))
279 runlist_to_analyse = []
280 for i in range(len(filelist_runs)):
281 logger.info("Inspecting candidate run file: %s", filelist_runs[i])
282 hdutemp = fits.open(filelist_runs[i])
284 if (hdutemp["EVENTS"].header["RA_PNT"] - hdu1["EVENTS"].header["RA_PNT"]) ** 2 < 9 and (
285 hdutemp["EVENTS"].header["DEC_PNT"] - hdu1["EVENTS"].header["DEC_PNT"]
286 ) ** 2 < 9:
287 run_id_candidate = int(filelist_runs[i][-10:-5])
288 runlist_to_analyse.append(run_id_candidate)
289 logger.info("Run selected for analysis: %s", run_id_candidate)
291 hdutemp.close()
293 cmd_index = (
294 "lstchain_create_dl3_index_files --file-pattern='dl3_LST*' "
295 "--input-dl3-dir='" + directory + "' --overwrite "
296 )
297 logger.info("Executing index command: %s", cmd_index)
298 ret = os.system(cmd_index)
299 logger.info("Index command exit code: %s", ret)
301 logger.info("Final RA_PNT: %s", hdu1["EVENTS"].header["RA_PNT"])
302 logger.info("Final run list for analysis: %s", runlist_to_analyse)
304 # Uncomment if needed
305 # RA, DEC = CreatePlots(
306 # directory,
307 # [runlist_to_analyse[-1]],
308 # hdu1["EVENTS"].header["RA_PNT"],
309 # hdu1["EVENTS"].header["DEC_PNT"],
310 # hdu1["EVENTS"].header["RA_PNT"],
311 # hdu1["EVENTS"].header["DEC_PNT"],
312 # logger=logger,
313 # )
315 hdu1.close()
317 logger.info("merge_DL3.py finished successfully.")
318 logger.info("============================================================")
320 except Exception as e:
321 logger.exception("Fatal error while running merge_DL3.py: %s", e)
322 logger.info("============================================================")
323 raise
326if __name__ == "__main__":
327 main()