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,30 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Metadata collector for simtools.
|
|
3
|
+
|
|
4
|
+
This should be the only module in simtools with knowledge on the
|
|
5
|
+
implementation of the metadata model.
|
|
6
|
+
|
|
7
|
+
"""
|
|
1
8
|
import datetime
|
|
9
|
+
import getpass
|
|
2
10
|
import logging
|
|
3
11
|
from pathlib import Path
|
|
4
12
|
|
|
13
|
+
from astropy.table import Table
|
|
14
|
+
|
|
15
|
+
import simtools.constants
|
|
5
16
|
import simtools.utils.general as gen
|
|
6
17
|
import simtools.version
|
|
7
|
-
from simtools import
|
|
8
|
-
from simtools.
|
|
9
|
-
from simtools.utils import names
|
|
18
|
+
from simtools.data_model import metadata_model
|
|
19
|
+
from simtools.io_operations import io_handler
|
|
10
20
|
|
|
11
21
|
__all__ = ["MetadataCollector"]
|
|
12
22
|
|
|
13
23
|
|
|
14
24
|
class MetadataCollector:
|
|
15
25
|
"""
|
|
16
|
-
Collects and combines metadata associated
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
Collects and combines metadata associated to describe the current
|
|
27
|
+
simtools activity and its data products. Collect as much metadata
|
|
28
|
+
as possible from command line configuration, input data, environment,
|
|
29
|
+
schema descriptions.
|
|
30
|
+
Depends on the CTAO top-level metadata definition.
|
|
19
31
|
|
|
20
32
|
Parameters
|
|
21
33
|
----------
|
|
22
|
-
|
|
34
|
+
args_dict: dict
|
|
23
35
|
Command line parameters
|
|
36
|
+
metadata_file_name: str
|
|
37
|
+
Name of metadata file (only required when args_dict is None)
|
|
38
|
+
data_model_name: str
|
|
39
|
+
Name of data model parameter
|
|
24
40
|
|
|
25
41
|
"""
|
|
26
42
|
|
|
27
|
-
def __init__(self, args_dict):
|
|
43
|
+
def __init__(self, args_dict, metadata_file_name=None, data_model_name=None):
|
|
28
44
|
"""
|
|
29
45
|
Initialize metadata collector.
|
|
30
46
|
|
|
@@ -33,38 +49,128 @@ class MetadataCollector:
|
|
|
33
49
|
self._logger = logging.getLogger(__name__)
|
|
34
50
|
self.io_handler = io_handler.IOHandler()
|
|
35
51
|
|
|
36
|
-
self.args_dict = args_dict
|
|
52
|
+
self.args_dict = args_dict if args_dict else {}
|
|
53
|
+
self.data_model_name = data_model_name
|
|
54
|
+
self.schema_file = None
|
|
55
|
+
self.schema_dict = None
|
|
37
56
|
self.top_level_meta = gen.change_dict_keys_case(
|
|
38
|
-
data_dict=metadata_model.
|
|
57
|
+
data_dict=metadata_model.get_default_metadata_dict(), lower_case=True
|
|
39
58
|
)
|
|
40
|
-
self.
|
|
59
|
+
self.input_metadata = self._read_input_metadata_from_file(
|
|
60
|
+
metadata_file_name=metadata_file_name
|
|
61
|
+
)
|
|
62
|
+
self.collect_meta_data()
|
|
41
63
|
|
|
42
|
-
def
|
|
64
|
+
def collect_meta_data(self):
|
|
43
65
|
"""
|
|
44
66
|
Collect and verify product metadata from different sources.
|
|
45
67
|
|
|
46
68
|
"""
|
|
47
69
|
|
|
48
|
-
self.
|
|
49
|
-
|
|
70
|
+
self._fill_contact_meta(self.top_level_meta["cta"]["contact"])
|
|
71
|
+
self._fill_product_meta(self.top_level_meta["cta"]["product"])
|
|
72
|
+
self._fill_activity_meta(self.top_level_meta["cta"]["activity"])
|
|
73
|
+
self._fill_process_meta(self.top_level_meta["cta"]["process"])
|
|
74
|
+
self._fill_context_from_input_meta(self.top_level_meta["cta"]["context"])
|
|
75
|
+
self._fill_associated_elements_from_args(
|
|
76
|
+
self.top_level_meta["cta"]["context"]["associated_elements"]
|
|
50
77
|
)
|
|
51
78
|
|
|
52
|
-
|
|
79
|
+
def get_data_model_schema_file_name(self):
|
|
80
|
+
"""
|
|
81
|
+
Return data model schema file name.
|
|
82
|
+
The schema file name is taken (in this order) from the command line,
|
|
83
|
+
from the metadata file, from the data model name, or from the input
|
|
84
|
+
metadata file.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
str
|
|
89
|
+
Name of schema file.
|
|
53
90
|
|
|
54
|
-
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
# from command line
|
|
94
|
+
try:
|
|
95
|
+
if self.args_dict["schema"]:
|
|
96
|
+
self._logger.debug(f"Schema file from command line: {self.args_dict['schema']}")
|
|
97
|
+
return self.args_dict["schema"]
|
|
98
|
+
except KeyError:
|
|
99
|
+
pass
|
|
55
100
|
|
|
56
|
-
|
|
101
|
+
# from metadata
|
|
102
|
+
try:
|
|
103
|
+
if self.top_level_meta["cta"]["product"]["data"]["model"]["url"]:
|
|
104
|
+
self._logger.debug(
|
|
105
|
+
"Schema file from product metadata: "
|
|
106
|
+
f"{self.top_level_meta['cta']['product']['data']['model']['url']}"
|
|
107
|
+
)
|
|
108
|
+
return self.top_level_meta["cta"]["product"]["data"]["model"]["url"]
|
|
109
|
+
except KeyError:
|
|
110
|
+
pass
|
|
57
111
|
|
|
58
|
-
|
|
112
|
+
# from data model name
|
|
113
|
+
if self.data_model_name:
|
|
114
|
+
self._logger.debug(f"Schema file from data model name: {self.data_model_name}")
|
|
115
|
+
return f"{simtools.constants.SCHEMA_URL}{self.data_model_name}.schema.yml"
|
|
116
|
+
|
|
117
|
+
# from input metadata
|
|
118
|
+
try:
|
|
119
|
+
self._logger.debug(
|
|
120
|
+
"Schema file from input metadata: "
|
|
121
|
+
f"{self.input_metadata['cta']['product']['data']['model']['url']}"
|
|
122
|
+
)
|
|
123
|
+
return self.input_metadata["cta"]["product"]["data"]["model"]["url"]
|
|
124
|
+
except KeyError:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
self._logger.warning("No schema file found.")
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def get_data_model_schema_dict(self):
|
|
131
|
+
"""
|
|
132
|
+
Return data model schema dictionary.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
dict
|
|
137
|
+
Data model schema dictionary.
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
return gen.collect_data_from_file_or_dict(file_name=self.schema_file, in_dict=None)
|
|
143
|
+
except gen.InvalidConfigData:
|
|
144
|
+
self._logger.debug(f"No valid schema file provided ({self.schema_file}).")
|
|
145
|
+
return {}
|
|
146
|
+
|
|
147
|
+
def _fill_contact_meta(self, contact_dict):
|
|
148
|
+
"""
|
|
149
|
+
Fill contact metadata fields.
|
|
150
|
+
|
|
151
|
+
Parameters
|
|
152
|
+
----------
|
|
153
|
+
contact_dict: dict
|
|
154
|
+
Dictionary for contact metadata fields.
|
|
59
155
|
|
|
60
|
-
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
if contact_dict.get("name", None) is None:
|
|
159
|
+
contact_dict["name"] = getpass.getuser()
|
|
160
|
+
|
|
161
|
+
def _fill_associated_elements_from_args(self, associated_elements_dict):
|
|
61
162
|
"""
|
|
62
163
|
Append association metadata set through configurator.
|
|
63
164
|
|
|
165
|
+
Note
|
|
166
|
+
----
|
|
167
|
+
This function might go in future, as instrument
|
|
168
|
+
information will not be given via command line.
|
|
169
|
+
|
|
64
170
|
Parameters
|
|
65
171
|
----------
|
|
66
|
-
|
|
67
|
-
Dictionary for
|
|
172
|
+
associated_elements_dict: dict
|
|
173
|
+
Dictionary for associated elements field.
|
|
68
174
|
|
|
69
175
|
Raises
|
|
70
176
|
------
|
|
@@ -90,16 +196,16 @@ class MetadataCollector:
|
|
|
90
196
|
self._logger.error("Error reading association metadata from args")
|
|
91
197
|
raise
|
|
92
198
|
|
|
93
|
-
self._fill_context_sim_list(
|
|
199
|
+
self._fill_context_sim_list(associated_elements_dict, _association)
|
|
94
200
|
|
|
95
|
-
def
|
|
201
|
+
def _fill_context_from_input_meta(self, context_dict):
|
|
96
202
|
"""
|
|
97
|
-
Read and validate metadata from file
|
|
203
|
+
Read and validate input metadata from file and fill CONTEXT metadata fields.
|
|
98
204
|
|
|
99
205
|
Parameters
|
|
100
206
|
----------
|
|
101
|
-
|
|
102
|
-
Dictionary
|
|
207
|
+
context_dict: dict
|
|
208
|
+
Dictionary with context level metadata.
|
|
103
209
|
|
|
104
210
|
Raises
|
|
105
211
|
------
|
|
@@ -108,26 +214,99 @@ class MetadataCollector:
|
|
|
108
214
|
|
|
109
215
|
"""
|
|
110
216
|
|
|
111
|
-
|
|
112
|
-
self.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
meta_file_name=self.args_dict["input_meta"],
|
|
118
|
-
)
|
|
217
|
+
try:
|
|
218
|
+
self._merge_config_dicts(context_dict, self.input_metadata["cta"]["context"])
|
|
219
|
+
for key in ("document", "associated_elements", "associated_data"):
|
|
220
|
+
self._copy_list_type_metadata(context_dict, self.input_metadata["cta"], key)
|
|
221
|
+
except KeyError:
|
|
222
|
+
self._logger.debug("No context metadata defined in input metadata file.")
|
|
119
223
|
|
|
120
|
-
|
|
121
|
-
# list entry copies
|
|
122
|
-
for association in _input_meta["product"]["association"]:
|
|
224
|
+
try:
|
|
123
225
|
self._fill_context_sim_list(
|
|
124
|
-
|
|
226
|
+
context_dict["associated_data"], self.input_metadata["cta"]["product"]
|
|
125
227
|
)
|
|
228
|
+
except (KeyError, TypeError):
|
|
229
|
+
self._logger.debug("No input product metadata appended to associated data.")
|
|
230
|
+
|
|
231
|
+
def _read_input_metadata_from_file(self, metadata_file_name=None, observatory="CTA"):
|
|
232
|
+
"""
|
|
233
|
+
Read and validate input metadata from file. In case of an ecsv file including a
|
|
234
|
+
table, the metadata is read from the table meta data. Returns empty dict in case
|
|
235
|
+
no file is given.
|
|
236
|
+
|
|
237
|
+
Parameter
|
|
238
|
+
---------
|
|
239
|
+
metadata_file_name: str or Path
|
|
240
|
+
Name of metadata file.
|
|
241
|
+
observatory: str
|
|
242
|
+
Observatory name.
|
|
243
|
+
|
|
244
|
+
Returns
|
|
245
|
+
-------
|
|
246
|
+
dict
|
|
247
|
+
Metadata dictionary.
|
|
248
|
+
|
|
249
|
+
Raises
|
|
250
|
+
------
|
|
251
|
+
gen.InvalidConfigData, FileNotFoundError
|
|
252
|
+
if metadata cannot be read from file.
|
|
253
|
+
KeyError:
|
|
254
|
+
if metadata does not exist for the given observatory.
|
|
255
|
+
|
|
256
|
+
"""
|
|
257
|
+
|
|
126
258
|
try:
|
|
127
|
-
|
|
128
|
-
self.
|
|
129
|
-
|
|
130
|
-
|
|
259
|
+
metadata_file_name = (
|
|
260
|
+
self.args_dict.get("input_meta", None)
|
|
261
|
+
if metadata_file_name is None
|
|
262
|
+
else metadata_file_name
|
|
263
|
+
)
|
|
264
|
+
except TypeError:
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
if metadata_file_name is None:
|
|
268
|
+
self._logger.debug("No input metadata file defined.")
|
|
269
|
+
return {}
|
|
270
|
+
|
|
271
|
+
# metadata from yml or json file
|
|
272
|
+
if Path(metadata_file_name).suffix in (".yaml", ".yml", ".json"):
|
|
273
|
+
try:
|
|
274
|
+
self._logger.debug("Reading meta data from %s", metadata_file_name)
|
|
275
|
+
_input_metadata = gen.collect_data_from_file_or_dict(
|
|
276
|
+
file_name=metadata_file_name, in_dict=None
|
|
277
|
+
)
|
|
278
|
+
_json_type_metadata = {"Metadata", "metadata", "METADATA"}.intersection(
|
|
279
|
+
_input_metadata
|
|
280
|
+
)
|
|
281
|
+
if len(_json_type_metadata) == 1:
|
|
282
|
+
_input_metadata = _input_metadata[_json_type_metadata.pop()]
|
|
283
|
+
elif len(_json_type_metadata) > 1:
|
|
284
|
+
self._logger.error(
|
|
285
|
+
"More than one metadata entry found in %s", metadata_file_name
|
|
286
|
+
)
|
|
287
|
+
raise gen.InvalidConfigData
|
|
288
|
+
except (gen.InvalidConfigData, FileNotFoundError):
|
|
289
|
+
self._logger.error("Failed reading metadata from %s", metadata_file_name)
|
|
290
|
+
raise
|
|
291
|
+
# metadata from table meta in ecsv file
|
|
292
|
+
elif Path(metadata_file_name).suffix == ".ecsv":
|
|
293
|
+
try:
|
|
294
|
+
_input_metadata = {observatory: Table.read(metadata_file_name).meta[observatory]}
|
|
295
|
+
except (FileNotFoundError, KeyError):
|
|
296
|
+
self._logger.error(
|
|
297
|
+
"Failed reading metadata for %s from %s", observatory, metadata_file_name
|
|
298
|
+
)
|
|
299
|
+
raise
|
|
300
|
+
else:
|
|
301
|
+
self._logger.error("Unknown metadata file format: %s", metadata_file_name)
|
|
302
|
+
raise gen.InvalidConfigData
|
|
303
|
+
|
|
304
|
+
metadata_model.validate_schema(_input_metadata, None)
|
|
305
|
+
|
|
306
|
+
return gen.change_dict_keys_case(
|
|
307
|
+
self._process_metadata_from_file(_input_metadata),
|
|
308
|
+
lower_case=True,
|
|
309
|
+
)
|
|
131
310
|
|
|
132
311
|
def _fill_product_meta(self, product_dict):
|
|
133
312
|
"""
|
|
@@ -146,69 +325,43 @@ class MetadataCollector:
|
|
|
146
325
|
|
|
147
326
|
"""
|
|
148
327
|
|
|
328
|
+
self.schema_file = self.get_data_model_schema_file_name()
|
|
329
|
+
self.schema_dict = self.get_data_model_schema_dict()
|
|
330
|
+
|
|
149
331
|
product_dict["id"] = self.args_dict.get("activity_id", "UNDEFINED_ACTIVITY_ID")
|
|
150
|
-
|
|
332
|
+
product_dict["creation_time"] = datetime.datetime.now().isoformat(timespec="seconds")
|
|
333
|
+
product_dict["description"] = self.schema_dict.get("description", None)
|
|
151
334
|
|
|
335
|
+
# DATA:CATEGORY
|
|
152
336
|
product_dict["data"]["category"] = "SIM"
|
|
153
337
|
product_dict["data"]["level"] = "R1"
|
|
154
|
-
product_dict["data"]["type"] = "
|
|
338
|
+
product_dict["data"]["type"] = "Service"
|
|
339
|
+
try:
|
|
340
|
+
product_dict["data"]["association"] = self.schema_dict["instrument"]["class"]
|
|
341
|
+
except KeyError:
|
|
342
|
+
pass
|
|
343
|
+
|
|
344
|
+
# DATA:MODEL
|
|
345
|
+
helper_dict = {"name": "name", "version": "version", "type": "base_schema"}
|
|
346
|
+
for key, value in helper_dict.items():
|
|
347
|
+
product_dict["data"]["model"][key] = self.schema_dict.get(value, None)
|
|
348
|
+
product_dict["data"]["model"]["url"] = self.schema_file
|
|
155
349
|
|
|
156
|
-
_schema_dict = self._collect_schema_dict()
|
|
157
|
-
product_dict["data"]["model"]["name"] = _schema_dict.get("name", "simpipe-schema")
|
|
158
|
-
product_dict["data"]["model"]["version"] = _schema_dict.get("version", "0.0.0")
|
|
159
350
|
product_dict["format"] = self.args_dict.get("output_file_format", None)
|
|
160
351
|
product_dict["filename"] = str(self.args_dict.get("output_file", None))
|
|
161
352
|
|
|
162
|
-
def
|
|
353
|
+
def _fill_process_meta(self, process_dict):
|
|
163
354
|
"""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
The schema configuration parameter points to a directory or a file.
|
|
167
|
-
For the case of a directory, the schema file is assumed to be named
|
|
168
|
-
<parameter_name>.schema.yml.
|
|
169
|
-
|
|
170
|
-
Returns
|
|
171
|
-
-------
|
|
172
|
-
dict
|
|
173
|
-
Dictionary containing schema metadata.
|
|
174
|
-
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
_schema = self.args_dict.get("schema", "")
|
|
178
|
-
if Path(_schema).is_dir():
|
|
179
|
-
try:
|
|
180
|
-
_data_dict = gen.collect_data_from_yaml_or_dict(
|
|
181
|
-
in_yaml=self.args_dict.get("input", None), in_dict=None, allow_empty=True
|
|
182
|
-
)
|
|
183
|
-
return gen.collect_dict_from_file(
|
|
184
|
-
file_path=_schema,
|
|
185
|
-
file_name=f"{_data_dict['name']}.schema.yml",
|
|
186
|
-
)
|
|
187
|
-
except (TypeError, KeyError):
|
|
188
|
-
return {}
|
|
189
|
-
return gen.collect_dict_from_file(_schema)
|
|
190
|
-
|
|
191
|
-
@staticmethod
|
|
192
|
-
def _fill_association_id(association_dict):
|
|
193
|
-
"""
|
|
194
|
-
Fill association id from site and telescope class, type, subtype.
|
|
355
|
+
Fill process fields in metadata.
|
|
195
356
|
|
|
196
357
|
Parameters
|
|
197
358
|
----------
|
|
198
|
-
|
|
199
|
-
|
|
359
|
+
process_dict: dict
|
|
360
|
+
Dictionary for process metadata fields.
|
|
200
361
|
|
|
201
362
|
"""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
association["id"] = names.simtools_instrument_name(
|
|
205
|
-
site=association["site"],
|
|
206
|
-
telescope_class_name=association["class"],
|
|
207
|
-
sub_system_name=association["type"],
|
|
208
|
-
telescope_id_name=association.get("subtype", "D"),
|
|
209
|
-
)
|
|
210
|
-
except ValueError:
|
|
211
|
-
association["id"] = None
|
|
363
|
+
|
|
364
|
+
process_dict["type"] = "simulation"
|
|
212
365
|
|
|
213
366
|
def _fill_activity_meta(self, activity_dict):
|
|
214
367
|
"""
|
|
@@ -217,11 +370,13 @@ class MetadataCollector:
|
|
|
217
370
|
Parameters
|
|
218
371
|
----------
|
|
219
372
|
activity_dict: dict
|
|
220
|
-
Dictionary for top-level
|
|
373
|
+
Dictionary for top-level activity metadata.
|
|
221
374
|
|
|
222
375
|
"""
|
|
223
376
|
|
|
224
377
|
activity_dict["name"] = self.args_dict.get("label", None)
|
|
378
|
+
activity_dict["type"] = "software"
|
|
379
|
+
activity_dict["id"] = self.args_dict.get("activity_id", "UNDEFINED_ACTIVITY_ID")
|
|
225
380
|
activity_dict["start"] = datetime.datetime.now().isoformat(timespec="seconds")
|
|
226
381
|
activity_dict["end"] = activity_dict["start"]
|
|
227
382
|
activity_dict["software"]["name"] = "simtools"
|
|
@@ -265,47 +420,120 @@ class MetadataCollector:
|
|
|
265
420
|
self._logger.error("Error merging dictionaries")
|
|
266
421
|
raise
|
|
267
422
|
|
|
268
|
-
def
|
|
423
|
+
def _fill_context_sim_list(self, meta_list, new_entry_dict):
|
|
269
424
|
"""
|
|
270
|
-
|
|
425
|
+
Fill list-type entries into metadata. Take into account the first list entry is the default
|
|
426
|
+
value filled with Nones.
|
|
427
|
+
|
|
428
|
+
Parameters
|
|
429
|
+
----------
|
|
430
|
+
meta_list: list
|
|
431
|
+
List of metadata entries.
|
|
432
|
+
new_entry_dict: dict
|
|
433
|
+
New metadata entry to be added to meta_list.
|
|
271
434
|
|
|
272
435
|
Returns
|
|
273
436
|
-------
|
|
274
|
-
|
|
275
|
-
|
|
437
|
+
list
|
|
438
|
+
Updated meta list.
|
|
276
439
|
|
|
277
|
-
Raises
|
|
278
|
-
------
|
|
279
|
-
KeyError
|
|
280
|
-
if missing description of INPUT_DATA
|
|
281
440
|
"""
|
|
282
441
|
|
|
442
|
+
if len(new_entry_dict) == 0:
|
|
443
|
+
return []
|
|
283
444
|
try:
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
445
|
+
if self._all_values_none(meta_list[0]):
|
|
446
|
+
meta_list[0] = new_entry_dict
|
|
447
|
+
else:
|
|
448
|
+
meta_list.append(new_entry_dict)
|
|
449
|
+
except (TypeError, IndexError):
|
|
450
|
+
meta_list = [new_entry_dict]
|
|
451
|
+
return meta_list
|
|
452
|
+
|
|
453
|
+
def _process_metadata_from_file(self, meta_dict):
|
|
454
|
+
"""
|
|
455
|
+
Process metadata from file to ensure compatibility with metadata model.
|
|
456
|
+
Changes keys to lower case and removes line feeds from description fields.
|
|
457
|
+
|
|
458
|
+
Parameters
|
|
459
|
+
----------
|
|
460
|
+
meta_dict: dict
|
|
461
|
+
Input metadata dictionary.
|
|
462
|
+
|
|
463
|
+
Returns
|
|
464
|
+
-------
|
|
465
|
+
dict
|
|
466
|
+
Metadata dictionary.
|
|
467
|
+
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
meta_dict = gen.change_dict_keys_case(meta_dict, True)
|
|
471
|
+
try:
|
|
472
|
+
meta_dict["cta"]["product"]["description"] = self._remove_line_feed(
|
|
473
|
+
meta_dict["cta"]["product"]["description"]
|
|
474
|
+
)
|
|
475
|
+
except (KeyError, AttributeError):
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
return meta_dict
|
|
288
479
|
|
|
289
480
|
@staticmethod
|
|
290
|
-
def
|
|
481
|
+
def _remove_line_feed(string):
|
|
291
482
|
"""
|
|
292
|
-
|
|
293
|
-
|
|
483
|
+
Remove all line feeds from a string
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
str
|
|
488
|
+
input string
|
|
294
489
|
|
|
295
490
|
Returns
|
|
296
491
|
-------
|
|
297
|
-
|
|
298
|
-
|
|
492
|
+
str
|
|
493
|
+
with line feeds removed
|
|
494
|
+
"""
|
|
495
|
+
|
|
496
|
+
return string.replace("\n", " ").replace("\r", "").replace(" ", " ")
|
|
497
|
+
|
|
498
|
+
def _copy_list_type_metadata(self, context_dict, _input_metadata, key):
|
|
499
|
+
"""
|
|
500
|
+
Copy list-type metadata from file.
|
|
501
|
+
Very fine tuned.
|
|
502
|
+
|
|
503
|
+
Parameters
|
|
504
|
+
----------
|
|
505
|
+
context_dict: dict
|
|
506
|
+
Dictionary for top level metadata (context level)
|
|
507
|
+
_input_metadata: dict
|
|
508
|
+
Dictionary for metadata from file.
|
|
509
|
+
key: str
|
|
510
|
+
Key for metadata entry.
|
|
299
511
|
|
|
300
512
|
"""
|
|
301
513
|
|
|
302
|
-
if len(new_entry_dict) == 0:
|
|
303
|
-
return []
|
|
304
514
|
try:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
515
|
+
for document in _input_metadata["context"][key]:
|
|
516
|
+
self._fill_context_sim_list(context_dict[key], document)
|
|
517
|
+
except KeyError:
|
|
518
|
+
pass
|
|
519
|
+
|
|
520
|
+
def _all_values_none(self, input_dict):
|
|
521
|
+
"""
|
|
522
|
+
Check recursively if all values in a dictionary are None.
|
|
523
|
+
|
|
524
|
+
Parameters
|
|
525
|
+
----------
|
|
526
|
+
input_dict: dict
|
|
527
|
+
Input dictionary.
|
|
528
|
+
|
|
529
|
+
Returns
|
|
530
|
+
-------
|
|
531
|
+
bool
|
|
532
|
+
True if all entries are None, False otherwise.
|
|
533
|
+
|
|
534
|
+
"""
|
|
535
|
+
|
|
536
|
+
if not isinstance(input_dict, dict):
|
|
537
|
+
return input_dict is None
|
|
538
|
+
|
|
539
|
+
return all(self._all_values_none(value) for value in input_dict.values())
|