gammasimtools 0.19.0__py3-none-any.whl → 0.21.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.
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/METADATA +1 -3
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/RECORD +54 -51
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/entry_points.txt +3 -3
- simtools/_version.py +2 -2
- simtools/applications/calculate_incident_angles.py +182 -0
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +17 -14
- simtools/applications/db_add_value_from_json_to_db.py +6 -9
- simtools/applications/db_generate_compound_indexes.py +7 -3
- simtools/applications/db_get_file_from_db.py +11 -23
- simtools/applications/derive_psf_parameters.py +58 -39
- simtools/applications/derive_trigger_rates.py +91 -0
- simtools/applications/generate_corsika_histograms.py +7 -184
- simtools/applications/maintain_simulation_model_add_production.py +105 -0
- simtools/applications/plot_simtel_events.py +5 -189
- simtools/applications/print_version.py +8 -7
- simtools/applications/validate_file_using_schema.py +7 -4
- simtools/configuration/commandline_parser.py +17 -11
- simtools/corsika/corsika_histograms.py +81 -0
- simtools/data_model/validate_data.py +8 -3
- simtools/db/db_handler.py +122 -31
- simtools/db/db_model_upload.py +51 -30
- simtools/dependencies.py +10 -5
- simtools/layout/array_layout_utils.py +37 -5
- simtools/model/array_model.py +18 -1
- simtools/model/model_repository.py +118 -63
- simtools/model/site_model.py +25 -0
- simtools/production_configuration/derive_corsika_limits.py +9 -34
- simtools/ray_tracing/incident_angles.py +706 -0
- simtools/ray_tracing/psf_parameter_optimisation.py +999 -565
- simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -2
- simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +1 -1
- simtools/schemas/model_parameters/nsb_spectrum.schema.yml +22 -29
- simtools/schemas/model_parameters/stars.schema.yml +1 -1
- simtools/schemas/production_tables.schema.yml +5 -0
- simtools/simtel/simtel_config_writer.py +18 -20
- simtools/simtel/simtel_io_event_histograms.py +253 -516
- simtools/simtel/simtel_io_event_reader.py +51 -2
- simtools/simtel/simtel_io_event_writer.py +31 -11
- simtools/simtel/simtel_io_metadata.py +1 -1
- simtools/simtel/simtel_table_reader.py +3 -3
- simtools/simulator.py +1 -4
- simtools/telescope_trigger_rates.py +119 -0
- simtools/testing/log_inspector.py +13 -11
- simtools/utils/geometry.py +20 -0
- simtools/version.py +89 -0
- simtools/{corsika/corsika_histograms_visualize.py → visualization/plot_corsika_histograms.py} +109 -0
- simtools/visualization/plot_incident_angles.py +431 -0
- simtools/visualization/plot_psf.py +673 -0
- simtools/visualization/plot_simtel_event_histograms.py +376 -0
- simtools/visualization/{simtel_event_plots.py → plot_simtel_events.py} +284 -87
- simtools/visualization/visualize.py +1 -3
- simtools/applications/calculate_trigger_rate.py +0 -187
- simtools/applications/generate_sim_telarray_histograms.py +0 -196
- simtools/applications/maintain_simulation_model_add_production_table.py +0 -71
- simtools/simtel/simtel_io_histogram.py +0 -623
- simtools/simtel/simtel_io_histograms.py +0 -556
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""Plots for light emission (flasher/calibration) sim_telarray events."""
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import astropy.units as u
|
|
7
8
|
import matplotlib.pyplot as plt
|
|
@@ -11,7 +12,12 @@ from ctapipe.io import EventSource
|
|
|
11
12
|
from ctapipe.visualization import CameraDisplay
|
|
12
13
|
from scipy import signal as _signal
|
|
13
14
|
|
|
15
|
+
from simtools.data_model.metadata_collector import MetadataCollector
|
|
16
|
+
from simtools.visualization.plot_corsika_histograms import save_figs_to_pdf
|
|
17
|
+
from simtools.visualization.visualize import save_figure
|
|
18
|
+
|
|
14
19
|
__all__ = [
|
|
20
|
+
"generate_and_save_plots",
|
|
15
21
|
"plot_simtel_event_image",
|
|
16
22
|
"plot_simtel_integrated_pedestal_image",
|
|
17
23
|
"plot_simtel_integrated_signal_image",
|
|
@@ -29,10 +35,45 @@ NO_R1_WAVEFORMS_MSG = "No R1 waveforms available in event"
|
|
|
29
35
|
TIME_NS_LABEL = "time [ns]"
|
|
30
36
|
R1_SAMPLES_LABEL = "R1 samples [d.c.]"
|
|
31
37
|
|
|
38
|
+
# Choices understood by the dispatcher used below
|
|
39
|
+
PLOT_CHOICES = {
|
|
40
|
+
"event_image": "event_image",
|
|
41
|
+
"time_traces": "time_traces",
|
|
42
|
+
"waveform_matrix": "waveform_matrix",
|
|
43
|
+
"step_traces": "step_traces",
|
|
44
|
+
"integrated_signal_image": "integrated_signal_image",
|
|
45
|
+
"integrated_pedestal_image": "integrated_pedestal_image",
|
|
46
|
+
"peak_timing": "peak_timing",
|
|
47
|
+
"all": "all",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get_event_source_and_r1_tel(filename, event_index=None, warn_context=None):
|
|
52
|
+
"""Return (source, event, first_r1_tel_id) or None if unavailable.
|
|
32
53
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
Centralizes creation of EventSource, event selection, and first R1 tel-id lookup.
|
|
55
|
+
|
|
56
|
+
When no event exists, logs a standard warning. When the event has no R1 tel data,
|
|
57
|
+
logs either a contextual message ("Event has no R1 data for <context>") if
|
|
58
|
+
warn_context is provided, or the generic "First event has no R1 telescope data".
|
|
59
|
+
"""
|
|
60
|
+
source = EventSource(filename, max_events=None)
|
|
61
|
+
event = _select_event_by_type(source)(event_index=event_index)
|
|
62
|
+
if not event:
|
|
63
|
+
_logger.warning("No event found in the file.")
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
tel_ids = sorted(getattr(event.r1, "tel", {}).keys())
|
|
67
|
+
if not tel_ids:
|
|
68
|
+
if warn_context:
|
|
69
|
+
_logger.warning("Event has no R1 data for %s", warn_context)
|
|
70
|
+
else:
|
|
71
|
+
_logger.warning("First event has no R1 telescope data")
|
|
72
|
+
return None
|
|
73
|
+
return source, event, int(tel_ids[0])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _compute_integration_window(peak_idx, n_samp, half_width, mode, offset):
|
|
36
77
|
"""Return [a, b) window bounds for integration for signal/pedestal modes."""
|
|
37
78
|
hw = int(half_width)
|
|
38
79
|
win_len = 2 * hw + 1
|
|
@@ -53,9 +94,7 @@ def _compute_integration_window(
|
|
|
53
94
|
return a, b
|
|
54
95
|
|
|
55
96
|
|
|
56
|
-
def _format_integrated_title(
|
|
57
|
-
tel_label: str, et_name: str, half_width: int, mode: str, offset: int | None
|
|
58
|
-
) -> str:
|
|
97
|
+
def _format_integrated_title(tel_label, et_name, half_width, mode, offset):
|
|
59
98
|
win_len = 2 * int(half_width) + 1
|
|
60
99
|
if mode == "signal":
|
|
61
100
|
return f"{tel_label} integrated signal (win {win_len}) ({et_name})"
|
|
@@ -138,17 +177,10 @@ def plot_simtel_event_image(filename, distance=None, event_index=None):
|
|
|
138
177
|
matplotlib.figure.Figure | None
|
|
139
178
|
The created figure, or ``None`` if no suitable event/image is available.
|
|
140
179
|
"""
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if not event:
|
|
144
|
-
_logger.warning("No event found in the file.")
|
|
145
|
-
return None
|
|
146
|
-
|
|
147
|
-
tel_ids = sorted(getattr(event.r1, "tel", {}).keys())
|
|
148
|
-
if not tel_ids:
|
|
149
|
-
_logger.warning("First event has no R1 telescope data")
|
|
180
|
+
prepared = _get_event_source_and_r1_tel(filename, event_index=event_index, warn_context=None)
|
|
181
|
+
if prepared is None:
|
|
150
182
|
return None
|
|
151
|
-
tel_id =
|
|
183
|
+
source, event, tel_id = prepared
|
|
152
184
|
|
|
153
185
|
calib = CameraCalibrator(subarray=source.subarray)
|
|
154
186
|
calib(event)
|
|
@@ -198,9 +230,9 @@ def plot_simtel_event_image(filename, distance=None, event_index=None):
|
|
|
198
230
|
|
|
199
231
|
def plot_simtel_time_traces(
|
|
200
232
|
filename,
|
|
201
|
-
tel_id
|
|
202
|
-
n_pixels
|
|
203
|
-
event_index
|
|
233
|
+
tel_id=None,
|
|
234
|
+
n_pixels=3,
|
|
235
|
+
event_index=None,
|
|
204
236
|
):
|
|
205
237
|
"""
|
|
206
238
|
Plot R1 time traces for a few pixels of one event.
|
|
@@ -221,15 +253,13 @@ def plot_simtel_time_traces(
|
|
|
221
253
|
matplotlib.figure.Figure | None
|
|
222
254
|
The created figure, or ``None`` if R1 waveforms are unavailable.
|
|
223
255
|
"""
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
dl1_tel_ids = sorted(getattr(event.dl1, "tel", {}).keys())
|
|
232
|
-
tel_id = tel_id or dl1_tel_ids[0]
|
|
256
|
+
prepared = _get_event_source_and_r1_tel(
|
|
257
|
+
filename, event_index=event_index, warn_context="time traces plot"
|
|
258
|
+
)
|
|
259
|
+
if prepared is None:
|
|
260
|
+
return None
|
|
261
|
+
source, event, tel_id_default = prepared
|
|
262
|
+
tel_id = tel_id or tel_id_default
|
|
233
263
|
|
|
234
264
|
calib = CameraCalibrator(subarray=source.subarray)
|
|
235
265
|
try:
|
|
@@ -273,10 +303,10 @@ def plot_simtel_time_traces(
|
|
|
273
303
|
|
|
274
304
|
def plot_simtel_waveform_matrix(
|
|
275
305
|
filename,
|
|
276
|
-
tel_id
|
|
277
|
-
vmax
|
|
278
|
-
event_index
|
|
279
|
-
pixel_step
|
|
306
|
+
tel_id=None,
|
|
307
|
+
vmax=None,
|
|
308
|
+
event_index=None,
|
|
309
|
+
pixel_step=None,
|
|
280
310
|
):
|
|
281
311
|
"""
|
|
282
312
|
Create a pseudocolor image of R1 waveforms (sample index vs. pixel id).
|
|
@@ -299,15 +329,13 @@ def plot_simtel_waveform_matrix(
|
|
|
299
329
|
matplotlib.figure.Figure | None
|
|
300
330
|
The created figure, or ``None`` if R1 waveforms are unavailable.
|
|
301
331
|
"""
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if r1_tel_ids:
|
|
307
|
-
tel_id = tel_id or r1_tel_ids[0]
|
|
308
|
-
else:
|
|
309
|
-
_logger.warning("Event has no R1 data for waveform plot")
|
|
332
|
+
prepared = _get_event_source_and_r1_tel(
|
|
333
|
+
filename, event_index=event_index, warn_context="waveform plot"
|
|
334
|
+
)
|
|
335
|
+
if prepared is None:
|
|
310
336
|
return None
|
|
337
|
+
source, event, tel_id_default = prepared
|
|
338
|
+
tel_id = tel_id or tel_id_default
|
|
311
339
|
|
|
312
340
|
waveforms = getattr(event.r1.tel.get(tel_id, None), "waveform", None)
|
|
313
341
|
if waveforms is None:
|
|
@@ -342,10 +370,10 @@ def plot_simtel_waveform_matrix(
|
|
|
342
370
|
|
|
343
371
|
def plot_simtel_step_traces(
|
|
344
372
|
filename,
|
|
345
|
-
tel_id
|
|
346
|
-
pixel_step
|
|
347
|
-
max_pixels
|
|
348
|
-
event_index
|
|
373
|
+
tel_id=None,
|
|
374
|
+
pixel_step=100,
|
|
375
|
+
max_pixels=None,
|
|
376
|
+
event_index=None,
|
|
349
377
|
):
|
|
350
378
|
"""
|
|
351
379
|
Plot step-style R1 traces for regularly sampled pixels (0, N, 2N, ...).
|
|
@@ -368,15 +396,13 @@ def plot_simtel_step_traces(
|
|
|
368
396
|
matplotlib.figure.Figure | None
|
|
369
397
|
The created figure, or ``None`` if R1 waveforms are unavailable.
|
|
370
398
|
"""
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if r1_tel_ids:
|
|
376
|
-
tel_id = tel_id or r1_tel_ids[0]
|
|
377
|
-
else:
|
|
378
|
-
_logger.warning("Event has no R1 data for traces plot")
|
|
399
|
+
prepared = _get_event_source_and_r1_tel(
|
|
400
|
+
filename, event_index=event_index, warn_context="traces plot"
|
|
401
|
+
)
|
|
402
|
+
if prepared is None:
|
|
379
403
|
return None
|
|
404
|
+
source, event, tel_id_default = prepared
|
|
405
|
+
tel_id = tel_id or tel_id_default
|
|
380
406
|
|
|
381
407
|
waveforms = getattr(event.r1.tel.get(tel_id, None), "waveform", None)
|
|
382
408
|
if waveforms is None:
|
|
@@ -583,13 +609,13 @@ def _draw_peak_hist(
|
|
|
583
609
|
|
|
584
610
|
def plot_simtel_peak_timing(
|
|
585
611
|
filename,
|
|
586
|
-
tel_id
|
|
587
|
-
sum_threshold
|
|
588
|
-
peak_width
|
|
589
|
-
examples
|
|
590
|
-
timing_bins
|
|
591
|
-
return_stats
|
|
592
|
-
event_index
|
|
612
|
+
tel_id=None,
|
|
613
|
+
sum_threshold=10.0,
|
|
614
|
+
peak_width=8,
|
|
615
|
+
examples=3,
|
|
616
|
+
timing_bins=None,
|
|
617
|
+
return_stats=False,
|
|
618
|
+
event_index=None,
|
|
593
619
|
):
|
|
594
620
|
"""
|
|
595
621
|
Peak finding per pixel; report mean/std of peak sample and plot a histogram.
|
|
@@ -620,15 +646,13 @@ def plot_simtel_peak_timing(
|
|
|
620
646
|
``return_stats`` is True, a tuple ``(fig, stats)`` is returned, where
|
|
621
647
|
``stats`` has keys ``{"considered", "found", "mean", "std"}``.
|
|
622
648
|
"""
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if r1_tel_ids:
|
|
628
|
-
tel_id = tel_id or r1_tel_ids[0]
|
|
629
|
-
else:
|
|
630
|
-
_logger.warning("Event has no R1 data for peak timing plot")
|
|
649
|
+
prepared = _get_event_source_and_r1_tel(
|
|
650
|
+
filename, event_index=event_index, warn_context="peak timing plot"
|
|
651
|
+
)
|
|
652
|
+
if prepared is None:
|
|
631
653
|
return None
|
|
654
|
+
source, event, tel_id_default = prepared
|
|
655
|
+
tel_id = tel_id or tel_id_default
|
|
632
656
|
|
|
633
657
|
waveforms = getattr(event.r1.tel.get(tel_id, None), "waveform", None)
|
|
634
658
|
if waveforms is None:
|
|
@@ -718,15 +742,13 @@ def _prepare_waveforms_for_image(filename, tel_id, context_no_r1, event_index=No
|
|
|
718
742
|
``n_samp`` are integers, and ``source``, ``event`` and ``tel_id`` are
|
|
719
743
|
the ctapipe objects used. Returns ``None`` on failure.
|
|
720
744
|
"""
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
if r1_tel_ids:
|
|
726
|
-
tel_id = tel_id or r1_tel_ids[0]
|
|
727
|
-
else:
|
|
728
|
-
_logger.warning(f"Event has no R1 data for {context_no_r1}")
|
|
745
|
+
prepared = _get_event_source_and_r1_tel(
|
|
746
|
+
filename, event_index=event_index, warn_context=context_no_r1
|
|
747
|
+
)
|
|
748
|
+
if prepared is None:
|
|
729
749
|
return None
|
|
750
|
+
source, event, tel_id_default = prepared
|
|
751
|
+
tel_id = tel_id or tel_id_default
|
|
730
752
|
|
|
731
753
|
waveforms = getattr(event.r1.tel.get(tel_id, None), "waveform", None)
|
|
732
754
|
if waveforms is None:
|
|
@@ -742,9 +764,9 @@ def _prepare_waveforms_for_image(filename, tel_id, context_no_r1, event_index=No
|
|
|
742
764
|
|
|
743
765
|
def plot_simtel_integrated_signal_image(
|
|
744
766
|
filename,
|
|
745
|
-
tel_id
|
|
746
|
-
half_width
|
|
747
|
-
event_index
|
|
767
|
+
tel_id=None,
|
|
768
|
+
half_width=8,
|
|
769
|
+
event_index=None,
|
|
748
770
|
):
|
|
749
771
|
"""Plot camera image of integrated signal per pixel around the flasher peak."""
|
|
750
772
|
return _plot_simtel_integrated_image(
|
|
@@ -758,10 +780,10 @@ def plot_simtel_integrated_signal_image(
|
|
|
758
780
|
|
|
759
781
|
def plot_simtel_integrated_pedestal_image(
|
|
760
782
|
filename,
|
|
761
|
-
tel_id
|
|
762
|
-
half_width
|
|
763
|
-
offset
|
|
764
|
-
event_index
|
|
783
|
+
tel_id=None,
|
|
784
|
+
half_width=8,
|
|
785
|
+
offset=16,
|
|
786
|
+
event_index=None,
|
|
765
787
|
):
|
|
766
788
|
"""Plot camera image of integrated pedestal per pixel away from the flasher peak."""
|
|
767
789
|
return _plot_simtel_integrated_image(
|
|
@@ -776,11 +798,11 @@ def plot_simtel_integrated_pedestal_image(
|
|
|
776
798
|
|
|
777
799
|
def _plot_simtel_integrated_image(
|
|
778
800
|
filename,
|
|
779
|
-
tel_id
|
|
780
|
-
half_width
|
|
781
|
-
event_index
|
|
782
|
-
mode
|
|
783
|
-
offset
|
|
801
|
+
tel_id,
|
|
802
|
+
half_width,
|
|
803
|
+
event_index,
|
|
804
|
+
mode,
|
|
805
|
+
offset=None,
|
|
784
806
|
):
|
|
785
807
|
"""Shared implementation for integrated signal/pedestal images.
|
|
786
808
|
|
|
@@ -814,3 +836,178 @@ def _plot_simtel_integrated_image(
|
|
|
814
836
|
ax.set_axis_off()
|
|
815
837
|
fig.tight_layout()
|
|
816
838
|
return fig
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def _make_output_paths(ioh, base, input_file):
|
|
842
|
+
"""Return (out_dir, pdf_path) based on base name and input file."""
|
|
843
|
+
out_dir = ioh.get_output_directory(label=Path(__file__).stem)
|
|
844
|
+
pdf_path = ioh.get_output_file(f"{base}_{input_file.stem}" if base else input_file.stem)
|
|
845
|
+
pdf_path = Path(f"{pdf_path}.pdf") if Path(pdf_path).suffix != ".pdf" else Path(pdf_path)
|
|
846
|
+
return out_dir, pdf_path
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def _call_peak_timing(
|
|
850
|
+
filename,
|
|
851
|
+
tel_id=None,
|
|
852
|
+
sum_threshold=10.0,
|
|
853
|
+
peak_width=8,
|
|
854
|
+
examples=3,
|
|
855
|
+
timing_bins=None,
|
|
856
|
+
event_index=None,
|
|
857
|
+
):
|
|
858
|
+
"""Call plot_simtel_peak_timing while tolerating older signature.
|
|
859
|
+
|
|
860
|
+
Returns a matplotlib Figure or None.
|
|
861
|
+
"""
|
|
862
|
+
try:
|
|
863
|
+
fig_stats = plot_simtel_peak_timing(
|
|
864
|
+
filename,
|
|
865
|
+
tel_id=tel_id,
|
|
866
|
+
sum_threshold=sum_threshold,
|
|
867
|
+
peak_width=peak_width,
|
|
868
|
+
examples=examples,
|
|
869
|
+
timing_bins=timing_bins,
|
|
870
|
+
return_stats=True,
|
|
871
|
+
event_index=event_index,
|
|
872
|
+
)
|
|
873
|
+
return fig_stats[0] if isinstance(fig_stats, tuple) else fig_stats
|
|
874
|
+
except TypeError:
|
|
875
|
+
return plot_simtel_peak_timing(
|
|
876
|
+
filename,
|
|
877
|
+
tel_id=tel_id,
|
|
878
|
+
sum_threshold=sum_threshold,
|
|
879
|
+
peak_width=peak_width,
|
|
880
|
+
examples=examples,
|
|
881
|
+
timing_bins=timing_bins,
|
|
882
|
+
event_index=event_index,
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def _collect_figures_for_file(
|
|
887
|
+
filename,
|
|
888
|
+
plots,
|
|
889
|
+
args,
|
|
890
|
+
out_dir,
|
|
891
|
+
base_stem,
|
|
892
|
+
save_pngs,
|
|
893
|
+
dpi,
|
|
894
|
+
):
|
|
895
|
+
"""Generate selected plots for a single sim_telarray file.
|
|
896
|
+
|
|
897
|
+
Returns a list of figures. If ``save_pngs`` is True, also writes PNGs to ``out_dir``.
|
|
898
|
+
"""
|
|
899
|
+
figures = []
|
|
900
|
+
|
|
901
|
+
def add(fig, tag):
|
|
902
|
+
if fig is not None:
|
|
903
|
+
figures.append(fig)
|
|
904
|
+
if save_pngs:
|
|
905
|
+
base_path = out_dir / f"{base_stem}_{tag}"
|
|
906
|
+
try:
|
|
907
|
+
save_figure(fig, base_path, figure_format=["png"], dpi=int(dpi))
|
|
908
|
+
except Exception as ex: # pylint:disable=broad-except
|
|
909
|
+
_logger.warning("Failed to save PNG %s: %s", base_path.with_suffix(".png"), ex)
|
|
910
|
+
else:
|
|
911
|
+
_logger.warning("Plot '%s' returned no figure for %s", tag, filename)
|
|
912
|
+
|
|
913
|
+
plots_to_run = (
|
|
914
|
+
[
|
|
915
|
+
"event_image",
|
|
916
|
+
"time_traces",
|
|
917
|
+
"waveform_matrix",
|
|
918
|
+
"step_traces",
|
|
919
|
+
"integrated_signal_image",
|
|
920
|
+
"integrated_pedestal_image",
|
|
921
|
+
"peak_timing",
|
|
922
|
+
]
|
|
923
|
+
if "all" in plots
|
|
924
|
+
else list(plots)
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
dispatch = {
|
|
928
|
+
"event_image": (
|
|
929
|
+
plot_simtel_event_image,
|
|
930
|
+
{"distance": None, "event_index": None},
|
|
931
|
+
),
|
|
932
|
+
"time_traces": (
|
|
933
|
+
plot_simtel_time_traces,
|
|
934
|
+
{"tel_id": None, "n_pixels": 3, "event_index": None},
|
|
935
|
+
),
|
|
936
|
+
"waveform_matrix": (
|
|
937
|
+
plot_simtel_waveform_matrix,
|
|
938
|
+
{"tel_id": None, "vmax": None, "event_index": None},
|
|
939
|
+
),
|
|
940
|
+
"step_traces": (
|
|
941
|
+
plot_simtel_step_traces,
|
|
942
|
+
{"tel_id": None, "pixel_step": None, "max_pixels": None, "event_index": None},
|
|
943
|
+
),
|
|
944
|
+
"integrated_signal_image": (
|
|
945
|
+
plot_simtel_integrated_signal_image,
|
|
946
|
+
{"tel_id": None, "half_width": 8, "event_index": None},
|
|
947
|
+
),
|
|
948
|
+
"integrated_pedestal_image": (
|
|
949
|
+
plot_simtel_integrated_pedestal_image,
|
|
950
|
+
{"tel_id": None, "half_width": 8, "offset": 16, "event_index": None},
|
|
951
|
+
),
|
|
952
|
+
"peak_timing": (
|
|
953
|
+
_call_peak_timing,
|
|
954
|
+
{
|
|
955
|
+
"tel_id": None,
|
|
956
|
+
"sum_threshold": 10.0,
|
|
957
|
+
"peak_width": 8,
|
|
958
|
+
"examples": 3,
|
|
959
|
+
"timing_bins": None,
|
|
960
|
+
"event_index": None,
|
|
961
|
+
},
|
|
962
|
+
),
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
for plot_name in plots_to_run:
|
|
966
|
+
entry = dispatch.get(plot_name)
|
|
967
|
+
if entry is None:
|
|
968
|
+
_logger.warning("Unknown plot selection '%s'", plot_name)
|
|
969
|
+
continue
|
|
970
|
+
func, defaults = entry
|
|
971
|
+
kwargs = {k: args.get(k, v) for k, v in defaults.items()}
|
|
972
|
+
fig = func(filename, **kwargs) # type: ignore[misc]
|
|
973
|
+
add(fig, plot_name)
|
|
974
|
+
|
|
975
|
+
return figures
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def generate_and_save_plots(
|
|
979
|
+
simtel_files,
|
|
980
|
+
plots,
|
|
981
|
+
args,
|
|
982
|
+
ioh,
|
|
983
|
+
):
|
|
984
|
+
"""Generate plots for files and save a multi-page PDF per input.
|
|
985
|
+
|
|
986
|
+
Also writes metadata JSON next to the PDF.
|
|
987
|
+
"""
|
|
988
|
+
for simtel in simtel_files:
|
|
989
|
+
out_dir, pdf_path = _make_output_paths(ioh, args.get("output_file"), simtel)
|
|
990
|
+
figures = _collect_figures_for_file(
|
|
991
|
+
filename=simtel,
|
|
992
|
+
plots=plots,
|
|
993
|
+
args=args,
|
|
994
|
+
out_dir=out_dir,
|
|
995
|
+
base_stem=simtel.stem,
|
|
996
|
+
save_pngs=bool(args.get("save_pngs", False)),
|
|
997
|
+
dpi=int(args.get("dpi", 300)),
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
if not figures:
|
|
1001
|
+
_logger.warning("No figures produced for %s", simtel)
|
|
1002
|
+
continue
|
|
1003
|
+
|
|
1004
|
+
try:
|
|
1005
|
+
save_figs_to_pdf(figures, pdf_path)
|
|
1006
|
+
_logger.info("Saved PDF: %s", pdf_path)
|
|
1007
|
+
except Exception as ex: # pylint:disable=broad-except
|
|
1008
|
+
_logger.error("Failed to save PDF %s: %s", pdf_path, ex)
|
|
1009
|
+
|
|
1010
|
+
try:
|
|
1011
|
+
MetadataCollector.dump(args, pdf_path, add_activity_name=True)
|
|
1012
|
+
except Exception as ex: # pylint:disable=broad-except
|
|
1013
|
+
_logger.warning("Failed to write metadata for %s: %s", pdf_path, ex)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import logging
|
|
5
5
|
import re
|
|
6
6
|
from collections import OrderedDict
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
import astropy.units as u
|
|
9
10
|
import matplotlib.pyplot as plt
|
|
@@ -641,9 +642,6 @@ def save_figure(fig, output_file, figure_format=None, log_title="", dpi="figure"
|
|
|
641
642
|
title: str
|
|
642
643
|
Title of the figure to be added to the log message.
|
|
643
644
|
"""
|
|
644
|
-
# pylint: disable=import-outside-toplevel
|
|
645
|
-
from pathlib import Path
|
|
646
|
-
|
|
647
645
|
figure_format = figure_format or ["pdf", "png"]
|
|
648
646
|
for fmt in figure_format:
|
|
649
647
|
_file = Path(output_file).with_suffix(f".{fmt}")
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
|
-
|
|
3
|
-
r"""
|
|
4
|
-
Calculates array or single-telescope trigger rates.
|
|
5
|
-
|
|
6
|
-
The applications reads from a sim_telarray output file, a list of
|
|
7
|
-
sim_telarray output files ou from a file containing a list of sim_telarray files.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Command line arguments
|
|
11
|
-
----------------------
|
|
12
|
-
simtel_file_names (str or list):
|
|
13
|
-
Path to the sim_telarray file or a list of sim_telarray output files.
|
|
14
|
-
Files can be generated in `simulate_prod` using the ``--save_file_lists`` option.
|
|
15
|
-
save_tables (bool):
|
|
16
|
-
If true, save the tables with the energy-dependent trigger rate to a ecsv file.
|
|
17
|
-
area_from_distribution (bool):
|
|
18
|
-
If true, the area thrown (the area in which the simulated events are distributed)
|
|
19
|
-
in the trigger rate calculation is estimated based on the event distribution.
|
|
20
|
-
The expected shape of the distribution of events as function of the core distance is triangular
|
|
21
|
-
up to the maximum distance. The weighted mean radius of the triangular distribution is 2/3 times
|
|
22
|
-
the upper edge. Therefore, when using the ``area_from_distribution`` flag, the mean distance
|
|
23
|
-
times 3/2, returns just the position of the upper edge in the triangle distribution with little
|
|
24
|
-
impact of the binning and little dependence on the scatter area defined in the simulation.
|
|
25
|
-
This is special useful when calculating trigger rate for individual telescopes.
|
|
26
|
-
If false, the area thrown is estimated based on the maximum distance as given in
|
|
27
|
-
the simulation configuration.
|
|
28
|
-
|
|
29
|
-
Example
|
|
30
|
-
-------
|
|
31
|
-
Calculate trigger rate from sim_telarray file
|
|
32
|
-
|
|
33
|
-
.. code-block:: console
|
|
34
|
-
|
|
35
|
-
simtools-calculate-trigger-rate --simtel_file_names tests/resources/ \\
|
|
36
|
-
run201_proton_za20deg_azm0deg_North_test_layout_test-prod.simtel.zst
|
|
37
|
-
|
|
38
|
-
Expected final print-out message:
|
|
39
|
-
|
|
40
|
-
.. code-block:: console
|
|
41
|
-
|
|
42
|
-
System trigger rate (Hz): 9.0064e+03 pm 9.0087e+03 Hz
|
|
43
|
-
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
import logging
|
|
47
|
-
from pathlib import Path
|
|
48
|
-
|
|
49
|
-
import simtools.utils.general as gen
|
|
50
|
-
from simtools.configuration import configurator
|
|
51
|
-
from simtools.io import io_handler
|
|
52
|
-
from simtools.simtel.simtel_io_histograms import SimtelIOHistograms
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _parse(label, description):
|
|
56
|
-
"""
|
|
57
|
-
Parse command line configuration.
|
|
58
|
-
|
|
59
|
-
Parameters
|
|
60
|
-
----------
|
|
61
|
-
label: str
|
|
62
|
-
Label describing the application.
|
|
63
|
-
description: str
|
|
64
|
-
Description of the application.
|
|
65
|
-
|
|
66
|
-
Returns
|
|
67
|
-
-------
|
|
68
|
-
CommandLineParser
|
|
69
|
-
Command line parser object
|
|
70
|
-
|
|
71
|
-
"""
|
|
72
|
-
config = configurator.Configurator(label=label, description=description)
|
|
73
|
-
|
|
74
|
-
config.parser.add_argument(
|
|
75
|
-
"--simtel_file_names",
|
|
76
|
-
help="Name of the sim_telarray output files to be calculate the trigger rate from or the "
|
|
77
|
-
"text file containing the list of sim_telarray output files.",
|
|
78
|
-
nargs="+",
|
|
79
|
-
required=True,
|
|
80
|
-
type=str,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
config.parser.add_argument(
|
|
84
|
-
"--save_tables",
|
|
85
|
-
help="Save trigger rates per energy bin into ECSV files.",
|
|
86
|
-
action="store_true",
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
config.parser.add_argument(
|
|
90
|
-
"--area_from_distribution",
|
|
91
|
-
help="Calculate trigger rates using the event distribution.",
|
|
92
|
-
action="store_true",
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
config.parser.add_argument(
|
|
96
|
-
"--stack_files",
|
|
97
|
-
help="Stacks all histograms.",
|
|
98
|
-
action="store_true",
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
config_parser, _ = config.initialize(
|
|
102
|
-
db_config=False,
|
|
103
|
-
paths=True,
|
|
104
|
-
simulation_configuration={"corsika_configuration": ["energy_range", "view_cone"]},
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
return config_parser
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _get_simulation_parameters(config_parser):
|
|
111
|
-
"""
|
|
112
|
-
Get energy range and view cone in the correct form to use in the simtel classes.
|
|
113
|
-
|
|
114
|
-
Parameters
|
|
115
|
-
----------
|
|
116
|
-
CommandLineParser:
|
|
117
|
-
Command line parser object as defined by the _parse function.
|
|
118
|
-
|
|
119
|
-
Returns
|
|
120
|
-
-------
|
|
121
|
-
list:
|
|
122
|
-
The energy range used in the simulation.
|
|
123
|
-
list:
|
|
124
|
-
The view cone used in the simulation.
|
|
125
|
-
|
|
126
|
-
"""
|
|
127
|
-
|
|
128
|
-
def convert(param, unit):
|
|
129
|
-
return [param[0].to(unit).value, param[1].to(unit).value] if param else None
|
|
130
|
-
|
|
131
|
-
return convert(config_parser.get("energy_range"), "TeV"), convert(
|
|
132
|
-
config_parser.get("view_cone"), "deg"
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def main(): # noqa: D103
|
|
137
|
-
label = Path(__file__).stem
|
|
138
|
-
description = (
|
|
139
|
-
"Calculates the simulated and triggered event rate based on sim_telarray output files."
|
|
140
|
-
)
|
|
141
|
-
config_parser = _parse(label, description)
|
|
142
|
-
|
|
143
|
-
logger = logging.getLogger()
|
|
144
|
-
logger.setLevel(gen.get_log_level_from_user(config_parser["log_level"]))
|
|
145
|
-
|
|
146
|
-
sim_telarray_files = gen.get_list_of_files_from_command_line(
|
|
147
|
-
config_parser["simtel_file_names"], [".zst", ".simtel", ".hdata"]
|
|
148
|
-
)
|
|
149
|
-
energy_range, view_cone = _get_simulation_parameters(config_parser)
|
|
150
|
-
|
|
151
|
-
histograms = SimtelIOHistograms(
|
|
152
|
-
sim_telarray_files,
|
|
153
|
-
area_from_distribution=config_parser["area_from_distribution"],
|
|
154
|
-
energy_range=energy_range,
|
|
155
|
-
view_cone=view_cone,
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
logger.info("Calculating simulated and triggered event rate")
|
|
159
|
-
(
|
|
160
|
-
sim_event_rates,
|
|
161
|
-
triggered_event_rates,
|
|
162
|
-
triggered_event_rate_uncertainties,
|
|
163
|
-
trigger_rate_in_tables,
|
|
164
|
-
) = histograms.calculate_trigger_rates(
|
|
165
|
-
print_info=True, stack_files=config_parser["stack_files"]
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
# Print out results
|
|
169
|
-
for i_hist, _ in enumerate(sim_event_rates):
|
|
170
|
-
print(f"\nFile {histograms.histogram_files[i_hist]}\n")
|
|
171
|
-
print(
|
|
172
|
-
f"System trigger rate (Hz): {triggered_event_rates[i_hist].value:.4e} \u00b1 "
|
|
173
|
-
f"{triggered_event_rate_uncertainties[i_hist].value:.4e} Hz"
|
|
174
|
-
)
|
|
175
|
-
if config_parser["save_tables"]:
|
|
176
|
-
io_handler_instance = io_handler.IOHandler()
|
|
177
|
-
output_path = io_handler_instance.get_output_directory(label, sub_dir="application-plots")
|
|
178
|
-
for i_table, table in enumerate(trigger_rate_in_tables):
|
|
179
|
-
output_file = (
|
|
180
|
-
str(output_path.joinpath(Path(sim_telarray_files[i_table]).stem)) + ".ecsv"
|
|
181
|
-
)
|
|
182
|
-
logger.info(f"Writing table {i_table + 1} to {output_file}")
|
|
183
|
-
table.write(output_file, overwrite=True)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if __name__ == "__main__":
|
|
187
|
-
main()
|