gammasimtools 0.24.0__py3-none-any.whl → 0.26.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 (138) hide show
  1. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
  2. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +134 -130
  3. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +3 -1
  4. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
  5. simtools/_version.py +2 -2
  6. simtools/application_control.py +78 -0
  7. simtools/applications/calculate_incident_angles.py +0 -2
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -2
  9. simtools/applications/db_add_file_to_db.py +1 -1
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -1
  12. simtools/applications/db_generate_compound_indexes.py +1 -1
  13. simtools/applications/db_get_array_layouts_from_db.py +2 -6
  14. simtools/applications/db_get_file_from_db.py +1 -1
  15. simtools/applications/db_get_parameter_from_db.py +1 -1
  16. simtools/applications/db_inspect_databases.py +1 -1
  17. simtools/applications/db_upload_model_repository.py +1 -1
  18. simtools/applications/derive_ctao_array_layouts.py +1 -2
  19. simtools/applications/derive_mirror_rnda.py +1 -3
  20. simtools/applications/derive_psf_parameters.py +5 -1
  21. simtools/applications/derive_pulse_shape_parameters.py +194 -0
  22. simtools/applications/derive_trigger_rates.py +1 -1
  23. simtools/applications/docs_produce_array_element_report.py +2 -8
  24. simtools/applications/docs_produce_calibration_reports.py +1 -3
  25. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  26. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  27. simtools/applications/generate_array_config.py +0 -1
  28. simtools/applications/generate_corsika_histograms.py +48 -235
  29. simtools/applications/generate_regular_arrays.py +5 -35
  30. simtools/applications/generate_simtel_event_data.py +2 -2
  31. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  32. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  33. simtools/applications/plot_array_layout.py +64 -108
  34. simtools/applications/plot_simulated_event_distributions.py +57 -0
  35. simtools/applications/plot_tabular_data.py +0 -1
  36. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  37. simtools/applications/production_derive_corsika_limits.py +1 -1
  38. simtools/applications/production_generate_grid.py +0 -1
  39. simtools/applications/run_application.py +1 -1
  40. simtools/applications/simulate_flasher.py +3 -4
  41. simtools/applications/simulate_illuminator.py +0 -1
  42. simtools/applications/simulate_pedestals.py +2 -6
  43. simtools/applications/simulate_prod.py +9 -28
  44. simtools/applications/simulate_prod_htcondor_generator.py +8 -1
  45. simtools/applications/submit_array_layouts.py +7 -7
  46. simtools/applications/submit_model_parameter_from_external.py +1 -3
  47. simtools/applications/validate_camera_efficiency.py +0 -1
  48. simtools/applications/validate_camera_fov.py +0 -1
  49. simtools/applications/validate_cumulative_psf.py +0 -2
  50. simtools/applications/validate_file_using_schema.py +49 -123
  51. simtools/applications/validate_optics.py +0 -13
  52. simtools/camera/camera_efficiency.py +1 -6
  53. simtools/camera/single_photon_electron_spectrum.py +2 -1
  54. simtools/configuration/commandline_parser.py +43 -8
  55. simtools/configuration/configurator.py +6 -11
  56. simtools/corsika/corsika_config.py +204 -99
  57. simtools/corsika/corsika_histograms.py +411 -1735
  58. simtools/corsika/primary_particle.py +1 -1
  59. simtools/data_model/metadata_collector.py +5 -2
  60. simtools/data_model/metadata_model.py +0 -4
  61. simtools/data_model/model_data_writer.py +27 -17
  62. simtools/data_model/schema.py +112 -5
  63. simtools/data_model/validate_data.py +80 -48
  64. simtools/db/db_handler.py +19 -8
  65. simtools/db/db_model_upload.py +2 -1
  66. simtools/db/mongo_db.py +133 -42
  67. simtools/dependencies.py +83 -44
  68. simtools/io/ascii_handler.py +4 -2
  69. simtools/io/table_handler.py +1 -1
  70. simtools/job_execution/htcondor_script_generator.py +0 -2
  71. simtools/layout/array_layout.py +4 -12
  72. simtools/layout/array_layout_utils.py +227 -58
  73. simtools/model/array_model.py +37 -18
  74. simtools/model/calibration_model.py +0 -4
  75. simtools/model/legacy_model_parameter.py +134 -0
  76. simtools/model/model_parameter.py +24 -14
  77. simtools/model/model_repository.py +18 -5
  78. simtools/model/model_utils.py +1 -6
  79. simtools/model/site_model.py +0 -4
  80. simtools/model/telescope_model.py +6 -11
  81. simtools/production_configuration/derive_corsika_limits.py +6 -11
  82. simtools/production_configuration/interpolation_handler.py +16 -16
  83. simtools/ray_tracing/incident_angles.py +5 -11
  84. simtools/ray_tracing/mirror_panel_psf.py +3 -7
  85. simtools/ray_tracing/psf_analysis.py +29 -27
  86. simtools/ray_tracing/psf_parameter_optimisation.py +822 -680
  87. simtools/ray_tracing/ray_tracing.py +6 -15
  88. simtools/reporting/docs_auto_report_generator.py +8 -13
  89. simtools/reporting/docs_read_parameters.py +70 -16
  90. simtools/runners/corsika_runner.py +15 -10
  91. simtools/runners/corsika_simtel_runner.py +9 -8
  92. simtools/runners/runner_services.py +17 -7
  93. simtools/runners/simtel_runner.py +11 -58
  94. simtools/runners/simtools_runner.py +2 -4
  95. simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
  96. simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
  97. simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
  98. simtools/schemas/simulation_models_info.schema.yml +2 -0
  99. simtools/settings.py +154 -0
  100. simtools/sim_events/file_info.py +128 -0
  101. simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
  102. simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
  103. simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
  104. simtools/simtel/pulse_shapes.py +273 -0
  105. simtools/simtel/simtel_config_writer.py +146 -22
  106. simtools/simtel/simtel_table_reader.py +6 -4
  107. simtools/simtel/simulator_array.py +62 -23
  108. simtools/simtel/simulator_camera_efficiency.py +4 -6
  109. simtools/simtel/simulator_light_emission.py +101 -19
  110. simtools/simtel/simulator_ray_tracing.py +4 -10
  111. simtools/simulator.py +360 -353
  112. simtools/telescope_trigger_rates.py +3 -4
  113. simtools/testing/assertions.py +115 -8
  114. simtools/testing/configuration.py +2 -3
  115. simtools/testing/helpers.py +2 -3
  116. simtools/testing/log_inspector.py +5 -1
  117. simtools/testing/sim_telarray_metadata.py +1 -1
  118. simtools/testing/validate_output.py +69 -23
  119. simtools/utils/general.py +37 -0
  120. simtools/utils/geometry.py +0 -77
  121. simtools/utils/names.py +7 -9
  122. simtools/version.py +37 -0
  123. simtools/visualization/legend_handlers.py +21 -10
  124. simtools/visualization/plot_array_layout.py +312 -41
  125. simtools/visualization/plot_corsika_histograms.py +143 -605
  126. simtools/visualization/plot_mirrors.py +834 -0
  127. simtools/visualization/plot_pixels.py +2 -4
  128. simtools/visualization/plot_psf.py +0 -1
  129. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  130. simtools/visualization/plot_simtel_events.py +6 -11
  131. simtools/visualization/plot_tables.py +8 -19
  132. simtools/visualization/visualize.py +22 -2
  133. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  134. simtools/applications/print_version.py +0 -53
  135. simtools/io/hdf5_handler.py +0 -139
  136. simtools/simtel/simtel_io_file_info.py +0 -62
  137. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
  138. {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
@@ -1,680 +1,218 @@
1
1
  """Visualize Cherenkov photon distributions from CORSIKA."""
2
2
 
3
- import logging
4
- import re
5
3
  from pathlib import Path
6
4
 
7
5
  import matplotlib.pyplot as plt
8
6
  import numpy as np
9
7
  from astropy import units as u
10
- from matplotlib import colors
11
- from matplotlib.backends.backend_pdf import PdfPages
8
+ from matplotlib import colormaps, colors
12
9
 
13
- _logger = logging.getLogger(__name__)
10
+ from simtools.visualization.visualize import save_figures_to_single_document
14
11
 
15
12
 
16
- def _kernel_plot_2d_photons(histograms_instance, property_name, log_z=False):
13
+ def _plot_2d(hist_list, labels=None):
17
14
  """
18
- Provide helper functions for plotting Cherenkov photon distributions saved in CorsikaHistograms.
19
-
20
- Create the figure of a 2D plot. The parameter name indicate which plot.
21
- Choices are "counts", "density", "direction", "time_altitude", and "num_photons_per_telescope".
15
+ Plot 2D Cherenkov photon distributions.
22
16
 
23
17
  Parameters
24
18
  ----------
25
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
26
- instance of corsika.corsika_histograms.CorsikaHistograms.
27
- property_name: string
28
- Name of the quantity. Options are: "counts", "density", "direction", "time_altitude" and
29
- "num_photons_per_telescope".
30
- log_z: bool
31
- if True, the intensity of the color bar is given in logarithmic scale.
19
+ hist_list: list
20
+ List of histogram dictionaries.
21
+ labels: list or None
22
+ Optional list of labels for the input files. If None, uses file names.
32
23
 
33
24
  Returns
34
25
  -------
35
26
  list
36
- List of figures for the given telescopes.
37
-
38
- Raises
39
- ------
40
- ValueError
41
- if property is not allowed.
27
+ List of figures.
42
28
  """
43
- if property_name not in histograms_instance.dict_2d_distributions:
44
- msg = (
45
- f"This property does not exist. The valid entries are "
46
- f"{histograms_instance.dict_2d_distributions}"
47
- )
48
- _logger.error(msg)
49
- raise ValueError(msg)
50
- _function = getattr(
51
- histograms_instance,
52
- histograms_instance.dict_2d_distributions[property_name]["function"],
53
- )
54
- hist_values, x_bin_edges, y_bin_edges = _function()
29
+ if not hist_list:
30
+ return []
55
31
 
56
32
  all_figs = []
57
- for i_hist, _ in enumerate(x_bin_edges):
58
- fig, ax = plt.subplots()
59
- if log_z is True:
60
- norm = colors.LogNorm(vmin=1, vmax=np.amax([np.amax(hist_values[i_hist]), 2]))
61
- else:
62
- norm = None
63
- mesh = ax.pcolormesh(
64
- x_bin_edges[i_hist], y_bin_edges[i_hist], hist_values[i_hist], norm=norm
65
- )
66
- if (
67
- histograms_instance.dict_2d_distributions[property_name]["x axis unit"]
68
- is not u.dimensionless_unscaled
69
- ):
70
- ax.set_xlabel(
71
- f"{histograms_instance.dict_2d_distributions[property_name]['x bin edges']} "
72
- f"({histograms_instance.dict_2d_distributions[property_name]['x axis unit']})"
73
- )
74
- else:
75
- ax.set_xlabel(
76
- f"{histograms_instance.dict_2d_distributions[property_name]['x bin edges']} "
77
- )
78
- if (
79
- histograms_instance.dict_2d_distributions[property_name]["y axis unit"]
80
- is not u.dimensionless_unscaled
81
- ):
82
- ax.set_ylabel(
83
- f"{histograms_instance.dict_2d_distributions[property_name]['y bin edges']} "
84
- f"({histograms_instance.dict_2d_distributions[property_name]['y axis unit']})"
85
- )
86
- else:
87
- ax.set_ylabel(
88
- f"{histograms_instance.dict_2d_distributions[property_name]['y bin edges']} "
89
- )
90
- ax.set_xlim(np.amin(x_bin_edges[i_hist]), np.amax(x_bin_edges[i_hist]))
91
- ax.set_ylim(np.amin(y_bin_edges[i_hist]), np.amax(y_bin_edges[i_hist]))
92
- ax.set_facecolor("black")
93
- fig.colorbar(mesh)
94
- all_figs.append(fig)
95
- if histograms_instance.individual_telescopes is False:
96
- ax.set_title(
97
- f"{histograms_instance.dict_2d_distributions[property_name]['file name']}_all_tels"
98
- )
99
- else:
100
- ax.text(
101
- 0.99,
102
- 0.99,
103
- "tel. " + str(i_hist),
104
- ha="right",
105
- va="top",
106
- transform=ax.transAxes,
107
- color="white",
108
- )
109
- ax.set_title(
110
- f"{histograms_instance.dict_2d_distributions[property_name]['file name']}"
111
- f"_tel_index_{histograms_instance.telescope_indices[i_hist]}",
112
- )
113
- plt.close()
114
-
115
- return all_figs
116
-
117
-
118
- def plot_2d_counts(histograms_instance, log_z=True):
119
- """
120
- Plot the 2D histogram of the photon positions on the ground.
121
-
122
- Parameters
123
- ----------
124
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
125
- instance of corsika.corsika_histograms.CorsikaHistograms.
126
- log_z: bool
127
- if True, the intensity of the color bar is given in logarithmic scale.
128
-
129
- Returns
130
- -------
131
- list
132
- List of figures for the given telescopes.
133
- """
134
- return _kernel_plot_2d_photons(histograms_instance, "counts", log_z=log_z)
135
-
136
-
137
- def plot_2d_density(histograms_instance, log_z=True):
138
- """
139
- Plot the 2D histogram of the photon density distribution on the ground.
140
-
141
- Parameters
142
- ----------
143
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
144
- instance of corsika.corsika_histograms.CorsikaHistograms.
145
- log_z: bool
146
- if True, the intensity of the color bar is given in logarithmic scale.
147
-
148
- Returns
149
- -------
150
- list
151
- List of figures for the given telescopes.
152
33
 
153
- """
154
- return _kernel_plot_2d_photons(histograms_instance, "density", log_z=log_z)
155
-
156
-
157
- def plot_2d_direction(histograms_instance, log_z=True):
158
- """
159
- Plot the 2D histogram of the incoming direction of photons.
160
-
161
- Parameters
162
- ----------
163
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
164
- instance of corsika.corsika_histograms.CorsikaHistograms.
165
- log_z: bool
166
- if True, the intensity of the color bar is given in logarithmic scale.
167
-
168
- Returns
169
- -------
170
- list
171
- List of figures for the given telescopes.
172
-
173
- """
174
- return _kernel_plot_2d_photons(histograms_instance, "direction", log_z=log_z)
175
-
176
-
177
- def plot_2d_time_altitude(histograms_instance, log_z=True):
178
- """
179
- Plot the 2D histogram of the time and altitude where the photon was produced.
180
-
181
- Parameters
182
- ----------
183
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
184
- instance of corsika.corsika_histograms.CorsikaHistograms.
185
- log_z: bool
186
- if True, the intensity of the color bar is given in logarithmic scale.
187
-
188
- Returns
189
- -------
190
- list
191
- List of figures for the given telescopes.
192
-
193
- """
194
- return _kernel_plot_2d_photons(histograms_instance, "time_altitude", log_z=log_z)
195
-
196
-
197
- def plot_2d_num_photons_per_telescope(histograms_instance, log_z=True):
198
- """
199
- Plot the 2D histogram of the number of photons per event and per telescope.
200
-
201
- Parameters
202
- ----------
203
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
204
- instance of corsika.corsika_histograms.CorsikaHistograms.
205
- log_z: bool
206
- if True, the intensity of the color bar is given in logarithmic scale.
207
-
208
- Returns
209
- -------
210
- list
211
- List of figures for the given telescopes.
212
-
213
- """
214
- return _kernel_plot_2d_photons(histograms_instance, "num_photons_per_telescope", log_z=log_z)
215
-
216
-
217
- def _kernel_plot_1d_photons(histograms_instance, property_name, log_y=True):
218
- """
219
- Create the figure of a 1D plot. The parameter property indicate which plot.
220
-
221
- Parameters
222
- ----------
223
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
224
- instance of corsika.corsika_histograms.CorsikaHistograms.
225
- property_name: string
226
- Name of the quantity. Choices are
227
- "counts", "density", "direction", "time", "altitude", "num_photons_per_event", and
228
- "num_photons_per_telescope".
229
- log_y: bool
230
- if True, the intensity of the Y axis is given in logarithmic scale.
231
-
232
- Returns
233
- -------
234
- list
235
- List of figures for the given telescopes.
236
-
237
- Raises
238
- ------
239
- ValueError
240
- if property is not allowed.
241
- """
242
- if property_name not in histograms_instance.dict_1d_distributions:
243
- msg = (
244
- f"This property does not exist. The valid entries are "
245
- f"{histograms_instance.dict_1d_distributions}"
246
- )
247
- _logger.error(msg)
248
- raise ValueError(msg)
249
-
250
- _function = getattr(
251
- histograms_instance,
252
- histograms_instance.dict_1d_distributions[property_name]["function"],
253
- )
254
- hist_values, bin_edges = _function()
255
- all_figs = []
256
- for i_hist, _ in enumerate(bin_edges):
257
- fig, ax = plt.subplots()
258
- ax.bar(
259
- bin_edges[i_hist][:-1],
260
- hist_values[i_hist],
261
- align="edge",
262
- width=np.abs(np.diff(bin_edges[i_hist])),
263
- )
264
- if (
265
- histograms_instance.dict_1d_distributions[property_name]["axis unit"]
266
- is not u.dimensionless_unscaled
267
- ):
268
- ax.set_xlabel(
269
- f"{histograms_instance.dict_1d_distributions[property_name]['bin edges']} "
270
- f"({histograms_instance.dict_1d_distributions[property_name]['axis unit']})"
271
- )
272
- else:
273
- ax.set_xlabel(
274
- f"{histograms_instance.dict_1d_distributions[property_name]['bin edges']} "
275
- )
276
- if property_name == "density":
277
- ax.set_ylabel(
278
- f"Density ({histograms_instance.dict_1d_distributions[property_name]['axis unit']}"
279
- r"$^{-2}$)"
34
+ for i_file, hist_dict in enumerate(hist_list):
35
+ hist_values = hist_dict["hist_values"]
36
+ x_bin_edges = hist_dict["x_bin_edges"]
37
+ y_bin_edges = hist_dict["y_bin_edges"]
38
+
39
+ for i_hist, _ in enumerate(x_bin_edges):
40
+ fig, ax = plt.subplots()
41
+ if hist_dict.get("log_z", False):
42
+ max_val = np.amax(hist_values[i_hist])
43
+ norm = colors.LogNorm(vmin=1, vmax=np.amax([max_val, 2]))
44
+ else:
45
+ norm = None
46
+ mesh = ax.pcolormesh(
47
+ x_bin_edges[i_hist], y_bin_edges[i_hist], hist_values[i_hist], norm=norm
280
48
  )
281
- else:
282
- ax.set_ylabel("Counts")
49
+ ax.set_xlabel(_get_axis_label(hist_dict["x_axis_title"], hist_dict["x_axis_unit"]))
50
+ ax.set_ylabel(_get_axis_label(hist_dict["y_axis_title"], hist_dict["y_axis_unit"]))
51
+ ax.set_xlim(np.amin(x_bin_edges[i_hist]), np.amax(x_bin_edges[i_hist]))
52
+ ax.set_ylim(np.amin(y_bin_edges[i_hist]), np.amax(y_bin_edges[i_hist]))
53
+ ax.set_facecolor("black")
54
+ cbar = fig.colorbar(mesh)
55
+ cbar.set_label(_get_axis_label(hist_dict["z_axis_title"], hist_dict["z_axis_unit"]))
56
+
57
+ if labels is not None and i_file < len(labels):
58
+ label = labels[i_file]
59
+ else:
60
+ label = Path(hist_dict.get("input_file_name", f"File {i_file}")).name
61
+ ax.set_title(f"{hist_dict['title']} - {label}")
62
+
63
+ all_figs.append(fig)
64
+ plt.close()
283
65
 
284
- if log_y is True:
285
- ax.set_yscale("log")
286
- if histograms_instance.individual_telescopes is False:
287
- ax.set_title(
288
- f"{histograms_instance.dict_1d_distributions[property_name]['file name']}_all_tels"
289
- )
290
- else:
291
- ax.set_title(
292
- f"{histograms_instance.dict_1d_distributions[property_name]['file name']}"
293
- f"_tel_index_{histograms_instance.telescope_indices[i_hist]}",
294
- )
295
- all_figs.append(fig)
296
- plt.close(fig)
297
66
  return all_figs
298
67
 
299
68
 
300
- def plot_wavelength_distr(histograms_instance, log_y=True):
301
- """
302
- Plot the 1D distribution of the photon wavelengths.
69
+ def _get_histogram_label(hist_dict, i_file, labels):
70
+ """Get label for histogram curve."""
71
+ if labels is not None and i_file < len(labels):
72
+ return labels[i_file]
73
+ return Path(hist_dict.get("input_file_name", f"File {i_file}")).name
303
74
 
304
- Parameters
305
- ----------
306
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
307
- instance of corsika.corsika_histograms.CorsikaHistograms.
308
- log_y: bool
309
- if True, the intensity of the Y axis is given in logarithmic scale.
310
75
 
311
- Returns
312
- -------
313
- list
314
- List of figures for the given telescopes.
315
- """
316
- return _kernel_plot_1d_photons(histograms_instance, "wavelength", log_y=log_y)
317
-
318
-
319
- def plot_counts_distr(histograms_instance, log_y=True):
320
- """
321
- Plot the 1D distribution, i.e. the radial distribution, of the photons on the ground.
322
-
323
- Parameters
324
- ----------
325
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
326
- instance of corsika.corsika_histograms.CorsikaHistograms.
327
- log_y: bool
328
- if True, the intensity of the Y axis is given in logarithmic scale.
329
-
330
- Returns
331
- -------
332
- list
333
- List of figures for the given telescopes.
334
- """
335
- return _kernel_plot_1d_photons(histograms_instance, "counts", log_y=log_y)
336
-
337
-
338
- def plot_density_distr(histograms_instance, log_y=True):
339
- """
340
- Plot the photon density distribution on the ground.
341
-
342
- Parameters
343
- ----------
344
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
345
- instance of corsika.corsika_histograms.CorsikaHistograms.
346
- log_y: bool
347
- if True, the intensity of the Y axis is given in logarithmic scale.
348
-
349
- Returns
350
- -------
351
- list
352
- List of figures for the given telescopes.
353
- """
354
- return _kernel_plot_1d_photons(histograms_instance, "density", log_y=log_y)
76
+ def _extract_uncertainty(uncertainties, i_hist):
77
+ """Extract uncertainty values if available."""
78
+ if uncertainties is not None and uncertainties[i_hist] is not None:
79
+ return uncertainties[i_hist]
80
+ return None
355
81
 
356
82
 
357
- def plot_time_distr(histograms_instance, log_y=True):
358
- """
359
- Plot the distribution times in which the photons were generated in ns.
83
+ def _plot_histogram_curve(ax, bin_centers, hist_values, uncertainties, color, label):
84
+ """Plot a single histogram curve with or without error bars."""
85
+ common_params = {
86
+ "color": color,
87
+ "label": label,
88
+ "marker": "o",
89
+ "markersize": 3,
90
+ "linestyle": "-",
91
+ "linewidth": 0.5,
92
+ }
93
+
94
+ if uncertainties is not None:
95
+ ax.errorbar(
96
+ bin_centers,
97
+ hist_values,
98
+ yerr=uncertainties,
99
+ capsize=2,
100
+ capthick=0.5,
101
+ **common_params,
102
+ )
103
+ else:
104
+ ax.plot(bin_centers, hist_values, **common_params)
360
105
 
361
- Parameters
362
- ----------
363
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
364
- instance of corsika.corsika_histograms.CorsikaHistograms.
365
- log_y: bool
366
- if True, the intensity of the Y axis is given in logarithmic scale.
367
106
 
368
- Returns
369
- -------
370
- list
371
- List of figures for the given telescopes.
372
- """
373
- return _kernel_plot_1d_photons(histograms_instance, "time", log_y=log_y)
107
+ def _configure_plot_scales(ax, hist):
108
+ """Configure x and y axis scales."""
109
+ if len(hist["x_bins"]) > 3 and hist["x_bins"][3] == "log":
110
+ ax.set_xscale("log")
111
+ if hist["log_y"] is True:
112
+ ax.set_yscale("log")
374
113
 
375
114
 
376
- def plot_altitude_distr(histograms_instance, log_y=True):
115
+ def _plot_1d(hist_list, labels=None):
377
116
  """
378
- Plot the distribution of altitude in which the photons were generated in km.
117
+ Plot 1D Cherenkov photon distributions.
379
118
 
380
119
  Parameters
381
120
  ----------
382
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
383
- instance of corsika.corsika_histograms.CorsikaHistograms.
384
- log_y: bool
385
- if True, the intensity of the Y axis is given in logarithmic scale.
121
+ hist_list: list
122
+ List of histogram dictionaries from different files.
123
+ labels: list or None
124
+ Optional list of labels for the histogram curves. If None, uses file names.
386
125
 
387
126
  Returns
388
127
  -------
389
128
  list
390
- List of figures for the given telescopes.
129
+ List of figures.
391
130
  """
392
- return _kernel_plot_1d_photons(histograms_instance, "altitude", log_y=log_y)
393
-
131
+ if not hist_list:
132
+ return []
394
133
 
395
- def plot_photon_per_event_distr(histograms_instance, log_y=True):
396
- """
397
- Plot the distribution of the number of Cherenkov photons per event.
398
-
399
- Parameters
400
- ----------
401
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
402
- instance of corsika.corsika_histograms.CorsikaHistograms.
403
- log_y: bool
404
- if True, the intensity of the Y axis is given in logarithmic scale.
405
-
406
- Returns
407
- -------
408
- list
409
- List of figures for the given telescopes.
134
+ hist = hist_list[0]
135
+ plot_colors = colormaps["tab10"](np.linspace(0, 1, len(hist_list)))
136
+ fig, ax = plt.subplots()
410
137
 
411
- """
412
- return _kernel_plot_1d_photons(histograms_instance, "num_photons_per_event", log_y=log_y)
138
+ for i_file, (hist_dict, color) in enumerate(zip(hist_list, plot_colors)):
139
+ hist_values = hist_dict["hist_values"]
140
+ x_bin_edges = hist_dict["x_bin_edges"]
141
+ uncertainties = hist_dict.get("uncertainties")
142
+ label = _get_histogram_label(hist_dict, i_file, labels)
413
143
 
144
+ for i_hist, x_edges in enumerate(x_bin_edges):
145
+ bin_centers = (x_edges[:-1] + x_edges[1:]) / 2
146
+ unc = _extract_uncertainty(uncertainties, i_hist)
147
+ _plot_histogram_curve(ax, bin_centers, hist_values[i_hist], unc, color, label)
414
148
 
415
- def plot_photon_per_telescope_distr(histograms_instance, log_y=True):
416
- """
417
- Plot the distribution of the number of Cherenkov photons per telescope.
149
+ ax.set_xlabel(_get_axis_label(hist["x_axis_title"], hist["x_axis_unit"]))
150
+ ax.set_ylabel(_get_axis_label(hist["y_axis_title"], hist["y_axis_unit"]))
151
+ _configure_plot_scales(ax, hist)
152
+ ax.set_title(f"{hist['title']}")
153
+ ax.legend()
154
+ ax.grid(True, alpha=0.3)
418
155
 
419
- Parameters
420
- ----------
421
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
422
- instance of corsika.corsika_histograms.CorsikaHistograms.
423
- log_y: bool
424
- if True, the intensity of the Y axis is given in logarithmic scale.
156
+ plt.close(fig)
157
+ return [fig]
425
158
 
426
- Returns
427
- -------
428
- list
429
- List of figures for the given telescopes.
430
159
 
431
- """
432
- return _kernel_plot_1d_photons(histograms_instance, "num_photons_per_telescope", log_y=log_y)
160
+ def _get_axis_label(title, unit):
161
+ """Return axis label with unit if applicable."""
162
+ if unit is not u.dimensionless_unscaled:
163
+ return f"{title} ({unit})"
164
+ return f"{title}"
433
165
 
434
166
 
435
- def plot_1d_event_header_distribution(
436
- histograms_instance, event_header_element, log_y=True, bins=50, hist_range=None
437
- ):
438
- """
439
- Plot the distribution of the quantity given by .
167
+ def _build_all_photon_figures(histograms_list, labels=None):
168
+ """Build list of all photon histogram figures.
440
169
 
441
170
  Parameters
442
171
  ----------
443
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
444
- instance of corsika.corsika_histograms.CorsikaHistograms.
445
- event_header_element: str
446
- The key to the CORSIKA event header element.
447
- log_y: bool
448
- if True, the intensity of the Y axis is given in logarithmic scale.
449
- bins: float
450
- Number of bins for the histogram.
451
- hist_range: 2-tuple
452
- Tuple to define the range of the histogram.
172
+ histograms_list: list
173
+ List of CorsikaHistograms instances from different input files.
174
+ labels: list or None
175
+ Optional list of labels for the input files. If None, uses file names.
453
176
 
454
177
  Returns
455
178
  -------
456
179
  list
457
- List of figures for the given telescopes.
458
-
180
+ List of figures.
459
181
  """
460
- hist_values, bin_edges = histograms_instance.event_1d_histogram(
461
- event_header_element, bins=bins, hist_range=hist_range
462
- )
463
- fig, ax = plt.subplots()
464
- ax.bar(
465
- bin_edges[:-1],
466
- hist_values,
467
- align="edge",
468
- width=np.abs(np.diff(bin_edges)),
469
- )
470
- if (
471
- histograms_instance.event_information[event_header_element].unit
472
- is not u.dimensionless_unscaled
473
- ):
474
- ax.set_xlabel(
475
- f"{event_header_element} ("
476
- f"{histograms_instance.event_information[event_header_element].unit})"
477
- )
478
- else:
479
- ax.set_xlabel(f"{event_header_element}")
480
- ax.set_ylabel("Counts")
182
+ all_figs = []
481
183
 
482
- if log_y is True:
483
- ax.set_yscale("log")
484
- ax.set_title(f"hist_1d_{event_header_element}")
485
- return fig
486
-
487
-
488
- def plot_2d_event_header_distribution(
489
- histograms_instance,
490
- event_header_element_1,
491
- event_header_element_2,
492
- log_z=True,
493
- bins=50,
494
- hist_range=None,
495
- ):
496
- """
497
- Plot the distribution of the quantity given by CorsikaHistograms.
184
+ if not isinstance(histograms_list, list):
185
+ histograms_list = [histograms_list]
498
186
 
499
- Parameters
500
- ----------
501
- histograms_instance: corsika.corsika_histograms.CorsikaHistograms
502
- instance of corsika.corsika_histograms.CorsikaHistograms.
503
- event_header_element_1: str
504
- The first key to the CORSIKA event header element
505
- event_header_element_2: str
506
- The second key to the CORSIKA event header element.
507
- log_z: bool
508
- if True, the intensity of the Y axis is given in logarithmic scale.
509
- bins: float
510
- Number of bins for the histogram.
511
- hist_range: 2-tuple
512
- Tuple to define the range of the histogram.
187
+ hist_keys = list(histograms_list[0].hist.keys())
513
188
 
514
- Returns
515
- -------
516
- list
517
- List of figures for the given telescopes.
189
+ for hist_key in hist_keys:
190
+ hist_from_all_files = [h.hist[hist_key] for h in histograms_list]
518
191
 
519
- """
520
- hist_values, x_bin_edges, y_bin_edges = histograms_instance.event_2d_histogram(
521
- event_header_element_1, event_header_element_2, bins=bins, hist_range=hist_range
522
- )
523
- fig, ax = plt.subplots()
524
- if log_z is True:
525
- norm = colors.LogNorm(vmin=1, vmax=np.amax([np.amax(hist_values), 2]))
526
- else:
527
- norm = None
528
- mesh = ax.pcolormesh(x_bin_edges, y_bin_edges, hist_values, norm=norm)
529
-
530
- if (
531
- histograms_instance.event_information[event_header_element_1].unit
532
- is not u.dimensionless_unscaled
533
- ):
534
- ax.set_xlabel(
535
- f"{event_header_element_1} ("
536
- f"{histograms_instance.event_information[event_header_element_1].unit})"
537
- )
538
- else:
539
- ax.set_xlabel(f"{event_header_element_2}")
540
- if (
541
- histograms_instance.event_information[event_header_element_2].unit
542
- is not u.dimensionless_unscaled
543
- ):
544
- ax.set_ylabel(
545
- f"{event_header_element_2} "
546
- f"({histograms_instance.event_information[event_header_element_2].unit})"
547
- )
548
- else:
549
- ax.set_ylabel(f"{event_header_element_2}")
192
+ if hist_from_all_files[0]["is_1d"]:
193
+ all_figs.extend(_plot_1d(hist_from_all_files, labels=labels))
194
+ else:
195
+ all_figs.extend(_plot_2d(hist_from_all_files, labels=labels))
550
196
 
551
- ax.set_facecolor("black")
552
- ax.set_title(f"hist_2d_{event_header_element_1}_{event_header_element_2}")
553
- fig.colorbar(mesh)
554
- return fig
197
+ return all_figs
555
198
 
556
199
 
557
- def save_figs_to_pdf(figs, pdf_file_name):
200
+ def export_all_photon_figures_pdf(histograms_instance, pdf_file_name, labels=None):
558
201
  """
559
- Save figures from corsika histograms to an output pdf file.
202
+ Build and save all photon histogram figures into a single PDF.
560
203
 
561
204
  Parameters
562
205
  ----------
563
- figs: list or numpy.array
564
- List with the figures output by corsika_output_visualize.py.
206
+ histograms_instance: corsika.corsika_histograms.CorsikaHistograms or list
207
+ Single histogram instance or list of CorsikaHistograms instances from multiple files.
208
+ When a list is provided, 1D histograms from all files are combined into single plots
209
+ with different colors, while 2D histograms of the same type are plotted sequentially.
565
210
  pdf_file_name: str or Path
566
- Name of the pdf file.
567
- """
568
- pdf_pages = PdfPages(Path(pdf_file_name).absolute().as_posix())
569
- for fig in figs:
570
- plt.tight_layout()
571
- pdf_pages.savefig(fig)
572
- pdf_pages.close()
573
-
574
-
575
- def build_all_photon_figures(histograms_instance, test: bool = False):
576
- """Return list of all photon histogram figures for the given instance.
577
-
578
- When test is True, only generate the first two figure groups to reduce runtime.
211
+ Name of the output pdf file to save the histograms.
212
+ labels: list or None
213
+ Optional list of labels for the input files. If None, file names are used as labels.
214
+ The order should match the order of histograms_instance if it's a list.
579
215
  """
580
- plot_function_names = sorted(
581
- [
582
- name
583
- for name, obj in globals().items()
584
- if name.startswith("plot_")
585
- and "event_header_distribution" not in name
586
- and callable(obj)
587
- ]
216
+ save_figures_to_single_document(
217
+ _build_all_photon_figures(histograms_instance, labels=labels), Path(pdf_file_name)
588
218
  )
589
- if test:
590
- plot_function_names = plot_function_names[:2]
591
-
592
- figure_list = []
593
- module_obj = globals()
594
- for fn_name in plot_function_names:
595
- plot_fn = module_obj[fn_name]
596
- figs = plot_fn(histograms_instance)
597
- for fig in figs:
598
- figure_list.append(fig)
599
- return np.array(figure_list).flatten()
600
-
601
-
602
- def export_all_photon_figures_pdf(histograms_instance, test: bool = False):
603
- """Build and save all photon histogram figures into a single PDF.
604
-
605
- The PDF name is derived from the HDF5 file name core and written under output_path.
606
- """
607
- figs = build_all_photon_figures(histograms_instance, test=test)
608
- core_name = re.sub(r"\.hdf5$", "", Path(histograms_instance.hdf5_file_name).name)
609
- output_file_name = Path(histograms_instance.output_path).joinpath(f"{core_name}.pdf")
610
- save_figs_to_pdf(figs, output_file_name)
611
- return output_file_name
612
-
613
-
614
- def derive_event_1d_histograms(
615
- histograms_instance,
616
- event_1d_header_keys,
617
- pdf: bool,
618
- hdf5: bool,
619
- overwrite: bool = False,
620
- ):
621
- """Create 1D event header histograms; optionally save to PDF and/or HDF5."""
622
- figure_list = []
623
- for key in event_1d_header_keys:
624
- if pdf:
625
- fig = plot_1d_event_header_distribution(histograms_instance, key)
626
- figure_list.append(fig)
627
- if hdf5:
628
- histograms_instance.export_event_header_1d_histogram(
629
- key, bins=50, hist_range=None, overwrite=overwrite
630
- )
631
- if pdf:
632
- figs_array = np.array(figure_list).flatten()
633
- pdf_name = Path(histograms_instance.output_path).joinpath(
634
- f"{Path(histograms_instance.hdf5_file_name).name}_event_1d_histograms.pdf"
635
- )
636
- save_figs_to_pdf(figs_array, pdf_name)
637
- return pdf_name
638
- return None
639
-
640
-
641
- def derive_event_2d_histograms(
642
- histograms_instance,
643
- event_2d_header_keys,
644
- pdf: bool,
645
- hdf5: bool,
646
- overwrite: bool = False,
647
- ):
648
- """Create 2D event header histograms in pairs; optionally save PDF and/or HDF5.
649
-
650
- If an odd number of keys is provided, the last one is ignored (with a warning).
651
- """
652
- if len(event_2d_header_keys) % 2 == 1:
653
- _logger.warning(
654
- "An odd number of keys was passed to generate 2D histograms.\n"
655
- "The last key is being ignored."
656
- )
657
-
658
- figure_list = []
659
- for i, _ in enumerate(event_2d_header_keys[::2]):
660
- if pdf:
661
- fig = plot_2d_event_header_distribution(
662
- histograms_instance, event_2d_header_keys[i], event_2d_header_keys[i + 1]
663
- )
664
- figure_list.append(fig)
665
- if hdf5:
666
- histograms_instance.export_event_header_2d_histogram(
667
- event_2d_header_keys[i],
668
- event_2d_header_keys[i + 1],
669
- bins=50,
670
- hist_range=None,
671
- overwrite=overwrite,
672
- )
673
- if pdf:
674
- figs_array = np.array(figure_list).flatten()
675
- pdf_name = Path(histograms_instance.output_path).joinpath(
676
- f"{Path(histograms_instance.hdf5_file_name).name}_event_2d_histograms.pdf"
677
- )
678
- save_figs_to_pdf(figs_array, pdf_name)
679
- return pdf_name
680
- return None