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.
Files changed (78) hide show
  1. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/METADATA +80 -28
  2. gammasimtools-0.6.1.dist-info/RECORD +91 -0
  3. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/entry_points.txt +4 -2
  5. simtools/_version.py +14 -2
  6. simtools/applications/add_file_to_db.py +2 -1
  7. simtools/applications/compare_cumulative_psf.py +10 -15
  8. simtools/applications/db_development_tools/add_new_parameter_to_db.py +12 -6
  9. simtools/applications/derive_mirror_rnda.py +95 -71
  10. simtools/applications/generate_corsika_histograms.py +216 -131
  11. simtools/applications/generate_default_metadata.py +110 -0
  12. simtools/applications/generate_simtel_array_histograms.py +192 -0
  13. simtools/applications/get_file_from_db.py +1 -1
  14. simtools/applications/get_parameter.py +3 -3
  15. simtools/applications/make_regular_arrays.py +89 -93
  16. simtools/applications/{plot_layout_array.py → plot_array_layout.py} +15 -14
  17. simtools/applications/print_array_elements.py +81 -34
  18. simtools/applications/produce_array_config.py +2 -2
  19. simtools/applications/production.py +39 -5
  20. simtools/applications/sim_showers_for_trigger_rates.py +26 -30
  21. simtools/applications/simulate_prod.py +49 -107
  22. simtools/applications/submit_data_from_external.py +8 -10
  23. simtools/applications/tune_psf.py +16 -18
  24. simtools/applications/validate_camera_efficiency.py +63 -9
  25. simtools/applications/validate_camera_fov.py +9 -13
  26. simtools/applications/validate_file_using_schema.py +127 -0
  27. simtools/applications/validate_optics.py +13 -15
  28. simtools/camera_efficiency.py +73 -80
  29. simtools/configuration/commandline_parser.py +52 -22
  30. simtools/configuration/configurator.py +98 -33
  31. simtools/constants.py +9 -0
  32. simtools/corsika/corsika_config.py +28 -22
  33. simtools/corsika/corsika_default_config.py +282 -0
  34. simtools/corsika/corsika_histograms.py +328 -282
  35. simtools/corsika/corsika_histograms_visualize.py +162 -163
  36. simtools/corsika/corsika_runner.py +8 -4
  37. simtools/corsika_simtel/corsika_simtel_runner.py +18 -23
  38. simtools/data_model/data_reader.py +129 -0
  39. simtools/data_model/metadata_collector.py +346 -118
  40. simtools/data_model/metadata_model.py +123 -218
  41. simtools/data_model/model_data_writer.py +79 -22
  42. simtools/data_model/validate_data.py +96 -46
  43. simtools/db_handler.py +67 -42
  44. simtools/io_operations/__init__.py +0 -0
  45. simtools/io_operations/hdf5_handler.py +112 -0
  46. simtools/{io_handler.py → io_operations/io_handler.py} +51 -22
  47. simtools/job_execution/job_manager.py +1 -1
  48. simtools/layout/{layout_array.py → array_layout.py} +168 -199
  49. simtools/layout/geo_coordinates.py +196 -0
  50. simtools/layout/telescope_position.py +12 -12
  51. simtools/model/array_model.py +16 -14
  52. simtools/model/camera.py +5 -8
  53. simtools/model/mirrors.py +136 -73
  54. simtools/model/model_utils.py +1 -69
  55. simtools/model/telescope_model.py +32 -25
  56. simtools/psf_analysis.py +26 -19
  57. simtools/ray_tracing.py +54 -26
  58. simtools/schemas/data.metaschema.yml +400 -0
  59. simtools/schemas/metadata.metaschema.yml +566 -0
  60. simtools/simtel/simtel_config_writer.py +14 -5
  61. simtools/simtel/simtel_histograms.py +266 -83
  62. simtools/simtel/simtel_runner.py +8 -7
  63. simtools/simtel/simtel_runner_array.py +7 -8
  64. simtools/simtel/simtel_runner_camera_efficiency.py +48 -2
  65. simtools/simtel/simtel_runner_ray_tracing.py +61 -25
  66. simtools/simulator.py +43 -50
  67. simtools/utils/general.py +232 -286
  68. simtools/utils/geometry.py +163 -0
  69. simtools/utils/names.py +294 -142
  70. simtools/visualization/legend_handlers.py +115 -9
  71. simtools/visualization/visualize.py +13 -13
  72. gammasimtools-0.5.1.dist-info/RECORD +0 -83
  73. simtools/applications/plot_simtel_histograms.py +0 -120
  74. simtools/applications/validate_schema_files.py +0 -135
  75. simtools/corsika/corsika_output_visualize.py +0 -345
  76. simtools/data_model/validate_schema.py +0 -285
  77. {gammasimtools-0.5.1.dist-info → gammasimtools-0.6.1.dist-info}/LICENSE +0 -0
  78. {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", "")