dkist-processing-common 11.6.0rc1__py3-none-any.whl → 11.7.0rc2__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 (29) hide show
  1. changelog/267.feature.1.rst +1 -0
  2. changelog/267.feature.2.rst +1 -0
  3. changelog/267.feature.rst +1 -0
  4. changelog/267.misc.rst +1 -0
  5. changelog/267.removal.1.rst +2 -0
  6. changelog/267.removal.rst +1 -0
  7. dkist_processing_common/models/constants.py +357 -21
  8. dkist_processing_common/models/fits_access.py +16 -25
  9. dkist_processing_common/models/telemetry.py +9 -2
  10. dkist_processing_common/parsers/average_bud.py +48 -0
  11. dkist_processing_common/parsers/experiment_id_bud.py +8 -4
  12. dkist_processing_common/parsers/id_bud.py +35 -19
  13. dkist_processing_common/parsers/l0_fits_access.py +3 -3
  14. dkist_processing_common/parsers/l1_fits_access.py +47 -21
  15. dkist_processing_common/parsers/near_bud.py +4 -4
  16. dkist_processing_common/parsers/proposal_id_bud.py +11 -5
  17. dkist_processing_common/parsers/single_value_single_key_flower.py +0 -1
  18. dkist_processing_common/parsers/time.py +141 -27
  19. dkist_processing_common/tasks/base.py +21 -3
  20. dkist_processing_common/tasks/parse_l0_input_data.py +254 -36
  21. dkist_processing_common/tests/test_fits_access.py +19 -44
  22. dkist_processing_common/tests/test_parse_l0_input_data.py +39 -5
  23. dkist_processing_common/tests/test_stems.py +127 -10
  24. dkist_processing_common/tests/test_task_parsing.py +6 -6
  25. {dkist_processing_common-11.6.0rc1.dist-info → dkist_processing_common-11.7.0rc2.dist-info}/METADATA +3 -3
  26. {dkist_processing_common-11.6.0rc1.dist-info → dkist_processing_common-11.7.0rc2.dist-info}/RECORD +28 -22
  27. changelog/268.misc.rst +0 -1
  28. {dkist_processing_common-11.6.0rc1.dist-info → dkist_processing_common-11.7.0rc2.dist-info}/WHEEL +0 -0
  29. {dkist_processing_common-11.6.0rc1.dist-info → dkist_processing_common-11.7.0rc2.dist-info}/top_level.txt +0 -0
@@ -24,13 +24,8 @@ at a different frame to get information (constants).
24
24
  import logging
25
25
  from abc import ABC
26
26
  from abc import abstractmethod
27
- from typing import Generator
28
27
  from typing import TypeVar
29
28
 
30
- from opentelemetry.metrics import CallbackOptions
31
- from opentelemetry.metrics import ObservableGauge
32
- from opentelemetry.metrics import Observation
33
-
34
29
  from dkist_processing_common.codecs.fits import fits_access_decoder
35
30
  from dkist_processing_common.models.constants import BudName
36
31
  from dkist_processing_common.models.fits_access import MetadataKey
@@ -39,17 +34,20 @@ from dkist_processing_common.models.flower_pot import Stem
39
34
  from dkist_processing_common.models.flower_pot import Thorn
40
35
  from dkist_processing_common.models.tags import Tag
41
36
  from dkist_processing_common.models.task_name import TaskName
42
- from dkist_processing_common.models.telemetry import Progress
37
+ from dkist_processing_common.parsers.average_bud import TaskAverageBud
43
38
  from dkist_processing_common.parsers.experiment_id_bud import ContributingExperimentIdsBud
44
39
  from dkist_processing_common.parsers.experiment_id_bud import ExperimentIdBud
40
+ from dkist_processing_common.parsers.id_bud import TaskContributingIdsBud
45
41
  from dkist_processing_common.parsers.proposal_id_bud import ContributingProposalIdsBud
46
42
  from dkist_processing_common.parsers.proposal_id_bud import ProposalIdBud
47
43
  from dkist_processing_common.parsers.time import AverageCadenceBud
48
44
  from dkist_processing_common.parsers.time import MaximumCadenceBud
49
45
  from dkist_processing_common.parsers.time import MinimumCadenceBud
46
+ from dkist_processing_common.parsers.time import TaskDateBeginBud
50
47
  from dkist_processing_common.parsers.time import TaskExposureTimesBud
51
48
  from dkist_processing_common.parsers.time import TaskReadoutExpTimesBud
52
49
  from dkist_processing_common.parsers.time import VarianceCadenceBud
50
+ from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
53
51
  from dkist_processing_common.parsers.unique_bud import UniqueBud
54
52
  from dkist_processing_common.tasks.base import WorkflowTaskBase
55
53
 
@@ -65,9 +63,253 @@ logger = logging.getLogger(__name__)
65
63
  S = TypeVar("S", bound=Stem)
66
64
 
67
65
 
66
+ def dataset_extra_bud_factory() -> list[S]:
67
+ """Provide constant buds for use in dataset extras."""
68
+ return [
69
+ UniqueBud(constant_name=BudName.camera_id, metadata_key=MetadataKey.camera_id),
70
+ UniqueBud(constant_name=BudName.camera_name, metadata_key=MetadataKey.camera_name),
71
+ UniqueBud(
72
+ constant_name=BudName.camera_bit_depth, metadata_key=MetadataKey.camera_bit_depth
73
+ ),
74
+ UniqueBud(
75
+ constant_name=BudName.hardware_binning_x, metadata_key=MetadataKey.hardware_binning_x
76
+ ),
77
+ UniqueBud(
78
+ constant_name=BudName.hardware_binning_y, metadata_key=MetadataKey.hardware_binning_x
79
+ ),
80
+ UniqueBud(
81
+ constant_name=BudName.software_binning_x, metadata_key=MetadataKey.software_binning_x
82
+ ),
83
+ UniqueBud(
84
+ constant_name=BudName.software_binning_y, metadata_key=MetadataKey.software_binning_y
85
+ ),
86
+ UniqueBud(
87
+ constant_name=BudName.hls_version,
88
+ metadata_key=MetadataKey.hls_version,
89
+ ),
90
+ TaskContributingIdsBud(
91
+ constant_name=BudName.dark_observing_program_execution_id,
92
+ metadata_key=MetadataKey.observing_program_execution_id,
93
+ ip_task_types=TaskName.dark,
94
+ ),
95
+ TaskContributingIdsBud(
96
+ constant_name=BudName.solar_gain_observing_program_execution_id,
97
+ metadata_key=MetadataKey.observing_program_execution_id,
98
+ ip_task_types=TaskName.solar_gain,
99
+ ),
100
+ TaskContributingIdsBud(
101
+ constant_name=BudName.polcal_observing_program_execution_id,
102
+ metadata_key=MetadataKey.observing_program_execution_id,
103
+ ip_task_types=TaskName.polcal,
104
+ ),
105
+ TaskUniqueBud(
106
+ constant_name=BudName.dark_num_raw_frames_per_fpa,
107
+ metadata_key=MetadataKey.num_raw_frames_per_fpa,
108
+ ip_task_types=TaskName.dark,
109
+ ),
110
+ TaskUniqueBud(
111
+ constant_name=BudName.solar_gain_num_raw_frames_per_fpa,
112
+ metadata_key=MetadataKey.num_raw_frames_per_fpa,
113
+ ip_task_types=TaskName.solar_gain,
114
+ ),
115
+ TaskUniqueBud(
116
+ constant_name=BudName.polcal_num_raw_frames_per_fpa,
117
+ metadata_key=MetadataKey.num_raw_frames_per_fpa,
118
+ ip_task_types=TaskName.polcal,
119
+ ),
120
+ TaskUniqueBud(
121
+ constant_name=BudName.dark_telescope_tracking_mode,
122
+ metadata_key=MetadataKey.telescope_tracking_mode,
123
+ ip_task_types=TaskName.dark,
124
+ ),
125
+ TaskUniqueBud(
126
+ constant_name=BudName.solar_gain_telescope_tracking_mode,
127
+ metadata_key=MetadataKey.telescope_tracking_mode,
128
+ ip_task_types=TaskName.solar_gain,
129
+ ),
130
+ TaskUniqueBud(
131
+ constant_name=BudName.polcal_telescope_tracking_mode,
132
+ metadata_key=MetadataKey.telescope_tracking_mode,
133
+ ip_task_types=TaskName.polcal,
134
+ ),
135
+ TaskUniqueBud(
136
+ constant_name=BudName.dark_coude_table_tracking_mode,
137
+ metadata_key=MetadataKey.coude_table_tracking_mode,
138
+ ip_task_types=TaskName.dark,
139
+ ),
140
+ TaskUniqueBud(
141
+ constant_name=BudName.solar_gain_coude_table_tracking_mode,
142
+ metadata_key=MetadataKey.coude_table_tracking_mode,
143
+ ip_task_types=TaskName.solar_gain,
144
+ ),
145
+ TaskUniqueBud(
146
+ constant_name=BudName.polcal_coude_table_tracking_mode,
147
+ metadata_key=MetadataKey.coude_table_tracking_mode,
148
+ ip_task_types=TaskName.polcal,
149
+ ),
150
+ TaskUniqueBud(
151
+ constant_name=BudName.dark_telescope_scanning_mode,
152
+ metadata_key=MetadataKey.telescope_scanning_mode,
153
+ ip_task_types=TaskName.dark,
154
+ ),
155
+ TaskUniqueBud(
156
+ constant_name=BudName.solar_gain_telescope_scanning_mode,
157
+ metadata_key=MetadataKey.telescope_scanning_mode,
158
+ ip_task_types=TaskName.solar_gain,
159
+ ),
160
+ TaskUniqueBud(
161
+ constant_name=BudName.polcal_telescope_scanning_mode,
162
+ metadata_key=MetadataKey.telescope_scanning_mode,
163
+ ip_task_types=TaskName.polcal,
164
+ ),
165
+ TaskUniqueBud(
166
+ constant_name=BudName.dark_gos_level3_status,
167
+ metadata_key=MetadataKey.gos_level3_status,
168
+ ip_task_types=TaskName.dark,
169
+ ),
170
+ TaskUniqueBud(
171
+ constant_name=BudName.solar_gain_gos_level3_status,
172
+ metadata_key=MetadataKey.gos_level3_status,
173
+ ip_task_types=TaskName.solar_gain,
174
+ ),
175
+ TaskUniqueBud(
176
+ constant_name=BudName.dark_gos_level3_lamp_status,
177
+ metadata_key=MetadataKey.gos_level3_lamp_status,
178
+ ip_task_types=TaskName.dark,
179
+ ),
180
+ TaskUniqueBud(
181
+ constant_name=BudName.solar_gain_gos_level3_lamp_status,
182
+ metadata_key=MetadataKey.gos_level3_lamp_status,
183
+ ip_task_types=TaskName.solar_gain,
184
+ ),
185
+ TaskUniqueBud(
186
+ constant_name=BudName.dark_gos_polarizer_status,
187
+ metadata_key=MetadataKey.gos_polarizer_status,
188
+ ip_task_types=TaskName.dark,
189
+ ),
190
+ TaskUniqueBud(
191
+ constant_name=BudName.solar_gain_gos_polarizer_status,
192
+ metadata_key=MetadataKey.gos_polarizer_status,
193
+ ip_task_types=TaskName.solar_gain,
194
+ ),
195
+ TaskUniqueBud(
196
+ constant_name=BudName.dark_gos_polarizer_angle,
197
+ metadata_key=MetadataKey.gos_polarizer_angle,
198
+ ip_task_types=TaskName.dark,
199
+ ),
200
+ TaskUniqueBud(
201
+ constant_name=BudName.solar_gain_gos_polarizer_angle,
202
+ metadata_key=MetadataKey.gos_polarizer_angle,
203
+ ip_task_types=TaskName.solar_gain,
204
+ ),
205
+ TaskUniqueBud(
206
+ constant_name=BudName.dark_gos_retarder_status,
207
+ metadata_key=MetadataKey.gos_retarder_status,
208
+ ip_task_types=TaskName.dark,
209
+ ),
210
+ TaskUniqueBud(
211
+ constant_name=BudName.solar_gain_gos_retarder_status,
212
+ metadata_key=MetadataKey.gos_retarder_status,
213
+ ip_task_types=TaskName.solar_gain,
214
+ ),
215
+ TaskUniqueBud(
216
+ constant_name=BudName.dark_gos_retarder_angle,
217
+ metadata_key=MetadataKey.gos_retarder_angle,
218
+ ip_task_types=TaskName.dark,
219
+ ),
220
+ TaskUniqueBud(
221
+ constant_name=BudName.solar_gain_gos_retarder_angle,
222
+ metadata_key=MetadataKey.gos_retarder_angle,
223
+ ip_task_types=TaskName.solar_gain,
224
+ ),
225
+ TaskUniqueBud(
226
+ constant_name=BudName.dark_gos_level0_status,
227
+ metadata_key=MetadataKey.gos_level0_status,
228
+ ip_task_types=TaskName.dark,
229
+ ),
230
+ TaskUniqueBud(
231
+ constant_name=BudName.solar_gain_gos_level0_status,
232
+ metadata_key=MetadataKey.gos_level0_status,
233
+ ip_task_types=TaskName.solar_gain,
234
+ ),
235
+ TaskAverageBud(
236
+ constant_name=BudName.dark_average_light_level,
237
+ metadata_key=MetadataKey.light_level,
238
+ ip_task_types=TaskName.dark,
239
+ ),
240
+ TaskAverageBud(
241
+ constant_name=BudName.solar_gain_average_light_level,
242
+ metadata_key=MetadataKey.light_level,
243
+ ip_task_types=TaskName.solar_gain,
244
+ ),
245
+ TaskAverageBud(
246
+ constant_name=BudName.polcal_average_light_level,
247
+ metadata_key=MetadataKey.light_level,
248
+ ip_task_types=TaskName.polcal,
249
+ ),
250
+ TaskAverageBud(
251
+ constant_name=BudName.dark_average_telescope_elevation,
252
+ metadata_key=MetadataKey.elevation,
253
+ ip_task_types=TaskName.dark,
254
+ ),
255
+ TaskAverageBud(
256
+ constant_name=BudName.solar_gain_average_telescope_elevation,
257
+ metadata_key=MetadataKey.elevation,
258
+ ip_task_types=TaskName.solar_gain,
259
+ ),
260
+ TaskAverageBud(
261
+ constant_name=BudName.polcal_average_telescope_elevation,
262
+ metadata_key=MetadataKey.elevation,
263
+ ip_task_types=TaskName.polcal,
264
+ ),
265
+ TaskAverageBud(
266
+ constant_name=BudName.dark_average_coude_table_angle,
267
+ metadata_key=MetadataKey.table_angle,
268
+ ip_task_types=TaskName.dark,
269
+ ),
270
+ TaskAverageBud(
271
+ constant_name=BudName.solar_gain_average_coude_table_angle,
272
+ metadata_key=MetadataKey.table_angle,
273
+ ip_task_types=TaskName.solar_gain,
274
+ ),
275
+ TaskAverageBud(
276
+ constant_name=BudName.polcal_average_coude_table_angle,
277
+ metadata_key=MetadataKey.table_angle,
278
+ ip_task_types=TaskName.polcal,
279
+ ),
280
+ TaskAverageBud(
281
+ constant_name=BudName.dark_average_telescope_azimuth,
282
+ metadata_key=MetadataKey.azimuth,
283
+ ip_task_types=TaskName.dark,
284
+ ),
285
+ TaskAverageBud(
286
+ constant_name=BudName.solar_gain_average_telescope_azimuth,
287
+ metadata_key=MetadataKey.azimuth,
288
+ ip_task_types=TaskName.solar_gain,
289
+ ),
290
+ TaskAverageBud(
291
+ constant_name=BudName.polcal_average_telescope_azimuth,
292
+ metadata_key=MetadataKey.azimuth,
293
+ ip_task_types=TaskName.polcal,
294
+ ),
295
+ TaskDateBeginBud(
296
+ constant_name=BudName.dark_date_begin,
297
+ ip_task_types=TaskName.dark,
298
+ ),
299
+ TaskDateBeginBud(
300
+ constant_name=BudName.solar_gain_date_begin,
301
+ ip_task_types=TaskName.solar_gain,
302
+ ),
303
+ TaskDateBeginBud(
304
+ constant_name=BudName.polcal_date_begin,
305
+ ip_task_types=TaskName.polcal,
306
+ ),
307
+ ]
308
+
309
+
68
310
  def default_constant_bud_factory() -> list[S]:
69
311
  """Provide default constant buds for use in common parsing tasks."""
70
- return [
312
+ return dataset_extra_bud_factory() + [
71
313
  UniqueBud(constant_name=BudName.instrument, metadata_key=MetadataKey.instrument),
72
314
  ProposalIdBud(),
73
315
  ContributingProposalIdsBud(),
@@ -92,33 +334,6 @@ def default_tag_flower_factory() -> list[S]:
92
334
  class ParseDataBase(WorkflowTaskBase, ABC):
93
335
  """Base class for tasks which need to parse some already tagged data and set constants and/or add additional tags to them."""
94
336
 
95
- def __init__(
96
- self,
97
- recipe_run_id: int,
98
- workflow_name: str,
99
- workflow_version: str,
100
- ):
101
- super().__init__(
102
- recipe_run_id=recipe_run_id,
103
- workflow_name=workflow_name,
104
- workflow_version=workflow_version,
105
- )
106
- # task specific metering instrumentation
107
- self.flower_pot_progress = Progress()
108
- self.flower_pot_progress_gauge: ObservableGauge = self.meter.create_observable_gauge(
109
- name=self.format_metric_name("tasks.flower.pot.progress"),
110
- description="The progress of loading input frames for parsing as a percentage.",
111
- callbacks=[lambda options: self.flower_pot_run_progress(options)],
112
- )
113
-
114
- def flower_pot_run_progress(
115
- self, options: CallbackOptions
116
- ) -> Generator[Observation, None, None]:
117
- """Observe the progress of the current task as a percentage."""
118
- yield Observation(
119
- self.flower_pot_progress.percent_complete, attributes=self.base_telemetry_attributes
120
- )
121
-
122
337
  @property
123
338
  @abstractmethod
124
339
  def constant_buds(self) -> list[S]:
@@ -139,13 +354,16 @@ class ParseDataBase(WorkflowTaskBase, ABC):
139
354
  def tags_for_input_frames(self) -> list[str]:
140
355
  """Define the tags for the data that will be parsed."""
141
356
 
357
+ def pre_run(self) -> None:
358
+ """Execute pre-task setup."""
359
+ self.outer_loop_progress.total = self.scratch.count_all(tags=self.tags_for_input_frames)
360
+
142
361
  def run(self) -> None:
143
362
  """Run method for this task."""
144
363
  with self.telemetry_span("Check that input frames exist"):
145
364
  self.check_input_frames()
146
365
 
147
366
  with self.telemetry_span("Ingest all input files"):
148
- self.flower_pot_progress.total = self.scratch.count_all(tags=self.tags_for_input_frames)
149
367
  tag_pot, constant_pot = self.make_flower_pots()
150
368
 
151
369
  with self.telemetry_span("Update constants"):
@@ -162,7 +380,7 @@ class ParseDataBase(WorkflowTaskBase, ABC):
162
380
  constant_pot.stems += self.constant_buds
163
381
 
164
382
  for fits_obj in self.input_frames:
165
- self.flower_pot_progress.increment()
383
+ self.outer_loop_progress.increment()
166
384
  filepath = fits_obj.name
167
385
  tag_pot.add_dirt(filepath, fits_obj)
168
386
  constant_pot.add_dirt(filepath, fits_obj)
@@ -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 one of the expected common by-frame keywords
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("ELEV_ANG")
106
- incomplete_header.pop("TAZIMUTH")
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._set_metadata_key_value(MetadataKeyWithNaxisKeys.naxis)
159
- self._set_metadata_key_value(MetadataKeyWithNaxisKeys.naxis1)
160
- self._set_metadata_key_value(MetadataKeyWithNaxisKeys.naxis2)
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
- l1_properties = {k for k, v in L1FitsAccess.__dict__.items() if isinstance(v, property)}
172
- l0_properties = {k for k, v in L0FitsAccess.__dict__.items() if isinstance(v, property)}
173
- all_fits_access_properties = l1_properties | l0_properties
174
- l1_dict_keys = {k for k, v in L1FitsAccess(hdu=hdu).__dict__.items()}
175
- l0_dict_keys = {k for k, v in L0FitsAccess(hdu=hdu).__dict__.items()}
176
- all_fits_access_dict_keys = l1_dict_keys | l0_dict_keys
177
- instance_dict_keys = {"_hdu", "name", "auto_squeeze"}
178
- keys_defined_in_fits_access = all_fits_access_dict_keys | all_fits_access_properties
179
- assert all_metadata_key_names == keys_defined_in_fits_access - instance_dict_keys
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: all values for common keywords are exposed as properties on the fits_obj class
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 all values are exposed
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
@@ -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._set_metadata_key_value(ViSPMetadataKey.num_mod)
101
- self._set_metadata_key_value(ViSPMetadataKey.modstate)
102
- self._set_metadata_key_value(ViSPMetadataKey.ip_task_type)
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,52 @@ 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) == 12
263
+ assert len(constant_pot.stems) == 64
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_ID",
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 "DARK_" + base in stem_names
299
+ assert "SOLAR_GAIN_" + base in stem_names
300
+ if "GOS" not in base:
301
+ assert "POLCAL_" + base in stem_names
302
+
303
+
270
304
  def test_constants_correct(parse_inputs_task):
271
305
  """
272
306
  Given: ParseInputData task with a populated constant FlowerPot
273
307
  When: Updating pipeline constants
274
- Then: Pipeline constants are correctly populated
308
+ Then: A pipeline constant is correctly populated
275
309
  """
276
310
  _, constant_pot = parse_inputs_task.make_flower_pots()
277
311
  parse_inputs_task.update_constants(constant_pot)