dkist-processing-common 11.7.1rc1__py3-none-any.whl → 11.8.0rc1__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.
- changelog/245.feature.1.rst +1 -0
- dkist_processing_common/models/constants.py +333 -21
- dkist_processing_common/models/fits_access.py +16 -25
- dkist_processing_common/parsers/average_bud.py +48 -0
- dkist_processing_common/parsers/experiment_id_bud.py +8 -4
- dkist_processing_common/parsers/id_bud.py +35 -19
- dkist_processing_common/parsers/l0_fits_access.py +3 -3
- dkist_processing_common/parsers/l1_fits_access.py +47 -21
- dkist_processing_common/parsers/near_bud.py +4 -4
- dkist_processing_common/parsers/observing_program_id_bud.py +24 -0
- dkist_processing_common/parsers/proposal_id_bud.py +11 -5
- dkist_processing_common/parsers/single_value_single_key_flower.py +0 -1
- dkist_processing_common/parsers/time.py +147 -27
- dkist_processing_common/tasks/mixin/quality/_metrics.py +6 -4
- dkist_processing_common/tasks/parse_l0_input_data.py +246 -1
- dkist_processing_common/tasks/trial_catalog.py +38 -0
- dkist_processing_common/tests/mock_metadata_store.py +39 -0
- dkist_processing_common/tests/test_fits_access.py +19 -44
- dkist_processing_common/tests/test_input_dataset.py +1 -37
- dkist_processing_common/tests/test_parse_l0_input_data.py +45 -5
- dkist_processing_common/tests/test_quality_mixin.py +3 -11
- dkist_processing_common/tests/test_stems.py +162 -10
- dkist_processing_common/tests/test_task_parsing.py +6 -6
- dkist_processing_common/tests/test_trial_catalog.py +72 -2
- {dkist_processing_common-11.7.1rc1.dist-info → dkist_processing_common-11.8.0rc1.dist-info}/METADATA +5 -5
- {dkist_processing_common-11.7.1rc1.dist-info → dkist_processing_common-11.8.0rc1.dist-info}/RECORD +28 -26
- changelog/271.misc.rst +0 -1
- {dkist_processing_common-11.7.1rc1.dist-info → dkist_processing_common-11.8.0rc1.dist-info}/WHEEL +0 -0
- {dkist_processing_common-11.7.1rc1.dist-info → dkist_processing_common-11.8.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -34,16 +34,23 @@ from dkist_processing_common.models.flower_pot import Stem
|
|
|
34
34
|
from dkist_processing_common.models.flower_pot import Thorn
|
|
35
35
|
from dkist_processing_common.models.tags import Tag
|
|
36
36
|
from dkist_processing_common.models.task_name import TaskName
|
|
37
|
+
from dkist_processing_common.parsers.average_bud import TaskAverageBud
|
|
37
38
|
from dkist_processing_common.parsers.experiment_id_bud import ContributingExperimentIdsBud
|
|
38
39
|
from dkist_processing_common.parsers.experiment_id_bud import ExperimentIdBud
|
|
40
|
+
from dkist_processing_common.parsers.observing_program_id_bud import (
|
|
41
|
+
TaskContributingObservingProgramExecutionIdsBud,
|
|
42
|
+
)
|
|
39
43
|
from dkist_processing_common.parsers.proposal_id_bud import ContributingProposalIdsBud
|
|
40
44
|
from dkist_processing_common.parsers.proposal_id_bud import ProposalIdBud
|
|
45
|
+
from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
|
|
41
46
|
from dkist_processing_common.parsers.time import AverageCadenceBud
|
|
42
47
|
from dkist_processing_common.parsers.time import MaximumCadenceBud
|
|
43
48
|
from dkist_processing_common.parsers.time import MinimumCadenceBud
|
|
49
|
+
from dkist_processing_common.parsers.time import TaskDateBeginBud
|
|
44
50
|
from dkist_processing_common.parsers.time import TaskExposureTimesBud
|
|
45
51
|
from dkist_processing_common.parsers.time import TaskReadoutExpTimesBud
|
|
46
52
|
from dkist_processing_common.parsers.time import VarianceCadenceBud
|
|
53
|
+
from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
|
|
47
54
|
from dkist_processing_common.parsers.unique_bud import UniqueBud
|
|
48
55
|
from dkist_processing_common.tasks.base import WorkflowTaskBase
|
|
49
56
|
|
|
@@ -59,9 +66,247 @@ logger = logging.getLogger(__name__)
|
|
|
59
66
|
S = TypeVar("S", bound=Stem)
|
|
60
67
|
|
|
61
68
|
|
|
69
|
+
def dataset_extra_bud_factory() -> list[S]:
|
|
70
|
+
"""Provide constant buds for use in dataset extras."""
|
|
71
|
+
return [
|
|
72
|
+
UniqueBud(constant_name=BudName.camera_id, metadata_key=MetadataKey.camera_id),
|
|
73
|
+
UniqueBud(constant_name=BudName.camera_name, metadata_key=MetadataKey.camera_name),
|
|
74
|
+
UniqueBud(
|
|
75
|
+
constant_name=BudName.camera_bit_depth, metadata_key=MetadataKey.camera_bit_depth
|
|
76
|
+
),
|
|
77
|
+
UniqueBud(
|
|
78
|
+
constant_name=BudName.hardware_binning_x, metadata_key=MetadataKey.hardware_binning_x
|
|
79
|
+
),
|
|
80
|
+
UniqueBud(
|
|
81
|
+
constant_name=BudName.hardware_binning_y, metadata_key=MetadataKey.hardware_binning_x
|
|
82
|
+
),
|
|
83
|
+
UniqueBud(
|
|
84
|
+
constant_name=BudName.software_binning_x, metadata_key=MetadataKey.software_binning_x
|
|
85
|
+
),
|
|
86
|
+
UniqueBud(
|
|
87
|
+
constant_name=BudName.software_binning_y, metadata_key=MetadataKey.software_binning_y
|
|
88
|
+
),
|
|
89
|
+
UniqueBud(
|
|
90
|
+
constant_name=BudName.hls_version,
|
|
91
|
+
metadata_key=MetadataKey.hls_version,
|
|
92
|
+
),
|
|
93
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
94
|
+
constant_name=BudName.dark_observing_program_execution_ids,
|
|
95
|
+
ip_task_types=TaskName.dark,
|
|
96
|
+
),
|
|
97
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
98
|
+
constant_name=BudName.solar_gain_observing_program_execution_ids,
|
|
99
|
+
ip_task_types=TaskName.solar_gain,
|
|
100
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
101
|
+
),
|
|
102
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
103
|
+
constant_name=BudName.polcal_observing_program_execution_ids,
|
|
104
|
+
ip_task_types=TaskName.polcal,
|
|
105
|
+
),
|
|
106
|
+
TaskUniqueBud(
|
|
107
|
+
constant_name=BudName.solar_gain_num_raw_frames_per_fpa,
|
|
108
|
+
metadata_key=MetadataKey.num_raw_frames_per_fpa,
|
|
109
|
+
ip_task_types=TaskName.solar_gain,
|
|
110
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
111
|
+
),
|
|
112
|
+
TaskUniqueBud(
|
|
113
|
+
constant_name=BudName.polcal_num_raw_frames_per_fpa,
|
|
114
|
+
metadata_key=MetadataKey.num_raw_frames_per_fpa,
|
|
115
|
+
ip_task_types=TaskName.polcal,
|
|
116
|
+
),
|
|
117
|
+
TaskUniqueBud(
|
|
118
|
+
constant_name=BudName.solar_gain_telescope_tracking_mode,
|
|
119
|
+
metadata_key=MetadataKey.telescope_tracking_mode,
|
|
120
|
+
ip_task_types=TaskName.solar_gain,
|
|
121
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
122
|
+
),
|
|
123
|
+
TaskUniqueBud(
|
|
124
|
+
constant_name=BudName.polcal_telescope_tracking_mode,
|
|
125
|
+
metadata_key=MetadataKey.telescope_tracking_mode,
|
|
126
|
+
ip_task_types=TaskName.polcal,
|
|
127
|
+
),
|
|
128
|
+
TaskUniqueBud(
|
|
129
|
+
constant_name=BudName.solar_gain_coude_table_tracking_mode,
|
|
130
|
+
metadata_key=MetadataKey.coude_table_tracking_mode,
|
|
131
|
+
ip_task_types=TaskName.solar_gain,
|
|
132
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
133
|
+
),
|
|
134
|
+
TaskUniqueBud(
|
|
135
|
+
constant_name=BudName.polcal_coude_table_tracking_mode,
|
|
136
|
+
metadata_key=MetadataKey.coude_table_tracking_mode,
|
|
137
|
+
ip_task_types=TaskName.polcal,
|
|
138
|
+
),
|
|
139
|
+
TaskUniqueBud(
|
|
140
|
+
constant_name=BudName.solar_gain_telescope_scanning_mode,
|
|
141
|
+
metadata_key=MetadataKey.telescope_scanning_mode,
|
|
142
|
+
ip_task_types=TaskName.solar_gain,
|
|
143
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
144
|
+
),
|
|
145
|
+
TaskUniqueBud(
|
|
146
|
+
constant_name=BudName.polcal_telescope_scanning_mode,
|
|
147
|
+
metadata_key=MetadataKey.telescope_scanning_mode,
|
|
148
|
+
ip_task_types=TaskName.polcal,
|
|
149
|
+
),
|
|
150
|
+
TaskUniqueBud(
|
|
151
|
+
constant_name=BudName.dark_gos_level3_status,
|
|
152
|
+
metadata_key=MetadataKey.gos_level3_status,
|
|
153
|
+
ip_task_types=TaskName.dark,
|
|
154
|
+
),
|
|
155
|
+
TaskUniqueBud(
|
|
156
|
+
constant_name=BudName.solar_gain_gos_level3_status,
|
|
157
|
+
metadata_key=MetadataKey.gos_level3_status,
|
|
158
|
+
ip_task_types=TaskName.solar_gain,
|
|
159
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
160
|
+
),
|
|
161
|
+
TaskUniqueBud(
|
|
162
|
+
constant_name=BudName.dark_gos_level3_lamp_status,
|
|
163
|
+
metadata_key=MetadataKey.gos_level3_lamp_status,
|
|
164
|
+
ip_task_types=TaskName.dark,
|
|
165
|
+
),
|
|
166
|
+
TaskUniqueBud(
|
|
167
|
+
constant_name=BudName.solar_gain_gos_level3_lamp_status,
|
|
168
|
+
metadata_key=MetadataKey.gos_level3_lamp_status,
|
|
169
|
+
ip_task_types=TaskName.solar_gain,
|
|
170
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
171
|
+
),
|
|
172
|
+
TaskUniqueBud(
|
|
173
|
+
constant_name=BudName.dark_gos_polarizer_status,
|
|
174
|
+
metadata_key=MetadataKey.gos_polarizer_status,
|
|
175
|
+
ip_task_types=TaskName.dark,
|
|
176
|
+
),
|
|
177
|
+
TaskUniqueBud(
|
|
178
|
+
constant_name=BudName.solar_gain_gos_polarizer_status,
|
|
179
|
+
metadata_key=MetadataKey.gos_polarizer_status,
|
|
180
|
+
ip_task_types=TaskName.solar_gain,
|
|
181
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
182
|
+
),
|
|
183
|
+
TaskUniqueBud(
|
|
184
|
+
constant_name=BudName.dark_gos_polarizer_angle,
|
|
185
|
+
metadata_key=MetadataKey.gos_polarizer_angle,
|
|
186
|
+
ip_task_types=TaskName.dark,
|
|
187
|
+
),
|
|
188
|
+
TaskUniqueBud(
|
|
189
|
+
constant_name=BudName.solar_gain_gos_polarizer_angle,
|
|
190
|
+
metadata_key=MetadataKey.gos_polarizer_angle,
|
|
191
|
+
ip_task_types=TaskName.solar_gain,
|
|
192
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
193
|
+
),
|
|
194
|
+
TaskUniqueBud(
|
|
195
|
+
constant_name=BudName.dark_gos_retarder_status,
|
|
196
|
+
metadata_key=MetadataKey.gos_retarder_status,
|
|
197
|
+
ip_task_types=TaskName.dark,
|
|
198
|
+
),
|
|
199
|
+
TaskUniqueBud(
|
|
200
|
+
constant_name=BudName.solar_gain_gos_retarder_status,
|
|
201
|
+
metadata_key=MetadataKey.gos_retarder_status,
|
|
202
|
+
ip_task_types=TaskName.solar_gain,
|
|
203
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
204
|
+
),
|
|
205
|
+
TaskUniqueBud(
|
|
206
|
+
constant_name=BudName.dark_gos_retarder_angle,
|
|
207
|
+
metadata_key=MetadataKey.gos_retarder_angle,
|
|
208
|
+
ip_task_types=TaskName.dark,
|
|
209
|
+
),
|
|
210
|
+
TaskUniqueBud(
|
|
211
|
+
constant_name=BudName.solar_gain_gos_retarder_angle,
|
|
212
|
+
metadata_key=MetadataKey.gos_retarder_angle,
|
|
213
|
+
ip_task_types=TaskName.solar_gain,
|
|
214
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
215
|
+
),
|
|
216
|
+
TaskUniqueBud(
|
|
217
|
+
constant_name=BudName.dark_gos_level0_status,
|
|
218
|
+
metadata_key=MetadataKey.gos_level0_status,
|
|
219
|
+
ip_task_types=TaskName.dark,
|
|
220
|
+
),
|
|
221
|
+
TaskUniqueBud(
|
|
222
|
+
constant_name=BudName.solar_gain_gos_level0_status,
|
|
223
|
+
metadata_key=MetadataKey.gos_level0_status,
|
|
224
|
+
ip_task_types=TaskName.solar_gain,
|
|
225
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
226
|
+
),
|
|
227
|
+
TaskAverageBud(
|
|
228
|
+
constant_name=BudName.dark_average_light_level,
|
|
229
|
+
metadata_key=MetadataKey.light_level,
|
|
230
|
+
ip_task_types=TaskName.dark,
|
|
231
|
+
),
|
|
232
|
+
TaskAverageBud(
|
|
233
|
+
constant_name=BudName.solar_gain_average_light_level,
|
|
234
|
+
metadata_key=MetadataKey.light_level,
|
|
235
|
+
ip_task_types=TaskName.solar_gain,
|
|
236
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
237
|
+
),
|
|
238
|
+
TaskAverageBud(
|
|
239
|
+
constant_name=BudName.polcal_average_light_level,
|
|
240
|
+
metadata_key=MetadataKey.light_level,
|
|
241
|
+
ip_task_types=TaskName.polcal,
|
|
242
|
+
),
|
|
243
|
+
TaskAverageBud(
|
|
244
|
+
constant_name=BudName.dark_average_telescope_elevation,
|
|
245
|
+
metadata_key=MetadataKey.elevation,
|
|
246
|
+
ip_task_types=TaskName.dark,
|
|
247
|
+
),
|
|
248
|
+
TaskAverageBud(
|
|
249
|
+
constant_name=BudName.solar_gain_average_telescope_elevation,
|
|
250
|
+
metadata_key=MetadataKey.elevation,
|
|
251
|
+
ip_task_types=TaskName.solar_gain,
|
|
252
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
253
|
+
),
|
|
254
|
+
TaskAverageBud(
|
|
255
|
+
constant_name=BudName.polcal_average_telescope_elevation,
|
|
256
|
+
metadata_key=MetadataKey.elevation,
|
|
257
|
+
ip_task_types=TaskName.polcal,
|
|
258
|
+
),
|
|
259
|
+
TaskAverageBud(
|
|
260
|
+
constant_name=BudName.dark_average_coude_table_angle,
|
|
261
|
+
metadata_key=MetadataKey.table_angle,
|
|
262
|
+
ip_task_types=TaskName.dark,
|
|
263
|
+
),
|
|
264
|
+
TaskAverageBud(
|
|
265
|
+
constant_name=BudName.solar_gain_average_coude_table_angle,
|
|
266
|
+
metadata_key=MetadataKey.table_angle,
|
|
267
|
+
ip_task_types=TaskName.solar_gain,
|
|
268
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
269
|
+
),
|
|
270
|
+
TaskAverageBud(
|
|
271
|
+
constant_name=BudName.polcal_average_coude_table_angle,
|
|
272
|
+
metadata_key=MetadataKey.table_angle,
|
|
273
|
+
ip_task_types=TaskName.polcal,
|
|
274
|
+
),
|
|
275
|
+
TaskAverageBud(
|
|
276
|
+
constant_name=BudName.dark_average_telescope_azimuth,
|
|
277
|
+
metadata_key=MetadataKey.azimuth,
|
|
278
|
+
ip_task_types=TaskName.dark,
|
|
279
|
+
),
|
|
280
|
+
TaskAverageBud(
|
|
281
|
+
constant_name=BudName.solar_gain_average_telescope_azimuth,
|
|
282
|
+
metadata_key=MetadataKey.azimuth,
|
|
283
|
+
ip_task_types=TaskName.solar_gain,
|
|
284
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
285
|
+
),
|
|
286
|
+
TaskAverageBud(
|
|
287
|
+
constant_name=BudName.polcal_average_telescope_azimuth,
|
|
288
|
+
metadata_key=MetadataKey.azimuth,
|
|
289
|
+
ip_task_types=TaskName.polcal,
|
|
290
|
+
),
|
|
291
|
+
TaskDateBeginBud(
|
|
292
|
+
constant_name=BudName.dark_date_begin,
|
|
293
|
+
ip_task_types=TaskName.dark,
|
|
294
|
+
),
|
|
295
|
+
TaskDateBeginBud(
|
|
296
|
+
constant_name=BudName.solar_gain_date_begin,
|
|
297
|
+
ip_task_types=TaskName.solar_gain,
|
|
298
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
299
|
+
),
|
|
300
|
+
TaskDateBeginBud(
|
|
301
|
+
constant_name=BudName.polcal_date_begin,
|
|
302
|
+
ip_task_types=TaskName.polcal,
|
|
303
|
+
),
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
|
|
62
307
|
def default_constant_bud_factory() -> list[S]:
|
|
63
308
|
"""Provide default constant buds for use in common parsing tasks."""
|
|
64
|
-
return [
|
|
309
|
+
return dataset_extra_bud_factory() + [
|
|
65
310
|
UniqueBud(constant_name=BudName.instrument, metadata_key=MetadataKey.instrument),
|
|
66
311
|
ProposalIdBud(),
|
|
67
312
|
ContributingProposalIdsBud(),
|
|
@@ -5,15 +5,18 @@ import logging
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from itertools import chain
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
8
9
|
from typing import Generator
|
|
9
10
|
from uuid import uuid4
|
|
10
11
|
|
|
11
12
|
from dkist_processing_common.codecs.asdf import asdf_fileobj_encoder
|
|
13
|
+
from dkist_processing_common.codecs.basemodel import basemodel_decoder
|
|
12
14
|
from dkist_processing_common.codecs.fits import fits_access_decoder
|
|
13
15
|
from dkist_processing_common.codecs.json import json_encoder
|
|
14
16
|
from dkist_processing_common.codecs.path import path_decoder
|
|
15
17
|
from dkist_processing_common.codecs.quality import quality_data_decoder
|
|
16
18
|
from dkist_processing_common.models.fits_access import FitsAccessBase
|
|
19
|
+
from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
|
|
17
20
|
from dkist_processing_common.models.tags import Tag
|
|
18
21
|
from dkist_processing_common.tasks.output_data_base import OutputDataBase
|
|
19
22
|
|
|
@@ -138,11 +141,15 @@ class CreateTrialAsdf(OutputDataBase):
|
|
|
138
141
|
|
|
139
142
|
def run(self) -> None:
|
|
140
143
|
"""Generate an ASDF file simulating the ASDF file that would be produced when cataloging the dataset."""
|
|
144
|
+
with self.telemetry_span("Collate input dataset parameters"):
|
|
145
|
+
parameters = self.parse_input_dataset_parameters()
|
|
146
|
+
|
|
141
147
|
with self.telemetry_span("Generate ASDF tree"):
|
|
142
148
|
tree = asdf_tree_from_filenames(
|
|
143
149
|
filenames=self.absolute_output_frame_paths,
|
|
144
150
|
hdu=1, # compressed
|
|
145
151
|
relative_to=self.scratch.workflow_base_path,
|
|
152
|
+
parameters=parameters,
|
|
146
153
|
)
|
|
147
154
|
|
|
148
155
|
trial_history = [
|
|
@@ -169,6 +176,37 @@ class CreateTrialAsdf(OutputDataBase):
|
|
|
169
176
|
),
|
|
170
177
|
)
|
|
171
178
|
|
|
179
|
+
def parse_input_dataset_parameters(self) -> list[dict[str, Any]]:
|
|
180
|
+
"""
|
|
181
|
+
Return the parameters associated with the dataset.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
list[dict[str, Any]]
|
|
186
|
+
A list of dictionaries, each containing a parameter name and its values.
|
|
187
|
+
|
|
188
|
+
Raises
|
|
189
|
+
------
|
|
190
|
+
ValueError
|
|
191
|
+
If there is not exactly one ``InputDatasetPartDocumentList`` found.
|
|
192
|
+
"""
|
|
193
|
+
part_docs_iter = self.read(
|
|
194
|
+
tags=Tag.input_dataset_parameters(),
|
|
195
|
+
decoder=basemodel_decoder,
|
|
196
|
+
model=InputDatasetPartDocumentList,
|
|
197
|
+
)
|
|
198
|
+
docs = list(part_docs_iter)
|
|
199
|
+
|
|
200
|
+
if not docs:
|
|
201
|
+
logger.warning("No parameter list decoded from files")
|
|
202
|
+
return []
|
|
203
|
+
|
|
204
|
+
if len(docs) > 1:
|
|
205
|
+
raise ValueError(f"Expected 1 parameter list, found {len(docs)}")
|
|
206
|
+
|
|
207
|
+
parameters = docs[0].model_dump(by_alias=True).get("doc_list", [])
|
|
208
|
+
return parameters
|
|
209
|
+
|
|
172
210
|
|
|
173
211
|
class CreateTrialQualityReport(OutputDataBase):
|
|
174
212
|
"""
|
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
from abc import ABC
|
|
7
7
|
from abc import abstractmethod
|
|
8
8
|
from datetime import datetime
|
|
9
|
+
from datetime import timedelta
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from uuid import uuid4
|
|
11
12
|
|
|
@@ -234,3 +235,41 @@ def fake_gql_client():
|
|
|
234
235
|
Convenience fixture for default mock GQL client. To customize, use fake_gql_client_factory.
|
|
235
236
|
"""
|
|
236
237
|
return fake_gql_client_factory()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def input_dataset_parameters_part_factory(
|
|
241
|
+
parameter_count: int = 1,
|
|
242
|
+
parameter_value_count: int = 1,
|
|
243
|
+
has_date: bool = False,
|
|
244
|
+
has_file: bool = False,
|
|
245
|
+
) -> list[dict]:
|
|
246
|
+
"""Create a mock InputDatasetPartDocumentList with parameters."""
|
|
247
|
+
result = [
|
|
248
|
+
{
|
|
249
|
+
"parameterName": uuid4().hex[:6],
|
|
250
|
+
"parameterValues": [
|
|
251
|
+
{"parameterValueId": i, "parameterValue": json.dumps(uuid4().hex)}
|
|
252
|
+
for i in range(parameter_value_count)
|
|
253
|
+
],
|
|
254
|
+
}
|
|
255
|
+
for _ in range(parameter_count)
|
|
256
|
+
]
|
|
257
|
+
if has_date:
|
|
258
|
+
base = datetime(2018, 9, 14, 0, 0, 0) # This date is before any possible start dates
|
|
259
|
+
for parameter_index, data in enumerate(result):
|
|
260
|
+
for item in data["parameterValues"]:
|
|
261
|
+
dt = base + timedelta(days=parameter_index)
|
|
262
|
+
item["parameterValueStartDate"] = dt.isoformat()
|
|
263
|
+
if has_file:
|
|
264
|
+
for data in result:
|
|
265
|
+
param_list = data["parameterValues"]
|
|
266
|
+
for item in param_list:
|
|
267
|
+
item["parameterValue"] = json.dumps(
|
|
268
|
+
{
|
|
269
|
+
"__file__": {
|
|
270
|
+
"bucket": "data",
|
|
271
|
+
"objectKey": f"parameters/{data['parameterName']}/{uuid4().hex}.dat",
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
return result
|
|
@@ -4,7 +4,6 @@ import numpy as np
|
|
|
4
4
|
import pytest
|
|
5
5
|
from astropy.io import fits
|
|
6
6
|
|
|
7
|
-
from dkist_processing_common.models.fits_access import NOT_FOUND_MESSAGE
|
|
8
7
|
from dkist_processing_common.models.fits_access import FitsAccessBase
|
|
9
8
|
from dkist_processing_common.models.fits_access import MetadataKey
|
|
10
9
|
from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
|
|
@@ -99,30 +98,16 @@ def hdu_with_no_data(complete_common_header):
|
|
|
99
98
|
@pytest.fixture()
|
|
100
99
|
def hdu_with_incomplete_common_header(complete_common_header):
|
|
101
100
|
"""
|
|
102
|
-
An HDU with data and a header missing
|
|
101
|
+
An HDU with data and a header missing two of the expected common by-frame keywords
|
|
103
102
|
"""
|
|
104
103
|
incomplete_header = complete_common_header
|
|
105
|
-
incomplete_header.pop(
|
|
106
|
-
incomplete_header.pop(
|
|
104
|
+
incomplete_header.pop(MetadataKey.elevation)
|
|
105
|
+
incomplete_header.pop(MetadataKey.azimuth)
|
|
107
106
|
data = np.arange(9).reshape(3, 3)
|
|
108
107
|
hdu = fits.PrimaryHDU(data, header=incomplete_header)
|
|
109
108
|
return hdu
|
|
110
109
|
|
|
111
110
|
|
|
112
|
-
class MetadataKeyWithOptionalKeys(StrEnum):
|
|
113
|
-
optional1 = "ELEV_ANG"
|
|
114
|
-
optional2 = "TAZIMUTH"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
class FitsAccessWithOptionalKeys(FitsAccessBase):
|
|
118
|
-
def __init__(self, hdu, name):
|
|
119
|
-
super().__init__(hdu, name)
|
|
120
|
-
self._set_metadata_key_value(MetadataKeyWithOptionalKeys.optional1, optional=True)
|
|
121
|
-
self._set_metadata_key_value(
|
|
122
|
-
MetadataKeyWithOptionalKeys.optional2, optional=True, default="SO_RAD"
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
|
|
126
111
|
@pytest.fixture()
|
|
127
112
|
def fits_file_path(tmp_path, complete_common_header):
|
|
128
113
|
file_path = tmp_path / "foo.fits"
|
|
@@ -155,35 +140,36 @@ class MetadataKeyWithNaxisKeys(StrEnum):
|
|
|
155
140
|
class FitsAccessWithNaxisKeys(FitsAccessBase):
|
|
156
141
|
def __init__(self, hdu, name):
|
|
157
142
|
super().__init__(hdu, name)
|
|
158
|
-
self.
|
|
159
|
-
self.
|
|
160
|
-
self.
|
|
143
|
+
self.naxis = self.header[MetadataKeyWithNaxisKeys.naxis]
|
|
144
|
+
self.naxis1 = self.header[MetadataKeyWithNaxisKeys.naxis1]
|
|
145
|
+
self.naxis2 = self.header[MetadataKeyWithNaxisKeys.naxis2]
|
|
161
146
|
|
|
162
147
|
|
|
163
148
|
def test_metadata_keys_in_access_bases(hdu_with_no_data):
|
|
164
149
|
"""
|
|
165
150
|
Given: a set of metadata key names in the MetadataKey sting enumeration
|
|
166
151
|
When: the FITS access classes define a set of attributes/properties
|
|
167
|
-
Then: the sets are the same
|
|
152
|
+
Then: the sets are the same and the values of the attributes/properties are correct
|
|
168
153
|
"""
|
|
169
154
|
all_metadata_key_names = {mk.name for mk in MetadataKey}
|
|
170
155
|
hdu = hdu_with_no_data
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
156
|
+
fits_obj = L0FitsAccess(hdu=hdu)
|
|
157
|
+
fits_access_defined_attributes = {
|
|
158
|
+
k for k, v in fits_obj.__dict__.items() if k not in ["_hdu", "name", "auto_squeeze"]
|
|
159
|
+
}
|
|
160
|
+
l0_access_properties = {k for k, v in L0FitsAccess.__dict__.items() if isinstance(v, property)}
|
|
161
|
+
l1_access_properties = {k for k, v in L1FitsAccess.__dict__.items() if isinstance(v, property)}
|
|
162
|
+
fits_access_properties = l0_access_properties | l1_access_properties
|
|
163
|
+
assert fits_access_defined_attributes | fits_access_properties == all_metadata_key_names
|
|
164
|
+
for key in fits_access_defined_attributes:
|
|
165
|
+
assert getattr(fits_obj, key) == fits_obj.header[MetadataKey[key]]
|
|
180
166
|
|
|
181
167
|
|
|
182
168
|
def test_from_single_hdu(hdu_with_complete_common_header):
|
|
183
169
|
"""
|
|
184
170
|
Given: an HDU with expected, common by-frame keywords
|
|
185
171
|
When: loading the HDU with the L0FitsAccess class
|
|
186
|
-
Then:
|
|
172
|
+
Then: values for common keywords are exposed as properties on the fits_obj class
|
|
187
173
|
"""
|
|
188
174
|
fits_obj = L0FitsAccess(hdu_with_complete_common_header)
|
|
189
175
|
assert fits_obj.elevation == 6.28
|
|
@@ -198,7 +184,7 @@ def test_l1_only_fits_access(hdu_with_complete_l1_only_header):
|
|
|
198
184
|
"""
|
|
199
185
|
Given: an HDU with 214 L1-only headers
|
|
200
186
|
When: loading the HDU with the L1FitsAccess class
|
|
201
|
-
Then: no errors are raised and
|
|
187
|
+
Then: no errors are raised and values are exposed
|
|
202
188
|
"""
|
|
203
189
|
fits_obj = L1FitsAccess(hdu_with_complete_l1_only_header)
|
|
204
190
|
assert fits_obj.elevation == 6.28
|
|
@@ -255,17 +241,6 @@ def test_no_header_value(hdu_with_incomplete_common_header):
|
|
|
255
241
|
_ = L0FitsAccess(hdu_with_incomplete_common_header)
|
|
256
242
|
|
|
257
243
|
|
|
258
|
-
def test_default_header_values(hdu_with_incomplete_common_header):
|
|
259
|
-
"""
|
|
260
|
-
Given: an HDU with a header with missing common by-frame keywords
|
|
261
|
-
When: processing the HDU with a FITS access class that sets these keywords as optional
|
|
262
|
-
Then: the correct default values are set
|
|
263
|
-
"""
|
|
264
|
-
fits_obj = FitsAccessWithOptionalKeys(hdu_with_incomplete_common_header, name="foo")
|
|
265
|
-
assert fits_obj.optional1 == MetadataKeyWithOptionalKeys.optional1 + NOT_FOUND_MESSAGE
|
|
266
|
-
assert fits_obj.optional2 == "SO_RAD"
|
|
267
|
-
|
|
268
|
-
|
|
269
244
|
def test_as_subclass(hdu_with_complete_common_header):
|
|
270
245
|
"""
|
|
271
246
|
Given: an instrument-specific fits_obj class that subclasses L0FitsAccess
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from datetime import datetime
|
|
3
2
|
from typing import Any
|
|
4
3
|
from uuid import uuid4
|
|
5
4
|
|
|
@@ -8,6 +7,7 @@ import pytest
|
|
|
8
7
|
from dkist_processing_common.codecs.basemodel import basemodel_decoder
|
|
9
8
|
from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
|
|
10
9
|
from dkist_processing_common.models.tags import Tag
|
|
10
|
+
from dkist_processing_common.tests.mock_metadata_store import input_dataset_parameters_part_factory
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def input_dataset_frames_part_factory(bucket_count: int = 1) -> list[dict]:
|
|
@@ -25,42 +25,6 @@ def flatten_frame_parts(frame_parts: list[dict]) -> list[tuple[str, str]]:
|
|
|
25
25
|
return result
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def input_dataset_parameters_part_factory(
|
|
29
|
-
parameter_count: int = 1,
|
|
30
|
-
parameter_value_count: int = 1,
|
|
31
|
-
has_date: bool = False,
|
|
32
|
-
has_file: bool = False,
|
|
33
|
-
) -> list[dict]:
|
|
34
|
-
result = [
|
|
35
|
-
{
|
|
36
|
-
"parameterName": uuid4().hex[:6],
|
|
37
|
-
"parameterValues": [
|
|
38
|
-
{"parameterValueId": i, "parameterValue": json.dumps(uuid4().hex)}
|
|
39
|
-
for i in range(parameter_value_count)
|
|
40
|
-
],
|
|
41
|
-
}
|
|
42
|
-
for _ in range(parameter_count)
|
|
43
|
-
]
|
|
44
|
-
if has_date:
|
|
45
|
-
for data in result:
|
|
46
|
-
param_list = data["parameterValues"]
|
|
47
|
-
for item in param_list:
|
|
48
|
-
item["parameterValueStartDate"] = datetime(2022, 9, 14).isoformat()
|
|
49
|
-
if has_file:
|
|
50
|
-
for data in result:
|
|
51
|
-
param_list = data["parameterValues"]
|
|
52
|
-
for item in param_list:
|
|
53
|
-
item["parameterValue"] = json.dumps(
|
|
54
|
-
{
|
|
55
|
-
"__file__": {
|
|
56
|
-
"bucket": "data",
|
|
57
|
-
"objectKey": f"parameters/{data['parameterName']}/{uuid4().hex}.dat",
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
)
|
|
61
|
-
return result
|
|
62
|
-
|
|
63
|
-
|
|
64
28
|
@pytest.mark.parametrize(
|
|
65
29
|
"input_dataset_parts",
|
|
66
30
|
[
|
|
@@ -97,9 +97,9 @@ class ViSPMetadataKey(StrEnum):
|
|
|
97
97
|
class ViSPFitsAccess(FitsAccessBase):
|
|
98
98
|
def __init__(self, hdu, name, auto_squeeze=False):
|
|
99
99
|
super().__init__(hdu, name, auto_squeeze=auto_squeeze)
|
|
100
|
-
self.
|
|
101
|
-
self.
|
|
102
|
-
self.
|
|
100
|
+
self.num_mod: int = self.header[ViSPMetadataKey.num_mod]
|
|
101
|
+
self.modstate: int = self.header[ViSPMetadataKey.modstate]
|
|
102
|
+
self.ip_task_type: str = self.header[ViSPMetadataKey.ip_task_type]
|
|
103
103
|
self.name = name
|
|
104
104
|
|
|
105
105
|
|
|
@@ -260,18 +260,58 @@ def test_subclass_flowers(visp_parse_inputs_task, max_cs_step_time_sec):
|
|
|
260
260
|
tag_pot, constant_pot = visp_parse_inputs_task.make_flower_pots()
|
|
261
261
|
|
|
262
262
|
assert len(tag_pot.stems) == 1
|
|
263
|
-
assert len(constant_pot.stems) ==
|
|
263
|
+
assert len(constant_pot.stems) == 60
|
|
264
264
|
all_flower_names = [StemName.modstate]
|
|
265
265
|
assert sorted([f.stem_name for f in tag_pot.stems]) == sorted(all_flower_names)
|
|
266
266
|
all_bud_names = [b.stem_name for b in default_constant_bud_factory()] + [BudName.num_modstates]
|
|
267
267
|
assert sorted([f.stem_name for f in constant_pot.stems]) == sorted(all_bud_names)
|
|
268
268
|
|
|
269
269
|
|
|
270
|
+
def test_dataset_extra_bud_factory(visp_parse_inputs_task, max_cs_step_time_sec):
|
|
271
|
+
"""
|
|
272
|
+
Given: ParseInputData child class with custom stems
|
|
273
|
+
When: Making the constant pot
|
|
274
|
+
Then: The multi-task dataset extra buds are created
|
|
275
|
+
"""
|
|
276
|
+
_, constant_pot = visp_parse_inputs_task.make_flower_pots()
|
|
277
|
+
stem_names = [f.stem_name.value for f in constant_pot.stems]
|
|
278
|
+
bud_name_base = [
|
|
279
|
+
"DATE_BEGIN",
|
|
280
|
+
"OBSERVING_PROGRAM_EXECUTION_IDS",
|
|
281
|
+
"NUM_RAW_FRAMES_PER_FPA",
|
|
282
|
+
"TELESCOPE_TRACKING_MODE",
|
|
283
|
+
"COUDE_TABLE_TRACKING_MODE",
|
|
284
|
+
"TELESCOPE_SCANNING_MODE",
|
|
285
|
+
"AVERAGE_LIGHT_LEVEL",
|
|
286
|
+
"AVERAGE_TELESCOPE_ELEVATION",
|
|
287
|
+
"AVERAGE_COUDE_TABLE_ANGLE",
|
|
288
|
+
"AVERAGE_TELESCOPE_AZIMUTH",
|
|
289
|
+
"GOS_LEVEL3_STATUS",
|
|
290
|
+
"GOS_LEVEL3_LAMP_STATUS",
|
|
291
|
+
"GOS_POLARIZER_STATUS",
|
|
292
|
+
"GOS_POLARIZER_ANGLE",
|
|
293
|
+
"GOS_RETARDER_STATUS",
|
|
294
|
+
"GOS_RETARDER_ANGLE",
|
|
295
|
+
"GOS_LEVEL0_STATUS",
|
|
296
|
+
]
|
|
297
|
+
for base in bud_name_base:
|
|
298
|
+
assert "SOLAR_GAIN_" + base in stem_names
|
|
299
|
+
if base not in [
|
|
300
|
+
"NUM_RAW_FRAMES_PER_FPA",
|
|
301
|
+
"TELESCOPE_TRACKING_MODE",
|
|
302
|
+
"COUDE_TABLE_TRACKING_MODE",
|
|
303
|
+
"TELESCOPE_SCANNING_MODE",
|
|
304
|
+
]:
|
|
305
|
+
assert "DARK_" + base in stem_names
|
|
306
|
+
if "GOS" not in base:
|
|
307
|
+
assert "POLCAL_" + base in stem_names
|
|
308
|
+
|
|
309
|
+
|
|
270
310
|
def test_constants_correct(parse_inputs_task):
|
|
271
311
|
"""
|
|
272
312
|
Given: ParseInputData task with a populated constant FlowerPot
|
|
273
313
|
When: Updating pipeline constants
|
|
274
|
-
Then:
|
|
314
|
+
Then: A pipeline constant is correctly populated
|
|
275
315
|
"""
|
|
276
316
|
_, constant_pot = parse_inputs_task.make_flower_pots()
|
|
277
317
|
parse_inputs_task.update_constants(constant_pot)
|
|
@@ -1214,20 +1214,12 @@ def wavecal_weights(wavecal_input_wavelength) -> np.ndarray:
|
|
|
1214
1214
|
|
|
1215
1215
|
|
|
1216
1216
|
@pytest.fixture(scope="session")
|
|
1217
|
-
def wavecal_fit_result(wavecal_input_wavelength
|
|
1217
|
+
def wavecal_fit_result(wavecal_input_wavelength) -> FitResult:
|
|
1218
1218
|
wavelength_params = WavelengthParameters(
|
|
1219
1219
|
crpix=1, crval=10.0, dispersion=1, grating_constant=1, order=1, incident_light_angle=0
|
|
1220
1220
|
)
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
residuals[-1] = np.nan
|
|
1224
|
-
minimizer_result = MinimizerResult(residual=residuals)
|
|
1225
|
-
return FitResult(
|
|
1226
|
-
wavelength_parameters=wavelength_params,
|
|
1227
|
-
minimizer_result=minimizer_result,
|
|
1228
|
-
input_wavelength_vector=wavecal_input_wavelength,
|
|
1229
|
-
input_spectrum=wavecal_input_spectrum,
|
|
1230
|
-
)
|
|
1221
|
+
minimizer_result = MinimizerResult(residual=np.random.random(wavecal_input_wavelength.size))
|
|
1222
|
+
return FitResult(wavelength_parameters=wavelength_params, minimizer_result=minimizer_result)
|
|
1231
1223
|
|
|
1232
1224
|
|
|
1233
1225
|
@pytest.mark.parametrize(
|