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.
Files changed (59) hide show
  1. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/METADATA +1 -3
  2. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/RECORD +54 -51
  3. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/entry_points.txt +3 -3
  4. simtools/_version.py +2 -2
  5. simtools/applications/calculate_incident_angles.py +182 -0
  6. simtools/applications/db_add_simulation_model_from_repository_to_db.py +17 -14
  7. simtools/applications/db_add_value_from_json_to_db.py +6 -9
  8. simtools/applications/db_generate_compound_indexes.py +7 -3
  9. simtools/applications/db_get_file_from_db.py +11 -23
  10. simtools/applications/derive_psf_parameters.py +58 -39
  11. simtools/applications/derive_trigger_rates.py +91 -0
  12. simtools/applications/generate_corsika_histograms.py +7 -184
  13. simtools/applications/maintain_simulation_model_add_production.py +105 -0
  14. simtools/applications/plot_simtel_events.py +5 -189
  15. simtools/applications/print_version.py +8 -7
  16. simtools/applications/validate_file_using_schema.py +7 -4
  17. simtools/configuration/commandline_parser.py +17 -11
  18. simtools/corsika/corsika_histograms.py +81 -0
  19. simtools/data_model/validate_data.py +8 -3
  20. simtools/db/db_handler.py +122 -31
  21. simtools/db/db_model_upload.py +51 -30
  22. simtools/dependencies.py +10 -5
  23. simtools/layout/array_layout_utils.py +37 -5
  24. simtools/model/array_model.py +18 -1
  25. simtools/model/model_repository.py +118 -63
  26. simtools/model/site_model.py +25 -0
  27. simtools/production_configuration/derive_corsika_limits.py +9 -34
  28. simtools/ray_tracing/incident_angles.py +706 -0
  29. simtools/ray_tracing/psf_parameter_optimisation.py +999 -565
  30. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -2
  31. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +1 -1
  32. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +22 -29
  33. simtools/schemas/model_parameters/stars.schema.yml +1 -1
  34. simtools/schemas/production_tables.schema.yml +5 -0
  35. simtools/simtel/simtel_config_writer.py +18 -20
  36. simtools/simtel/simtel_io_event_histograms.py +253 -516
  37. simtools/simtel/simtel_io_event_reader.py +51 -2
  38. simtools/simtel/simtel_io_event_writer.py +31 -11
  39. simtools/simtel/simtel_io_metadata.py +1 -1
  40. simtools/simtel/simtel_table_reader.py +3 -3
  41. simtools/simulator.py +1 -4
  42. simtools/telescope_trigger_rates.py +119 -0
  43. simtools/testing/log_inspector.py +13 -11
  44. simtools/utils/geometry.py +20 -0
  45. simtools/version.py +89 -0
  46. simtools/{corsika/corsika_histograms_visualize.py → visualization/plot_corsika_histograms.py} +109 -0
  47. simtools/visualization/plot_incident_angles.py +431 -0
  48. simtools/visualization/plot_psf.py +673 -0
  49. simtools/visualization/plot_simtel_event_histograms.py +376 -0
  50. simtools/visualization/{simtel_event_plots.py → plot_simtel_events.py} +284 -87
  51. simtools/visualization/visualize.py +1 -3
  52. simtools/applications/calculate_trigger_rate.py +0 -187
  53. simtools/applications/generate_sim_telarray_histograms.py +0 -196
  54. simtools/applications/maintain_simulation_model_add_production_table.py +0 -71
  55. simtools/simtel/simtel_io_histogram.py +0 -623
  56. simtools/simtel/simtel_io_histograms.py +0 -556
  57. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/WHEEL +0 -0
  58. {gammasimtools-0.19.0.dist-info → gammasimtools-0.21.0.dist-info}/licenses/LICENSE +0 -0
  59. {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
- def _compute_integration_window(
34
- peak_idx: int, n_samp: int, half_width: int, mode: str, offset: int | None
35
- ) -> tuple[int, int]:
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
- source = EventSource(filename, max_events=None)
142
- event = _select_event_by_type(source)(event_index=event_index)
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 = tel_ids[0]
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: int | None = None,
202
- n_pixels: int = 3,
203
- event_index: int | None = None,
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
- source = EventSource(filename, max_events=None)
225
- event = _select_event_by_type(source)(event_index=event_index)
226
-
227
- r1_tel_ids = sorted(getattr(event.r1, "tel", {}).keys())
228
- if r1_tel_ids:
229
- tel_id = tel_id or r1_tel_ids[0]
230
- else:
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: int | None = None,
277
- vmax: float | None = None,
278
- event_index: int | None = None,
279
- pixel_step: int | None = None,
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
- source = EventSource(filename, max_events=None)
303
- event = _select_event_by_type(source)(event_index=event_index)
304
-
305
- r1_tel_ids = sorted(getattr(event.r1, "tel", {}).keys())
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: int | None = None,
346
- pixel_step: int = 100,
347
- max_pixels: int | None = None,
348
- event_index: int | None = None,
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
- source = EventSource(filename, max_events=None)
372
- event = _select_event_by_type(source)(event_index=event_index)
373
-
374
- r1_tel_ids = sorted(getattr(event.r1, "tel", {}).keys())
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: int | None = None,
587
- sum_threshold: float = 10.0,
588
- peak_width: int = 8,
589
- examples: int = 3,
590
- timing_bins: int | None = None,
591
- return_stats: bool = False,
592
- event_index: int | None = None,
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
- source = EventSource(filename, max_events=None)
624
- event = _select_event_by_type(source)(event_index=event_index)
625
-
626
- r1_tel_ids = sorted(getattr(event.r1, "tel", {}).keys())
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
- source = EventSource(filename, max_events=None)
722
- event = _select_event_by_type(source)(event_index=event_index)
723
-
724
- r1_tel_ids = sorted(getattr(event.r1, "tel", {}).keys())
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: int | None = None,
746
- half_width: int = 8,
747
- event_index: int | None = None,
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: int | None = None,
762
- half_width: int = 8,
763
- offset: int = 16,
764
- event_index: int | None = None,
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: int | None,
780
- half_width: int,
781
- event_index: int | None,
782
- mode: str,
783
- offset: int | None = None,
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()