gammasimtools 0.5.1__py3-none-any.whl → 0.6.1__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.5.1.dist-info → gammasimtools-0.6.1.dist-info}/METADATA +80 -28
- gammasimtools-0.6.1.dist-info/RECORD +91 -0
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/WHEEL +1 -1
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/entry_points.txt +4 -2
- simtools/_version.py +14 -2
- simtools/applications/add_file_to_db.py +2 -1
- simtools/applications/compare_cumulative_psf.py +10 -15
- simtools/applications/db_development_tools/add_new_parameter_to_db.py +12 -6
- simtools/applications/derive_mirror_rnda.py +95 -71
- simtools/applications/generate_corsika_histograms.py +216 -131
- simtools/applications/generate_default_metadata.py +110 -0
- simtools/applications/generate_simtel_array_histograms.py +192 -0
- simtools/applications/get_file_from_db.py +1 -1
- simtools/applications/get_parameter.py +3 -3
- simtools/applications/make_regular_arrays.py +89 -93
- simtools/applications/{plot_layout_array.py → plot_array_layout.py} +15 -14
- simtools/applications/print_array_elements.py +81 -34
- simtools/applications/produce_array_config.py +2 -2
- simtools/applications/production.py +39 -5
- simtools/applications/sim_showers_for_trigger_rates.py +26 -30
- simtools/applications/simulate_prod.py +49 -107
- simtools/applications/submit_data_from_external.py +8 -10
- simtools/applications/tune_psf.py +16 -18
- simtools/applications/validate_camera_efficiency.py +63 -9
- simtools/applications/validate_camera_fov.py +9 -13
- simtools/applications/validate_file_using_schema.py +127 -0
- simtools/applications/validate_optics.py +13 -15
- simtools/camera_efficiency.py +73 -80
- simtools/configuration/commandline_parser.py +52 -22
- simtools/configuration/configurator.py +98 -33
- simtools/constants.py +9 -0
- simtools/corsika/corsika_config.py +28 -22
- simtools/corsika/corsika_default_config.py +282 -0
- simtools/corsika/corsika_histograms.py +328 -282
- simtools/corsika/corsika_histograms_visualize.py +162 -163
- simtools/corsika/corsika_runner.py +8 -4
- simtools/corsika_simtel/corsika_simtel_runner.py +18 -23
- simtools/data_model/data_reader.py +129 -0
- simtools/data_model/metadata_collector.py +346 -118
- simtools/data_model/metadata_model.py +123 -218
- simtools/data_model/model_data_writer.py +79 -22
- simtools/data_model/validate_data.py +96 -46
- simtools/db_handler.py +67 -42
- simtools/io_operations/__init__.py +0 -0
- simtools/io_operations/hdf5_handler.py +112 -0
- simtools/{io_handler.py → io_operations/io_handler.py} +51 -22
- simtools/job_execution/job_manager.py +1 -1
- simtools/layout/{layout_array.py → array_layout.py} +168 -199
- simtools/layout/geo_coordinates.py +196 -0
- simtools/layout/telescope_position.py +12 -12
- simtools/model/array_model.py +16 -14
- simtools/model/camera.py +5 -8
- simtools/model/mirrors.py +136 -73
- simtools/model/model_utils.py +1 -69
- simtools/model/telescope_model.py +32 -25
- simtools/psf_analysis.py +26 -19
- simtools/ray_tracing.py +54 -26
- simtools/schemas/data.metaschema.yml +400 -0
- simtools/schemas/metadata.metaschema.yml +566 -0
- simtools/simtel/simtel_config_writer.py +14 -5
- simtools/simtel/simtel_histograms.py +266 -83
- simtools/simtel/simtel_runner.py +8 -7
- simtools/simtel/simtel_runner_array.py +7 -8
- simtools/simtel/simtel_runner_camera_efficiency.py +48 -2
- simtools/simtel/simtel_runner_ray_tracing.py +61 -25
- simtools/simulator.py +43 -50
- simtools/utils/general.py +232 -286
- simtools/utils/geometry.py +163 -0
- simtools/utils/names.py +294 -142
- simtools/visualization/legend_handlers.py +115 -9
- simtools/visualization/visualize.py +13 -13
- gammasimtools-0.5.1.dist-info/RECORD +0 -83
- simtools/applications/plot_simtel_histograms.py +0 -120
- simtools/applications/validate_schema_files.py +0 -135
- simtools/corsika/corsika_output_visualize.py +0 -345
- simtools/data_model/validate_schema.py +0 -285
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/LICENSE +0 -0
- {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
import matplotlib.pyplot as plt
|
|
4
|
-
import numpy as np
|
|
5
|
-
from matplotlib import colors
|
|
6
|
-
|
|
7
|
-
_logger = logging.getLogger(__name__)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def _kernel_plot_2D_photons(corsika_output_instance, property_name, log_z=False):
|
|
11
|
-
"""
|
|
12
|
-
The next functions below are used by the the corsikaOutput class to plot all sort of information
|
|
13
|
-
from the Cherenkov photons saved.
|
|
14
|
-
|
|
15
|
-
Create the figure of a 2D plot. The parameter `name` indicate which plot. Choices are
|
|
16
|
-
"counts", "density", "direction".
|
|
17
|
-
|
|
18
|
-
Parameters
|
|
19
|
-
----------
|
|
20
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
21
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
22
|
-
property_name: string
|
|
23
|
-
Name of the quantity. Options are: "counts", "density", "direction", "time_altitude" and
|
|
24
|
-
"num_photons_per_telescope".
|
|
25
|
-
log_z: bool
|
|
26
|
-
if True, the intensity of the color bar is given in logarithmic scale.
|
|
27
|
-
|
|
28
|
-
Returns
|
|
29
|
-
-------
|
|
30
|
-
list
|
|
31
|
-
List of figures for the given telescopes.
|
|
32
|
-
|
|
33
|
-
Raises
|
|
34
|
-
------
|
|
35
|
-
ValueError
|
|
36
|
-
if `name` is not allowed.
|
|
37
|
-
|
|
38
|
-
"""
|
|
39
|
-
x_label = {
|
|
40
|
-
"counts": "x (m)",
|
|
41
|
-
"density": "x (m)",
|
|
42
|
-
"direction": "cos(x)",
|
|
43
|
-
"time_altitude": "Time since 1st interaction (ns)",
|
|
44
|
-
"num_photons_per_telescope": "Event number",
|
|
45
|
-
}
|
|
46
|
-
y_label = {
|
|
47
|
-
"counts": "y (m)",
|
|
48
|
-
"density": "y (m)",
|
|
49
|
-
"direction": "cos(y)",
|
|
50
|
-
"time_altitude": "Altitude of emission (km)",
|
|
51
|
-
"num_photons_per_telescope": "Telescope index",
|
|
52
|
-
}
|
|
53
|
-
if property_name not in x_label:
|
|
54
|
-
msg = f"property_name must be one of {list(x_label.keys())}"
|
|
55
|
-
_logger.error(msg)
|
|
56
|
-
raise ValueError(msg)
|
|
57
|
-
|
|
58
|
-
if property_name == "counts":
|
|
59
|
-
hist_values, x_edges, y_edges = corsika_output_instance.get_2D_photon_position_distr(
|
|
60
|
-
density=False
|
|
61
|
-
)
|
|
62
|
-
elif property_name == "density":
|
|
63
|
-
hist_values, x_edges, y_edges = corsika_output_instance.get_2D_photon_position_distr(
|
|
64
|
-
density=True
|
|
65
|
-
)
|
|
66
|
-
elif property_name == "direction":
|
|
67
|
-
hist_values, x_edges, y_edges = corsika_output_instance.get_2D_photon_direction_distr()
|
|
68
|
-
elif property_name == "time_altitude":
|
|
69
|
-
hist_values, x_edges, y_edges = corsika_output_instance.get_2D_photon_time_altitude()
|
|
70
|
-
elif property_name == "num_photons_per_telescope":
|
|
71
|
-
hist_values, x_edges, y_edges = corsika_output_instance.get_2D_num_photons_distr()
|
|
72
|
-
hist_values, x_edges, y_edges = [hist_values], [x_edges], [y_edges]
|
|
73
|
-
|
|
74
|
-
all_figs = []
|
|
75
|
-
for i_hist, _ in enumerate(x_edges):
|
|
76
|
-
fig, ax = plt.subplots()
|
|
77
|
-
if log_z is True:
|
|
78
|
-
norm = colors.LogNorm(vmin=1, vmax=np.amax([np.amax(hist_values[i_hist]), 2]))
|
|
79
|
-
else:
|
|
80
|
-
norm = None
|
|
81
|
-
mesh = ax.pcolormesh(x_edges[i_hist], y_edges[i_hist], hist_values[i_hist], norm=norm)
|
|
82
|
-
ax.set_xlabel(x_label[property_name])
|
|
83
|
-
ax.set_ylabel(y_label[property_name])
|
|
84
|
-
ax.set_xlim(np.amin(x_edges[i_hist]), np.amax(x_edges[i_hist]))
|
|
85
|
-
ax.set_ylim(np.amin(y_edges[i_hist]), np.amax(y_edges[i_hist]))
|
|
86
|
-
ax.set_facecolor("xkcd:black")
|
|
87
|
-
fig.colorbar(mesh)
|
|
88
|
-
all_figs.append(fig)
|
|
89
|
-
if corsika_output_instance.individual_telescopes is False:
|
|
90
|
-
fig.savefig(f"histogram_{property_name}_2D_all_tels.png", bbox_inches="tight")
|
|
91
|
-
else:
|
|
92
|
-
ax.text(
|
|
93
|
-
0.99,
|
|
94
|
-
0.99,
|
|
95
|
-
"tel. " + str(i_hist),
|
|
96
|
-
ha="right",
|
|
97
|
-
va="top",
|
|
98
|
-
transform=ax.transAxes,
|
|
99
|
-
color="white",
|
|
100
|
-
)
|
|
101
|
-
fig.savefig(
|
|
102
|
-
f"histogram_{property_name}_2D_tel_"
|
|
103
|
-
f"{str(corsika_output_instance.telescope_indices[i_hist])}.png",
|
|
104
|
-
bbox_inches="tight",
|
|
105
|
-
)
|
|
106
|
-
plt.close()
|
|
107
|
-
|
|
108
|
-
return all_figs
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def plot_2D_counts(corsika_output_instance, log_z=True):
|
|
112
|
-
"""
|
|
113
|
-
Plot the 2D histogram of the photon positions on the ground.
|
|
114
|
-
|
|
115
|
-
Parameters
|
|
116
|
-
----------
|
|
117
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
118
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
119
|
-
log_z: bool
|
|
120
|
-
if True, the intensity of the color bar is given in logarithmic scale.
|
|
121
|
-
"""
|
|
122
|
-
return _kernel_plot_2D_photons(corsika_output_instance, "counts", log_z=log_z)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def plot_2D_density(corsika_output_instance, log_z=True):
|
|
126
|
-
"""
|
|
127
|
-
Plot the 2D histogram of the photon density distribution on the ground.
|
|
128
|
-
|
|
129
|
-
Parameters
|
|
130
|
-
----------
|
|
131
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
132
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
133
|
-
log_z: bool
|
|
134
|
-
if True, the intensity of the color bar is given in logarithmic scale.
|
|
135
|
-
"""
|
|
136
|
-
return _kernel_plot_2D_photons(corsika_output_instance, "density", log_z=log_z)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def plot_2D_direction(corsika_output_instance, log_z=True):
|
|
140
|
-
"""
|
|
141
|
-
Plot the 2D histogram of the incoming direction of photons.
|
|
142
|
-
|
|
143
|
-
Parameters
|
|
144
|
-
----------
|
|
145
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
146
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
147
|
-
log_z: bool
|
|
148
|
-
if True, the intensity of the color bar is given in logarithmic scale.
|
|
149
|
-
"""
|
|
150
|
-
return _kernel_plot_2D_photons(corsika_output_instance, "direction", log_z=log_z)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def plot_2D_time_altitude(corsika_output_instance, log_z=True):
|
|
154
|
-
"""
|
|
155
|
-
Plot the 2D histogram of the time and altitude where the photon was produced.
|
|
156
|
-
|
|
157
|
-
Parameters
|
|
158
|
-
----------
|
|
159
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
160
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
161
|
-
log_z: bool
|
|
162
|
-
if True, the intensity of the color bar is given in logarithmic scale.
|
|
163
|
-
"""
|
|
164
|
-
return _kernel_plot_2D_photons(corsika_output_instance, "time_altitude", log_z=log_z)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def plot_2D_num_photons_per_telescope(corsika_output_instance, log_z=True):
|
|
168
|
-
"""
|
|
169
|
-
Plot the 2D histogram of the number of photons per event and per telescope.
|
|
170
|
-
|
|
171
|
-
Parameters
|
|
172
|
-
----------
|
|
173
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
174
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
175
|
-
log_z: bool
|
|
176
|
-
if True, the intensity of the color bar is given in logarithmic scale.
|
|
177
|
-
"""
|
|
178
|
-
return _kernel_plot_2D_photons(
|
|
179
|
-
corsika_output_instance, "num_photons_per_telescope", log_z=log_z
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def _kernel_plot_1D_photons(corsika_output_instance, property_name, log_y=True):
|
|
184
|
-
"""
|
|
185
|
-
Create the figure of a 1D plot. The parameter `name` indicate which plot. Choices are
|
|
186
|
-
"counts", "density", "direction".
|
|
187
|
-
|
|
188
|
-
Parameters
|
|
189
|
-
----------
|
|
190
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
191
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
192
|
-
property_name: string
|
|
193
|
-
Name of the quantity. Options are: "wavelength", "counts", "density", "time", "altitude",
|
|
194
|
-
"num_photons".
|
|
195
|
-
log_y: bool
|
|
196
|
-
if True, the intensity of the Y axis is given in logarithmic scale.
|
|
197
|
-
|
|
198
|
-
Returns
|
|
199
|
-
-------
|
|
200
|
-
list
|
|
201
|
-
List of figures for the given telescopes.
|
|
202
|
-
|
|
203
|
-
Raises
|
|
204
|
-
------
|
|
205
|
-
ValueError
|
|
206
|
-
if `name` is not allowed.
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
|
-
x_label = {
|
|
210
|
-
"wavelength": "Wavelength (nm)",
|
|
211
|
-
"counts": "Distance to center (m)",
|
|
212
|
-
"density": "Distance to center (m)",
|
|
213
|
-
"time": "Time since 1st interaction (ns)",
|
|
214
|
-
"altitude": "Altitude of emission (km)",
|
|
215
|
-
"num_photons": "Number of photons per event",
|
|
216
|
-
}
|
|
217
|
-
if property_name not in x_label:
|
|
218
|
-
msg = f"results: status must be one of {list(x_label.keys())}"
|
|
219
|
-
_logger.error(msg)
|
|
220
|
-
raise ValueError(msg)
|
|
221
|
-
|
|
222
|
-
if property_name == "wavelength":
|
|
223
|
-
hist_values, edges = corsika_output_instance.get_photon_wavelength_distr()
|
|
224
|
-
elif property_name == "counts":
|
|
225
|
-
hist_values, edges = corsika_output_instance.get_photon_radial_distr(density=False)
|
|
226
|
-
elif property_name == "density":
|
|
227
|
-
hist_values, edges = corsika_output_instance.get_photon_radial_distr(density=True)
|
|
228
|
-
elif property_name == "time":
|
|
229
|
-
hist_values, edges = corsika_output_instance.get_photon_time_of_emission_distr()
|
|
230
|
-
elif property_name == "altitude":
|
|
231
|
-
hist_values, edges = corsika_output_instance.get_photon_altitude_distr()
|
|
232
|
-
elif property_name == "num_photons":
|
|
233
|
-
hist_values, edges = corsika_output_instance.get_num_photons_distr()
|
|
234
|
-
hist_values, edges = [hist_values], [edges]
|
|
235
|
-
|
|
236
|
-
all_figs = []
|
|
237
|
-
for i_hist, _ in enumerate(edges):
|
|
238
|
-
fig, ax = plt.subplots()
|
|
239
|
-
ax.bar(
|
|
240
|
-
edges[i_hist][:-1],
|
|
241
|
-
hist_values[i_hist],
|
|
242
|
-
align="edge",
|
|
243
|
-
width=np.abs(np.diff(edges[i_hist])),
|
|
244
|
-
)
|
|
245
|
-
ax.set_xlabel(x_label[property_name])
|
|
246
|
-
ax.set_ylabel("Counts")
|
|
247
|
-
|
|
248
|
-
if log_y is True:
|
|
249
|
-
ax.set_yscale("log")
|
|
250
|
-
if corsika_output_instance.individual_telescopes is False:
|
|
251
|
-
fig.savefig(f"histogram_{property_name}_tels.png", bbox_inches="tight")
|
|
252
|
-
else:
|
|
253
|
-
fig.savefig(
|
|
254
|
-
f"histogram_{property_name}_tel_"
|
|
255
|
-
f"{str(corsika_output_instance.telescope_indices[i_hist])}.png",
|
|
256
|
-
bbox_inches="tight",
|
|
257
|
-
)
|
|
258
|
-
all_figs.append(fig)
|
|
259
|
-
return all_figs
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def plot_wavelength_distr(corsika_output_instance, log_y=True):
|
|
263
|
-
"""
|
|
264
|
-
Plots the 1D distribution of the photon wavelengths
|
|
265
|
-
|
|
266
|
-
Parameters
|
|
267
|
-
----------
|
|
268
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
269
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
270
|
-
log_y: bool
|
|
271
|
-
if True, the intensity of the Y axis is given in logarithmic scale.
|
|
272
|
-
|
|
273
|
-
"""
|
|
274
|
-
return _kernel_plot_1D_photons(corsika_output_instance, "wavelength", log_y=log_y)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def plot_counts_distr(corsika_output_instance, log_y=True):
|
|
278
|
-
"""
|
|
279
|
-
Plots the 1D distribution, i.e. the radial distribution, of the photons on the ground.
|
|
280
|
-
|
|
281
|
-
Parameters
|
|
282
|
-
----------
|
|
283
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
284
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
285
|
-
log_y: bool
|
|
286
|
-
if True, the intensity of the Y axis is given in logarithmic scale.
|
|
287
|
-
"""
|
|
288
|
-
return _kernel_plot_1D_photons(corsika_output_instance, "counts", log_y=log_y)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def plot_density_distr(corsika_output_instance, log_y=True):
|
|
292
|
-
"""
|
|
293
|
-
Plots the photon density distribution on the ground.
|
|
294
|
-
|
|
295
|
-
Parameters
|
|
296
|
-
----------
|
|
297
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
298
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
299
|
-
log_y: bool
|
|
300
|
-
if True, the intensity of the Y axis is given in logarithmic scale.
|
|
301
|
-
|
|
302
|
-
"""
|
|
303
|
-
return _kernel_plot_1D_photons(corsika_output_instance, "density", log_y=log_y)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def plot_time_distr(corsika_output_instance, log_y=True):
|
|
307
|
-
"""
|
|
308
|
-
Plots the distribution times in which the photons were generated in ns.
|
|
309
|
-
|
|
310
|
-
Parameters
|
|
311
|
-
----------
|
|
312
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
313
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
314
|
-
log_y: bool
|
|
315
|
-
if True, the intensity of the Y axis is given in logarithmic scale.
|
|
316
|
-
"""
|
|
317
|
-
return _kernel_plot_1D_photons(corsika_output_instance, "time", log_y=log_y)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
def plot_altitude_distr(corsika_output_instance, log_y=True):
|
|
321
|
-
"""
|
|
322
|
-
Plots the distribution of altitude in which the photons were generated in km.
|
|
323
|
-
|
|
324
|
-
Parameters
|
|
325
|
-
----------
|
|
326
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
327
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
328
|
-
log_y: bool
|
|
329
|
-
if True, the intensity of the Y axis is given in logarithmic scale.
|
|
330
|
-
"""
|
|
331
|
-
return _kernel_plot_1D_photons(corsika_output_instance, "altitude", log_y=log_y)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
def plot_num_photons_distr(corsika_output_instance, log_y=True):
|
|
335
|
-
"""
|
|
336
|
-
Plots the distribution of the number of Cherenkov photons per event.
|
|
337
|
-
|
|
338
|
-
Parameters
|
|
339
|
-
----------
|
|
340
|
-
corsika_output_instance: corsika.corsika_output.corsikaOutput
|
|
341
|
-
instance of corsika.corsika_output.corsikaOutput.
|
|
342
|
-
log_y: bool
|
|
343
|
-
if True, the intensity of the Y axis is given in logarithmic scale.
|
|
344
|
-
"""
|
|
345
|
-
return _kernel_plot_1D_photons(corsika_output_instance, "num_photons", log_y=log_y)
|
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
import logging
|
|
3
|
-
import re
|
|
4
|
-
|
|
5
|
-
import simtools.utils.general as gen
|
|
6
|
-
from simtools.data_model import metadata_model
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class SchemaValidator:
|
|
10
|
-
"""
|
|
11
|
-
Validate a dictionary against the simpipe reference schema.
|
|
12
|
-
Used e.g., to validate metadata provided as input.
|
|
13
|
-
|
|
14
|
-
Parameters
|
|
15
|
-
----------
|
|
16
|
-
data_dict: dict
|
|
17
|
-
Metadata dict to be validated against reference schema.
|
|
18
|
-
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def __init__(self, data_dict=None):
|
|
22
|
-
"""
|
|
23
|
-
Initialize validation class and load reference schema.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
self._logger = logging.getLogger(__name__)
|
|
27
|
-
|
|
28
|
-
self._reference_schema = gen.change_dict_keys_case(
|
|
29
|
-
metadata_model.metadata_input_reference_schema(), lower_case=True
|
|
30
|
-
)
|
|
31
|
-
self.data_dict = data_dict
|
|
32
|
-
|
|
33
|
-
def validate_and_transform(self, meta_file_name=None):
|
|
34
|
-
"""
|
|
35
|
-
Schema validation and processing.
|
|
36
|
-
|
|
37
|
-
Parameters
|
|
38
|
-
----------
|
|
39
|
-
meta_file_name
|
|
40
|
-
file name for file with meta data to
|
|
41
|
-
be validated (might also be given as
|
|
42
|
-
dictionary during initialization of the class).
|
|
43
|
-
|
|
44
|
-
Returns
|
|
45
|
-
-------
|
|
46
|
-
dict
|
|
47
|
-
Complete set of metadata following the CTA top-level metadata defintion
|
|
48
|
-
(None if meta_file_name is undefined)
|
|
49
|
-
|
|
50
|
-
"""
|
|
51
|
-
try:
|
|
52
|
-
self._logger.debug(f"Reading meta data from {meta_file_name}")
|
|
53
|
-
self.data_dict = gen.collect_data_from_yaml_or_dict(meta_file_name, self.data_dict)
|
|
54
|
-
except gen.InvalidConfigData:
|
|
55
|
-
self._logger.debug("Failed reading metadata from file.")
|
|
56
|
-
return None
|
|
57
|
-
|
|
58
|
-
self.data_dict = gen.change_dict_keys_case(self.data_dict, True)
|
|
59
|
-
self._validate_schema(self._reference_schema, self.data_dict)
|
|
60
|
-
self._process_schema()
|
|
61
|
-
return self.data_dict
|
|
62
|
-
|
|
63
|
-
def _validate_schema(self, ref_schema, data_dict):
|
|
64
|
-
"""
|
|
65
|
-
Validate schema for data types and required fields.
|
|
66
|
-
|
|
67
|
-
Parameters
|
|
68
|
-
----------
|
|
69
|
-
ref_schema: dict
|
|
70
|
-
Reference metadata schema
|
|
71
|
-
data_dict: dict
|
|
72
|
-
input metadata dict to be validated against
|
|
73
|
-
reference schema.
|
|
74
|
-
|
|
75
|
-
Raises
|
|
76
|
-
------
|
|
77
|
-
UnboundLocalError
|
|
78
|
-
If no data is available for metadata key from the
|
|
79
|
-
reference schema.
|
|
80
|
-
|
|
81
|
-
"""
|
|
82
|
-
|
|
83
|
-
for key, value in ref_schema.items():
|
|
84
|
-
if data_dict and key in data_dict:
|
|
85
|
-
_this_data = data_dict[key]
|
|
86
|
-
else:
|
|
87
|
-
if self._field_is_optional(value):
|
|
88
|
-
self._logger.debug(f"Optional field {key}")
|
|
89
|
-
continue
|
|
90
|
-
msg = f"Missing required field '{key}'"
|
|
91
|
-
raise ValueError(msg)
|
|
92
|
-
|
|
93
|
-
if isinstance(value, dict):
|
|
94
|
-
# "type" is used for data types (str) and for telescope types (dict)
|
|
95
|
-
if "type" in value and isinstance(value["type"], str):
|
|
96
|
-
try:
|
|
97
|
-
self._validate_data_type(value, key, _this_data)
|
|
98
|
-
except UnboundLocalError:
|
|
99
|
-
self._logger.error(f"No data for {key} key")
|
|
100
|
-
raise
|
|
101
|
-
else:
|
|
102
|
-
self._validate_schema(value, _this_data)
|
|
103
|
-
|
|
104
|
-
def _process_schema(self):
|
|
105
|
-
"""
|
|
106
|
-
Process schema entries for inconsistencies
|
|
107
|
-
(quite fine tuned)
|
|
108
|
-
- remove linefeeds from description string
|
|
109
|
-
|
|
110
|
-
Raises
|
|
111
|
-
------
|
|
112
|
-
KeyError
|
|
113
|
-
if data_dict["product"]["description"] is not available.
|
|
114
|
-
|
|
115
|
-
"""
|
|
116
|
-
|
|
117
|
-
try:
|
|
118
|
-
self.data_dict["product"]["description"] = self._remove_line_feed(
|
|
119
|
-
self.data_dict["product"]["description"]
|
|
120
|
-
)
|
|
121
|
-
except KeyError:
|
|
122
|
-
pass
|
|
123
|
-
|
|
124
|
-
def _validate_data_type(self, schema, key, data_field):
|
|
125
|
-
"""
|
|
126
|
-
Validate data type against the expected data type
|
|
127
|
-
from schema.
|
|
128
|
-
|
|
129
|
-
Parameters
|
|
130
|
-
----------
|
|
131
|
-
schema: dict
|
|
132
|
-
metadata description from reference schema.
|
|
133
|
-
key: str
|
|
134
|
-
data field name to be validated.
|
|
135
|
-
data_field: dict
|
|
136
|
-
data field to be validated.
|
|
137
|
-
|
|
138
|
-
Raises
|
|
139
|
-
------
|
|
140
|
-
ValueError
|
|
141
|
-
if data types are inconsistent.
|
|
142
|
-
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
self._logger.debug(f"checking data field {key} for {schema['type']}")
|
|
146
|
-
|
|
147
|
-
convert = {"str": type("str"), "float": type(1.0), "int": type(0), "bool": type(True)}
|
|
148
|
-
|
|
149
|
-
if schema["type"] == "datetime":
|
|
150
|
-
self._validate_datetime(data_field, self._field_is_optional(schema))
|
|
151
|
-
elif schema["type"] == "email":
|
|
152
|
-
self._validate_email(data_field, key)
|
|
153
|
-
elif schema["type"].endswith("list"):
|
|
154
|
-
self._validate_list(schema["type"], data_field)
|
|
155
|
-
elif type(data_field).__name__ != schema["type"]:
|
|
156
|
-
try:
|
|
157
|
-
if isinstance(data_field, (int, str)):
|
|
158
|
-
convert[schema["type"]](data_field)
|
|
159
|
-
elif data_field is not None:
|
|
160
|
-
raise ValueError
|
|
161
|
-
except ValueError as error:
|
|
162
|
-
raise ValueError(
|
|
163
|
-
f"invalid type for key {key}. Expected: {schema['type']}, "
|
|
164
|
-
f"Found: {type(data_field).__name__}"
|
|
165
|
-
) from error
|
|
166
|
-
|
|
167
|
-
@staticmethod
|
|
168
|
-
def _validate_datetime(data_field, optional_field=False):
|
|
169
|
-
"""
|
|
170
|
-
Validate entry to be of type datetime and of
|
|
171
|
-
format %Y-%m-%d %H:%M:%S.
|
|
172
|
-
|
|
173
|
-
Parameters
|
|
174
|
-
----------
|
|
175
|
-
data_field: dict
|
|
176
|
-
data field to be validated
|
|
177
|
-
optional_field: boolean
|
|
178
|
-
data field is optional
|
|
179
|
-
|
|
180
|
-
Raises
|
|
181
|
-
------
|
|
182
|
-
ValueError
|
|
183
|
-
if data field is of invalid format
|
|
184
|
-
|
|
185
|
-
"""
|
|
186
|
-
format_date = "%Y-%m-%d %H:%M:%S"
|
|
187
|
-
try:
|
|
188
|
-
datetime.datetime.strptime(data_field, format_date)
|
|
189
|
-
except (ValueError, TypeError) as error:
|
|
190
|
-
if not optional_field:
|
|
191
|
-
raise ValueError(
|
|
192
|
-
f"invalid date format. Expected {format_date}; Found {data_field}"
|
|
193
|
-
) from error
|
|
194
|
-
|
|
195
|
-
@staticmethod
|
|
196
|
-
def _validate_email(data_field, key):
|
|
197
|
-
"""
|
|
198
|
-
Validate entry to be a email address
|
|
199
|
-
|
|
200
|
-
Parameters
|
|
201
|
-
----------
|
|
202
|
-
data_field: dict
|
|
203
|
-
data field to be validated.
|
|
204
|
-
key: str
|
|
205
|
-
data field name to be validated.
|
|
206
|
-
|
|
207
|
-
Raises
|
|
208
|
-
------
|
|
209
|
-
ValueError
|
|
210
|
-
if data field is of invalid format.
|
|
211
|
-
|
|
212
|
-
"""
|
|
213
|
-
regex = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
|
|
214
|
-
if not re.fullmatch(regex, data_field):
|
|
215
|
-
raise ValueError(f"invalid email format in field {key}: {data_field}")
|
|
216
|
-
|
|
217
|
-
def _validate_list(self, schema_type, data_list):
|
|
218
|
-
"""
|
|
219
|
-
Validate schmema for list type entry
|
|
220
|
-
|
|
221
|
-
Parameters
|
|
222
|
-
----------
|
|
223
|
-
schema_type
|
|
224
|
-
reference schema type (e.g., instrumentlist, documentlist).
|
|
225
|
-
data_list: list
|
|
226
|
-
list of dictionaries to be validated.
|
|
227
|
-
"""
|
|
228
|
-
|
|
229
|
-
_ref_schema = gen.change_dict_keys_case(
|
|
230
|
-
metadata_model.metadata_input_reference_document_list(schema_type), lower_case=True
|
|
231
|
-
)
|
|
232
|
-
for entry in data_list:
|
|
233
|
-
self._validate_schema(_ref_schema, entry)
|
|
234
|
-
|
|
235
|
-
def _field_is_optional(self, field_dict):
|
|
236
|
-
"""
|
|
237
|
-
Check if data field is labeled as optional in the reference metadata schema.
|
|
238
|
-
Dictionaries as datafields are tested for any optional fields.
|
|
239
|
-
|
|
240
|
-
Parameters
|
|
241
|
-
----------
|
|
242
|
-
field_dict: dict
|
|
243
|
-
required field from reference metadata schema
|
|
244
|
-
|
|
245
|
-
Returns
|
|
246
|
-
-------
|
|
247
|
-
boolean
|
|
248
|
-
True if data field is required
|
|
249
|
-
|
|
250
|
-
Raises
|
|
251
|
-
------
|
|
252
|
-
KeyError
|
|
253
|
-
if 'required' field is not defined in reference
|
|
254
|
-
metadata schema
|
|
255
|
-
|
|
256
|
-
"""
|
|
257
|
-
try:
|
|
258
|
-
if field_dict["required"]:
|
|
259
|
-
return False
|
|
260
|
-
except KeyError:
|
|
261
|
-
if isinstance(field_dict, dict):
|
|
262
|
-
for value in field_dict.values():
|
|
263
|
-
if isinstance(value, dict) and not self._field_is_optional(value):
|
|
264
|
-
return False
|
|
265
|
-
return True
|
|
266
|
-
return False
|
|
267
|
-
return True
|
|
268
|
-
|
|
269
|
-
@staticmethod
|
|
270
|
-
def _remove_line_feed(string):
|
|
271
|
-
"""
|
|
272
|
-
Remove all line feeds from a string
|
|
273
|
-
|
|
274
|
-
Parameters
|
|
275
|
-
----------
|
|
276
|
-
str
|
|
277
|
-
input string
|
|
278
|
-
|
|
279
|
-
Returns
|
|
280
|
-
-------
|
|
281
|
-
str
|
|
282
|
-
with line feeds removed
|
|
283
|
-
"""
|
|
284
|
-
|
|
285
|
-
return string.replace("\n", " ").replace("\r", "")
|
|
File without changes
|
|
File without changes
|