dkist-processing-common 11.4.0rc1__py3-none-any.whl → 11.5.0__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 (30) hide show
  1. dkist_processing_common/models/constants.py +42 -28
  2. dkist_processing_common/models/fits_access.py +56 -0
  3. dkist_processing_common/models/tags.py +2 -1
  4. dkist_processing_common/models/task_name.py +2 -2
  5. dkist_processing_common/parsers/cs_step.py +2 -2
  6. dkist_processing_common/parsers/dsps_repeat.py +5 -4
  7. dkist_processing_common/parsers/experiment_id_bud.py +5 -2
  8. dkist_processing_common/parsers/id_bud.py +6 -3
  9. dkist_processing_common/parsers/l0_fits_access.py +4 -3
  10. dkist_processing_common/parsers/l1_fits_access.py +22 -21
  11. dkist_processing_common/parsers/near_bud.py +5 -2
  12. dkist_processing_common/parsers/proposal_id_bud.py +3 -2
  13. dkist_processing_common/parsers/retarder.py +4 -3
  14. dkist_processing_common/parsers/single_value_single_key_flower.py +6 -1
  15. dkist_processing_common/parsers/task.py +7 -6
  16. dkist_processing_common/parsers/time.py +19 -15
  17. dkist_processing_common/parsers/unique_bud.py +5 -2
  18. dkist_processing_common/parsers/wavelength.py +4 -3
  19. dkist_processing_common/tasks/parse_l0_input_data.py +5 -3
  20. dkist_processing_common/tests/test_assemble_movie.py +0 -1
  21. dkist_processing_common/tests/test_constants.py +15 -0
  22. dkist_processing_common/tests/test_fits_access.py +62 -7
  23. dkist_processing_common/tests/test_parse_l0_input_data.py +22 -24
  24. dkist_processing_common/tests/test_stems.py +30 -21
  25. dkist_processing_common/tests/test_task_parsing.py +17 -7
  26. {dkist_processing_common-11.4.0rc1.dist-info → dkist_processing_common-11.5.0.dist-info}/METADATA +2 -2
  27. {dkist_processing_common-11.4.0rc1.dist-info → dkist_processing_common-11.5.0.dist-info}/RECORD +29 -30
  28. changelog/266.misc.rst +0 -1
  29. {dkist_processing_common-11.4.0rc1.dist-info → dkist_processing_common-11.5.0.dist-info}/WHEEL +0 -0
  30. {dkist_processing_common-11.4.0rc1.dist-info → dkist_processing_common-11.5.0.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  from datetime import datetime
4
4
  from datetime import timezone
5
+ from enum import StrEnum
5
6
  from typing import Callable
6
7
  from typing import Hashable
7
8
  from typing import Type
@@ -9,6 +10,7 @@ from typing import Type
9
10
  import numpy as np
10
11
 
11
12
  from dkist_processing_common.models.constants import BudName
13
+ from dkist_processing_common.models.fits_access import MetadataKey
12
14
  from dkist_processing_common.models.flower_pot import SpilledDirt
13
15
  from dkist_processing_common.models.flower_pot import Stem
14
16
  from dkist_processing_common.models.tags import EXP_TIME_ROUND_DIGITS
@@ -28,9 +30,9 @@ class ObsIpStartTimeBud(TaskUniqueBud):
28
30
 
29
31
  def __init__(self):
30
32
  super().__init__(
31
- constant_name=BudName.obs_ip_start_time.value,
32
- metadata_key="ip_start_time",
33
- ip_task_types=TaskName.observe.value,
33
+ constant_name=BudName.obs_ip_start_time,
34
+ metadata_key=MetadataKey.ip_start_time,
35
+ ip_task_types=TaskName.observe,
34
36
  )
35
37
 
36
38
 
@@ -38,7 +40,7 @@ class CadenceBudBase(UniqueBud):
38
40
  """Base class for all Cadence Buds."""
39
41
 
40
42
  def __init__(self, constant_name: str):
41
- super().__init__(constant_name, metadata_key="time_obs")
43
+ super().__init__(constant_name, metadata_key=MetadataKey.time_obs)
42
44
 
43
45
  def setter(self, fits_obj: L0FitsAccess) -> float | Type[SpilledDirt]:
44
46
  """
@@ -65,7 +67,7 @@ class AverageCadenceBud(CadenceBudBase):
65
67
  """Class for the average cadence Bud."""
66
68
 
67
69
  def __init__(self):
68
- super().__init__(constant_name=BudName.average_cadence.value)
70
+ super().__init__(constant_name=BudName.average_cadence)
69
71
 
70
72
  def getter(self, key) -> np.float64:
71
73
  """
@@ -87,7 +89,7 @@ class MaximumCadenceBud(CadenceBudBase):
87
89
  """Class for the maximum cadence bud."""
88
90
 
89
91
  def __init__(self):
90
- super().__init__(constant_name=BudName.maximum_cadence.value)
92
+ super().__init__(constant_name=BudName.maximum_cadence)
91
93
 
92
94
  def getter(self, key) -> np.float64:
93
95
  """
@@ -109,7 +111,7 @@ class MinimumCadenceBud(CadenceBudBase):
109
111
  """Class for the minimum cadence bud."""
110
112
 
111
113
  def __init__(self):
112
- super().__init__(constant_name=BudName.minimum_cadence.value)
114
+ super().__init__(constant_name=BudName.minimum_cadence)
113
115
 
114
116
  def getter(self, key) -> np.float64:
115
117
  """
@@ -131,7 +133,7 @@ class VarianceCadenceBud(CadenceBudBase):
131
133
  """Class for the variance cadence Bud."""
132
134
 
133
135
  def __init__(self):
134
- super().__init__(constant_name=BudName.variance_cadence.value)
136
+ super().__init__(constant_name=BudName.variance_cadence)
135
137
 
136
138
  def getter(self, key) -> np.float64:
137
139
  """
@@ -172,7 +174,7 @@ class ExposureTimeFlower(TimeFlowerBase):
172
174
 
173
175
  def __init__(self):
174
176
  super().__init__(
175
- tag_stem_name=StemName.exposure_time.value, metadata_key="fpa_exposure_time_ms"
177
+ tag_stem_name=StemName.exposure_time, metadata_key=MetadataKey.fpa_exposure_time_ms
176
178
  )
177
179
 
178
180
 
@@ -181,8 +183,8 @@ class ReadoutExpTimeFlower(TimeFlowerBase):
181
183
 
182
184
  def __init__(self):
183
185
  super().__init__(
184
- tag_stem_name=StemName.readout_exp_time.value,
185
- metadata_key="sensor_readout_exposure_time_ms",
186
+ tag_stem_name=StemName.readout_exp_time,
187
+ metadata_key=MetadataKey.sensor_readout_exposure_time_ms,
186
188
  )
187
189
 
188
190
 
@@ -213,15 +215,17 @@ class TaskTimeBudBase(Stem):
213
215
  def __init__(
214
216
  self,
215
217
  stem_name: str,
216
- metadata_key: str,
218
+ metadata_key: str | StrEnum,
217
219
  ip_task_types: str | list[str],
218
220
  header_task_parsing_func: Callable = passthrough_header_ip_task,
219
221
  ):
220
222
  super().__init__(stem_name=stem_name)
221
223
 
224
+ if isinstance(metadata_key, StrEnum):
225
+ metadata_key = metadata_key.name
226
+ self.metadata_key = metadata_key
222
227
  if isinstance(ip_task_types, str):
223
228
  ip_task_types = [ip_task_types]
224
- self.metadata_key = metadata_key
225
229
  self.ip_task_types = [task.casefold() for task in ip_task_types]
226
230
  self.header_parsing_function = header_task_parsing_func
227
231
 
@@ -252,7 +256,7 @@ class TaskExposureTimesBud(TaskTimeBudBase):
252
256
  ):
253
257
  super().__init__(
254
258
  stem_name=stem_name,
255
- metadata_key="fpa_exposure_time_ms",
259
+ metadata_key=MetadataKey.fpa_exposure_time_ms,
256
260
  ip_task_types=ip_task_types,
257
261
  header_task_parsing_func=header_task_parsing_func,
258
262
  )
@@ -269,7 +273,7 @@ class TaskReadoutExpTimesBud(TaskTimeBudBase):
269
273
  ):
270
274
  super().__init__(
271
275
  stem_name=stem_name,
272
- metadata_key="sensor_readout_exposure_time_ms",
276
+ metadata_key=MetadataKey.sensor_readout_exposure_time_ms,
273
277
  ip_task_types=ip_task_types,
274
278
  header_task_parsing_func=header_task_parsing_func,
275
279
  )
@@ -1,5 +1,6 @@
1
1
  """Pre-made flower that reads a single header key from all files and raises a ValueError if it is not unique."""
2
2
 
3
+ from enum import StrEnum
3
4
  from typing import Callable
4
5
 
5
6
  from dkist_processing_common.models.flower_pot import SpilledDirt
@@ -24,9 +25,11 @@ class UniqueBud(Stem):
24
25
  def __init__(
25
26
  self,
26
27
  constant_name: str,
27
- metadata_key: str,
28
+ metadata_key: str | StrEnum,
28
29
  ):
29
30
  super().__init__(stem_name=constant_name)
31
+ if isinstance(metadata_key, StrEnum):
32
+ metadata_key = metadata_key.name
30
33
  self.metadata_key = metadata_key
31
34
 
32
35
  def setter(self, fits_obj: L0FitsAccess):
@@ -85,7 +88,7 @@ class TaskUniqueBud(UniqueBud):
85
88
  def __init__(
86
89
  self,
87
90
  constant_name: str,
88
- metadata_key: str,
91
+ metadata_key: str | StrEnum,
89
92
  ip_task_types: str | list[str],
90
93
  task_type_parsing_function: Callable = passthrough_header_ip_task,
91
94
  ):
@@ -1,6 +1,7 @@
1
1
  """Bud to get the wavelength of observe frames."""
2
2
 
3
3
  from dkist_processing_common.models.constants import BudName
4
+ from dkist_processing_common.models.fits_access import MetadataKey
4
5
  from dkist_processing_common.models.task_name import TaskName
5
6
  from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
6
7
 
@@ -10,7 +11,7 @@ class ObserveWavelengthBud(TaskUniqueBud):
10
11
 
11
12
  def __init__(self):
12
13
  super().__init__(
13
- constant_name=BudName.wavelength.value,
14
- metadata_key="wavelength",
15
- ip_task_types=TaskName.observe.value,
14
+ constant_name=BudName.wavelength,
15
+ metadata_key=MetadataKey.wavelength,
16
+ ip_task_types=TaskName.observe,
16
17
  )
@@ -28,10 +28,12 @@ from typing import TypeVar
28
28
 
29
29
  from dkist_processing_common.codecs.fits import fits_access_decoder
30
30
  from dkist_processing_common.models.constants import BudName
31
+ from dkist_processing_common.models.fits_access import MetadataKey
31
32
  from dkist_processing_common.models.flower_pot import FlowerPot
32
33
  from dkist_processing_common.models.flower_pot import Stem
33
34
  from dkist_processing_common.models.flower_pot import Thorn
34
35
  from dkist_processing_common.models.tags import Tag
36
+ from dkist_processing_common.models.task_name import TaskName
35
37
  from dkist_processing_common.parsers.experiment_id_bud import ContributingExperimentIdsBud
36
38
  from dkist_processing_common.parsers.experiment_id_bud import ExperimentIdBud
37
39
  from dkist_processing_common.parsers.proposal_id_bud import ContributingProposalIdsBud
@@ -60,7 +62,7 @@ S = TypeVar("S", bound=Stem)
60
62
  def default_constant_bud_factory() -> list[S]:
61
63
  """Provide default constant buds for use in common parsing tasks."""
62
64
  return [
63
- UniqueBud(constant_name=BudName.instrument.value, metadata_key="instrument"),
65
+ UniqueBud(constant_name=BudName.instrument, metadata_key=MetadataKey.instrument),
64
66
  ProposalIdBud(),
65
67
  ContributingProposalIdsBud(),
66
68
  ExperimentIdBud(),
@@ -69,9 +71,9 @@ def default_constant_bud_factory() -> list[S]:
69
71
  MaximumCadenceBud(),
70
72
  MinimumCadenceBud(),
71
73
  VarianceCadenceBud(),
72
- TaskExposureTimesBud(stem_name=BudName.dark_exposure_times.value, ip_task_types="dark"),
74
+ TaskExposureTimesBud(stem_name=BudName.dark_exposure_times, ip_task_types=TaskName.dark),
73
75
  TaskReadoutExpTimesBud(
74
- stem_name=BudName.dark_readout_exp_times.value, ip_task_types="dark"
76
+ stem_name=BudName.dark_readout_exp_times, ip_task_types=TaskName.dark
75
77
  ),
76
78
  ]
77
79
 
@@ -60,7 +60,6 @@ def assemble_task_with_tagged_movie_frames(
60
60
  task.constants._update(
61
61
  {
62
62
  BudName.num_dsps_repeats.value: num_dsps_repeats,
63
- BudName.recipe_run_id.value: recipe_run_id,
64
63
  }
65
64
  )
66
65
  for d in range(num_dsps_repeats):
@@ -40,6 +40,21 @@ class ConstantsFinal(ConstantsBase):
40
40
  return self._db_dict["KEY 1"] ** 2 # Just to show that you can do whatever you want
41
41
 
42
42
 
43
+ def test_bud_names_in_constant_base():
44
+ """
45
+ Given: a set of constants in the BudNames sting enumeration
46
+ When: ConstantBase class defines a set of properties
47
+ Then: the sets are the same (except for constants that are not in the redis database)
48
+ """
49
+ all_bud_names = {b.name for b in BudName}
50
+ all_properties_in_constants_base = {
51
+ k for k, v in ConstantsBase.__dict__.items() if isinstance(v, property)
52
+ }
53
+ constants_not_in_redis = {"dataset_id", "stokes_params"}
54
+ all_buds_defined_in_constant_base = all_properties_in_constants_base - constants_not_in_redis
55
+ assert all_bud_names == all_buds_defined_in_constant_base
56
+
57
+
43
58
  def test_constants_db_as_dict(test_constants_db, test_dict):
44
59
  """
45
60
  Given: a ConstantsDb object and a python dictionary
@@ -1,8 +1,12 @@
1
+ from enum import StrEnum
2
+
1
3
  import numpy as np
2
4
  import pytest
3
5
  from astropy.io import fits
4
6
 
7
+ from dkist_processing_common.models.fits_access import NOT_FOUND_MESSAGE
5
8
  from dkist_processing_common.models.fits_access import FitsAccessBase
9
+ from dkist_processing_common.models.fits_access import MetadataKey
6
10
  from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
7
11
  from dkist_processing_common.parsers.l1_fits_access import L1FitsAccess
8
12
 
@@ -93,17 +97,32 @@ def hdu_with_no_data(complete_common_header):
93
97
 
94
98
 
95
99
  @pytest.fixture()
96
- def hdu_with_incomplete_common_header(tmp_path):
100
+ def hdu_with_incomplete_common_header(complete_common_header):
97
101
  """
98
102
  An HDU with data and a header missing one of the expected common by-frame keywords
99
103
  """
104
+ incomplete_header = complete_common_header
105
+ incomplete_header.pop("ELEV_ANG")
106
+ incomplete_header.pop("TAZIMUTH")
100
107
  data = np.arange(9).reshape(3, 3)
101
- hdu = fits.PrimaryHDU(data)
102
- hdu.header["TELEVATN"] = 6.28
103
- hdu.header["TAZIMUTH"] = 3.14
108
+ hdu = fits.PrimaryHDU(data, header=incomplete_header)
104
109
  return hdu
105
110
 
106
111
 
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
+
107
126
  @pytest.fixture()
108
127
  def fits_file_path(tmp_path, complete_common_header):
109
128
  file_path = tmp_path / "foo.fits"
@@ -127,12 +146,37 @@ def fits_file_path_with_data_in_imagehdu(tmp_path, complete_common_header):
127
146
  return file_path
128
147
 
129
148
 
149
+ class MetadataKeyWithNaxisKeys(StrEnum):
150
+ naxis = "NAXIS"
151
+ naxis1 = "NAXIS1"
152
+ naxis2 = "NAXIS2"
153
+
154
+
130
155
  class FitsAccessWithNaxisKeys(FitsAccessBase):
131
156
  def __init__(self, hdu, name):
132
157
  super().__init__(hdu, name)
133
- self.naxis = self.header["NAXIS"]
134
- self.naxis1 = self.header["NAXIS1"]
135
- self.naxis2 = self.header["NAXIS2"]
158
+ self._set_metadata_key_value(MetadataKeyWithNaxisKeys.naxis)
159
+ self._set_metadata_key_value(MetadataKeyWithNaxisKeys.naxis1)
160
+ self._set_metadata_key_value(MetadataKeyWithNaxisKeys.naxis2)
161
+
162
+
163
+ def test_metadata_keys_in_access_bases(hdu_with_no_data):
164
+ """
165
+ Given: a set of metadata key names in the MetadataKey sting enumeration
166
+ When: the FITS access classes define a set of attributes/properties
167
+ Then: the sets are the same
168
+ """
169
+ all_metadata_key_names = {mk.name for mk in MetadataKey}
170
+ 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
136
180
 
137
181
 
138
182
  def test_from_single_hdu(hdu_with_complete_common_header):
@@ -211,6 +255,17 @@ def test_no_header_value(hdu_with_incomplete_common_header):
211
255
  _ = L0FitsAccess(hdu_with_incomplete_common_header)
212
256
 
213
257
 
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
+
214
269
  def test_as_subclass(hdu_with_complete_common_header):
215
270
  """
216
271
  Given: an instrument-specific fits_obj class that subclasses L0FitsAccess
@@ -1,5 +1,7 @@
1
1
  """Tests for the parse L0 input data task"""
2
2
 
3
+ from enum import StrEnum
4
+
3
5
  import numpy as np
4
6
  import pytest
5
7
  from astropy.io import fits
@@ -20,6 +22,7 @@ from dkist_processing_common.parsers.single_value_single_key_flower import (
20
22
  )
21
23
  from dkist_processing_common.parsers.unique_bud import UniqueBud
22
24
  from dkist_processing_common.tasks.parse_l0_input_data import ParseL0InputDataBase
25
+ from dkist_processing_common.tasks.parse_l0_input_data import default_constant_bud_factory
23
26
 
24
27
 
25
28
  class VispHeaders(Spec122Dataset):
@@ -85,25 +88,33 @@ class VispHeaders(Spec122Dataset):
85
88
  return self.index % self.num_mod
86
89
 
87
90
 
91
+ class ViSPMetadataKey(StrEnum):
92
+ num_mod = "VISP_010"
93
+ modstate = "VISP_011"
94
+ ip_task_type = "DKIST004"
95
+
96
+
88
97
  class ViSPFitsAccess(FitsAccessBase):
89
98
  def __init__(self, hdu, name, auto_squeeze=False):
90
99
  super().__init__(hdu, name, auto_squeeze=auto_squeeze)
91
- self.num_mod: int = self.header["VISP_010"]
92
- self.modstate: int = self.header["VISP_011"]
93
- self.ip_task_type: str = self.header["DKIST004"]
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)
94
103
  self.name = name
95
104
 
96
105
 
97
106
  @pytest.fixture(scope="function")
98
107
  def visp_flowers():
99
108
  return [
100
- SingleValueSingleKeyFlower(tag_stem_name=StemName.modstate.value, metadata_key="modstate")
109
+ SingleValueSingleKeyFlower(
110
+ tag_stem_name=StemName.modstate, metadata_key=ViSPMetadataKey.modstate
111
+ )
101
112
  ]
102
113
 
103
114
 
104
115
  @pytest.fixture(scope="function")
105
116
  def visp_buds():
106
- return [UniqueBud(constant_name=BudName.num_modstates.value, metadata_key="num_mod")]
117
+ return [UniqueBud(constant_name=BudName.num_modstates, metadata_key=ViSPMetadataKey.num_mod)]
107
118
 
108
119
 
109
120
  @pytest.fixture(scope="function")
@@ -233,9 +244,9 @@ def test_make_flowerpots(parse_inputs_task):
233
244
 
234
245
  assert len(tag_pot.stems) == 2
235
246
  assert len(constant_pot.stems) == 3
236
- assert tag_pot.stems[0].stem_name == StemName.modstate.value
247
+ assert tag_pot.stems[0].stem_name == StemName.modstate
237
248
  assert tag_pot.stems[1].stem_name == "EMPTY_FLOWER"
238
- assert constant_pot.stems[0].stem_name == BudName.num_modstates.value
249
+ assert constant_pot.stems[0].stem_name == BudName.num_modstates
239
250
  assert constant_pot.stems[1].stem_name == "EMPTY_BUD"
240
251
  assert constant_pot.stems[2].stem_name == "PICKY_BUD"
241
252
 
@@ -250,23 +261,10 @@ def test_subclass_flowers(visp_parse_inputs_task, max_cs_step_time_sec):
250
261
 
251
262
  assert len(tag_pot.stems) == 1
252
263
  assert len(constant_pot.stems) == 12
253
- assert sorted([f.stem_name for f in tag_pot.stems]) == sorted([StemName.modstate.value])
254
- assert sorted([f.stem_name for f in constant_pot.stems]) == sorted(
255
- [
256
- BudName.instrument.value,
257
- BudName.num_modstates.value,
258
- BudName.proposal_id.value,
259
- BudName.contributing_proposal_ids.value,
260
- BudName.average_cadence.value,
261
- BudName.maximum_cadence.value,
262
- BudName.minimum_cadence.value,
263
- BudName.variance_cadence.value,
264
- BudName.dark_exposure_times.value,
265
- BudName.dark_readout_exp_times.value,
266
- BudName.experiment_id.value,
267
- BudName.contributing_experiment_ids.value,
268
- ]
269
- )
264
+ all_flower_names = [StemName.modstate]
265
+ assert sorted([f.stem_name for f in tag_pot.stems]) == sorted(all_flower_names)
266
+ all_bud_names = [b.stem_name for b in default_constant_bud_factory()] + [BudName.num_modstates]
267
+ assert sorted([f.stem_name for f in constant_pot.stems]) == sorted(all_bud_names)
270
268
 
271
269
 
272
270
  def test_constants_correct(parse_inputs_task):
@@ -1,3 +1,4 @@
1
+ from enum import StrEnum
1
2
  from itertools import chain
2
3
 
3
4
  import pytest
@@ -39,30 +40,38 @@ from dkist_processing_common.parsers.unique_bud import UniqueBud
39
40
  from dkist_processing_common.parsers.wavelength import ObserveWavelengthBud
40
41
 
41
42
 
43
+ class FitsReaderMetadataKey(StrEnum):
44
+ thing_id = "id_key"
45
+ constant_thing = "constant"
46
+ near_thing = "near"
47
+ proposal_id = "ID___013"
48
+ experiment_id = "ID___012"
49
+ ip_task_type = "DKIST004"
50
+ ip_start_time = "DKIST011"
51
+ fpa_exposure_time_ms = "XPOSURE"
52
+ sensor_readout_exposure_time_ms = "TEXPOSUR"
53
+ num_raw_frames_per_fpa = "NSUMEXP"
54
+ num_dsps_repeats = "DSPSREPS"
55
+ current_dsps_repeat = "DSPSNUM"
56
+ time_obs = "DATE-OBS"
57
+ gos_level3_status = "GOSLVL3"
58
+ gos_level3_lamp_status = "GOSLAMP"
59
+ gos_level0_status = "GOSLVL0"
60
+ gos_retarder_status = "GOSRET"
61
+ gos_polarizer_status = "GOSPOL"
62
+ wavelength = "LINEWAV"
63
+ roundable_time = "RTIME"
64
+
65
+
42
66
  class FitsReader(FitsAccessBase):
43
67
  def __init__(self, hdu, name):
44
68
  super().__init__(hdu, name)
45
- self.thing_id: int = self.header.get("id_key")
46
- self.constant_thing: float = self.header.get("constant")
47
- self.near_thing: float = self.header.get("near")
69
+ for key in FitsReaderMetadataKey:
70
+ self._set_metadata_key_value(key=key, optional=True)
71
+ self._set_metadata_key_value(
72
+ FitsReaderMetadataKey.roundable_time, optional=True, default=0.0
73
+ )
48
74
  self.name = name
49
- self.proposal_id: str = self.header.get("ID___013")
50
- self.experiment_id: str = self.header.get("ID___012")
51
- self.ip_task_type: str = self.header.get("DKIST004")
52
- self.ip_start_time: str = self.header.get("DKIST011")
53
- self.fpa_exposure_time_ms: float = self.header.get("XPOSURE")
54
- self.sensor_readout_exposure_time_ms: float = self.header.get("TEXPOSUR")
55
- self.num_raw_frames_per_fpa: int = self.header.get("NSUMEXP")
56
- self.num_dsps_repeats: int = self.header.get("DSPSREPS")
57
- self.current_dsps_repeat: int = self.header.get("DSPSNUM")
58
- self.time_obs: str = self.header.get("DATE-OBS")
59
- self.gos_level3_status: str = self.header.get("GOSLVL3")
60
- self.gos_level3_lamp_status: str = self.header.get("GOSLAMP")
61
- self.gos_level0_status: str = self.header.get("GOSLVL0")
62
- self.gos_retarder_status: str = self.header.get("GOSRET")
63
- self.gos_polarizer_status: str = self.header.get("GOSPOL")
64
- self.wavelength: str = self.header.get("LINEWAV")
65
- self.roundable_time: float = self.header.get("RTIME", 0.0)
66
75
 
67
76
 
68
77
  @pytest.fixture()
@@ -926,4 +935,4 @@ def test_retarder_name_bud_error(bad_polcal_header_objs):
926
935
  _ = bud.bud
927
936
 
928
937
 
929
- # TODO: test new stems that have been added to parse_l0_input_data
938
+ # TODO: test new stem types that have been added to parse_l0_input_data
@@ -1,3 +1,5 @@
1
+ from enum import StrEnum
2
+
1
3
  import pytest
2
4
  from astropy.io import fits
3
5
 
@@ -9,6 +11,15 @@ from dkist_processing_common.parsers.task import parse_polcal_task_type
9
11
  from dkist_processing_common.parsers.task import passthrough_header_ip_task
10
12
 
11
13
 
14
+ class DummyMetadataKey(StrEnum):
15
+ ip_task_type = "IPTASK"
16
+ gos_level3_status = "GOSLVL3"
17
+ gos_level3_lamp_status = "GOSLAMP"
18
+ gos_level0_status = "GOSLVL0"
19
+ gos_retarder_status = "GOSRET"
20
+ gos_polarizer_status = "GOSPOL"
21
+
22
+
12
23
  class DummyFitsAccess(FitsAccessBase):
13
24
  def __init__(
14
25
  self,
@@ -17,13 +28,12 @@ class DummyFitsAccess(FitsAccessBase):
17
28
  auto_squeeze: bool = False, # Because L1 data should always have the right form, right?
18
29
  ):
19
30
  super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
20
-
21
- self.ip_task_type: str = self.header["IPTASK"]
22
- self.gos_level3_status: str = self.header["GOSLVL3"]
23
- self.gos_level3_lamp_status: str = self.header["GOSLAMP"]
24
- self.gos_level0_status: str = self.header["GOSLVL0"]
25
- self.gos_retarder_status: str = self.header["GOSRET"]
26
- self.gos_polarizer_status: str = self.header["GOSPOL"]
31
+ self._set_metadata_key_value(DummyMetadataKey.ip_task_type)
32
+ self._set_metadata_key_value(DummyMetadataKey.gos_level3_status)
33
+ self._set_metadata_key_value(DummyMetadataKey.gos_level3_lamp_status)
34
+ self._set_metadata_key_value(DummyMetadataKey.gos_level0_status)
35
+ self._set_metadata_key_value(DummyMetadataKey.gos_retarder_status)
36
+ self._set_metadata_key_value(DummyMetadataKey.gos_polarizer_status)
27
37
 
28
38
 
29
39
  @pytest.fixture
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-common
3
- Version: 11.4.0rc1
3
+ Version: 11.5.0
4
4
  Summary: Common task classes used by the DKIST science data processing pipelines
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause
@@ -17,7 +17,7 @@ Requires-Dist: asdf<4.0.0,>=3.5.0
17
17
  Requires-Dist: astropy>=7.0.0
18
18
  Requires-Dist: dkist-fits-specifications<5.0,>=4.0.0
19
19
  Requires-Dist: dkist-header-validator<6.0,>=5.0.0
20
- Requires-Dist: dkist-processing-core==5.2.0rc1
20
+ Requires-Dist: dkist-processing-core==5.2.1
21
21
  Requires-Dist: dkist-processing-pac<4.0,>=3.1
22
22
  Requires-Dist: dkist-service-configuration<3.0,>=2.0.2
23
23
  Requires-Dist: dkist-spectral-lines<4.0,>=3.0.0