dkist-processing-common 10.5.4__py3-none-any.whl → 12.1.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.
Files changed (122) hide show
  1. changelog/280.misc.rst +1 -0
  2. changelog/282.feature.2.rst +2 -0
  3. changelog/282.feature.rst +2 -0
  4. changelog/284.feature.rst +1 -0
  5. changelog/285.feature.rst +2 -0
  6. changelog/285.misc.rst +2 -0
  7. changelog/286.feature.rst +2 -0
  8. changelog/287.misc.rst +1 -0
  9. dkist_processing_common/__init__.py +1 -0
  10. dkist_processing_common/_util/constants.py +1 -0
  11. dkist_processing_common/_util/graphql.py +1 -0
  12. dkist_processing_common/_util/scratch.py +9 -9
  13. dkist_processing_common/_util/tags.py +1 -0
  14. dkist_processing_common/codecs/array.py +20 -0
  15. dkist_processing_common/codecs/asdf.py +9 -3
  16. dkist_processing_common/codecs/basemodel.py +22 -0
  17. dkist_processing_common/codecs/bytes.py +1 -0
  18. dkist_processing_common/codecs/fits.py +37 -9
  19. dkist_processing_common/codecs/iobase.py +1 -0
  20. dkist_processing_common/codecs/json.py +1 -0
  21. dkist_processing_common/codecs/path.py +1 -0
  22. dkist_processing_common/codecs/quality.py +1 -1
  23. dkist_processing_common/codecs/str.py +1 -0
  24. dkist_processing_common/config.py +64 -25
  25. dkist_processing_common/manual.py +6 -8
  26. dkist_processing_common/models/constants.py +373 -37
  27. dkist_processing_common/models/dkist_location.py +27 -0
  28. dkist_processing_common/models/fits_access.py +48 -0
  29. dkist_processing_common/models/flower_pot.py +231 -9
  30. dkist_processing_common/models/fried_parameter.py +41 -0
  31. dkist_processing_common/models/graphql.py +66 -75
  32. dkist_processing_common/models/input_dataset.py +117 -0
  33. dkist_processing_common/models/message.py +1 -1
  34. dkist_processing_common/models/message_queue_binding.py +1 -1
  35. dkist_processing_common/models/metric_code.py +2 -0
  36. dkist_processing_common/models/parameters.py +65 -28
  37. dkist_processing_common/models/quality.py +50 -5
  38. dkist_processing_common/models/tags.py +23 -21
  39. dkist_processing_common/models/task_name.py +3 -2
  40. dkist_processing_common/models/telemetry.py +28 -0
  41. dkist_processing_common/models/wavelength.py +3 -1
  42. dkist_processing_common/parsers/average_bud.py +46 -0
  43. dkist_processing_common/parsers/cs_step.py +13 -12
  44. dkist_processing_common/parsers/dsps_repeat.py +6 -4
  45. dkist_processing_common/parsers/experiment_id_bud.py +12 -4
  46. dkist_processing_common/parsers/id_bud.py +42 -27
  47. dkist_processing_common/parsers/l0_fits_access.py +5 -3
  48. dkist_processing_common/parsers/l1_fits_access.py +51 -23
  49. dkist_processing_common/parsers/lookup_bud.py +125 -0
  50. dkist_processing_common/parsers/near_bud.py +21 -20
  51. dkist_processing_common/parsers/observing_program_id_bud.py +24 -0
  52. dkist_processing_common/parsers/proposal_id_bud.py +13 -5
  53. dkist_processing_common/parsers/quality.py +2 -0
  54. dkist_processing_common/parsers/retarder.py +32 -0
  55. dkist_processing_common/parsers/single_value_single_key_flower.py +6 -1
  56. dkist_processing_common/parsers/task.py +8 -6
  57. dkist_processing_common/parsers/time.py +178 -72
  58. dkist_processing_common/parsers/unique_bud.py +21 -22
  59. dkist_processing_common/parsers/wavelength.py +5 -3
  60. dkist_processing_common/tasks/__init__.py +3 -2
  61. dkist_processing_common/tasks/assemble_movie.py +4 -3
  62. dkist_processing_common/tasks/base.py +59 -60
  63. dkist_processing_common/tasks/l1_output_data.py +54 -53
  64. dkist_processing_common/tasks/mixin/globus.py +24 -27
  65. dkist_processing_common/tasks/mixin/interservice_bus.py +1 -0
  66. dkist_processing_common/tasks/mixin/metadata_store.py +108 -243
  67. dkist_processing_common/tasks/mixin/object_store.py +22 -0
  68. dkist_processing_common/tasks/mixin/quality/__init__.py +1 -0
  69. dkist_processing_common/tasks/mixin/quality/_base.py +8 -1
  70. dkist_processing_common/tasks/mixin/quality/_metrics.py +166 -14
  71. dkist_processing_common/tasks/output_data_base.py +4 -3
  72. dkist_processing_common/tasks/parse_l0_input_data.py +277 -15
  73. dkist_processing_common/tasks/quality_metrics.py +9 -9
  74. dkist_processing_common/tasks/teardown.py +7 -7
  75. dkist_processing_common/tasks/transfer_input_data.py +67 -69
  76. dkist_processing_common/tasks/trial_catalog.py +77 -17
  77. dkist_processing_common/tasks/trial_output_data.py +16 -17
  78. dkist_processing_common/tasks/write_l1.py +102 -72
  79. dkist_processing_common/tests/conftest.py +32 -173
  80. dkist_processing_common/tests/mock_metadata_store.py +271 -0
  81. dkist_processing_common/tests/test_assemble_movie.py +4 -4
  82. dkist_processing_common/tests/test_assemble_quality.py +32 -4
  83. dkist_processing_common/tests/test_base.py +5 -19
  84. dkist_processing_common/tests/test_codecs.py +103 -12
  85. dkist_processing_common/tests/test_constants.py +15 -0
  86. dkist_processing_common/tests/test_dkist_location.py +15 -0
  87. dkist_processing_common/tests/test_fits_access.py +56 -19
  88. dkist_processing_common/tests/test_flower_pot.py +147 -5
  89. dkist_processing_common/tests/test_fried_parameter.py +27 -0
  90. dkist_processing_common/tests/test_input_dataset.py +78 -361
  91. dkist_processing_common/tests/test_interservice_bus.py +1 -0
  92. dkist_processing_common/tests/test_interservice_bus_mixin.py +1 -1
  93. dkist_processing_common/tests/test_manual_processing.py +33 -0
  94. dkist_processing_common/tests/test_output_data_base.py +5 -7
  95. dkist_processing_common/tests/test_parameters.py +71 -22
  96. dkist_processing_common/tests/test_parse_l0_input_data.py +115 -32
  97. dkist_processing_common/tests/test_publish_catalog_messages.py +2 -24
  98. dkist_processing_common/tests/test_quality.py +1 -0
  99. dkist_processing_common/tests/test_quality_mixin.py +255 -23
  100. dkist_processing_common/tests/test_scratch.py +2 -1
  101. dkist_processing_common/tests/test_stems.py +511 -168
  102. dkist_processing_common/tests/test_submit_dataset_metadata.py +3 -7
  103. dkist_processing_common/tests/test_tags.py +1 -0
  104. dkist_processing_common/tests/test_task_name.py +1 -1
  105. dkist_processing_common/tests/test_task_parsing.py +17 -7
  106. dkist_processing_common/tests/test_teardown.py +28 -24
  107. dkist_processing_common/tests/test_transfer_input_data.py +270 -125
  108. dkist_processing_common/tests/test_transfer_l1_output_data.py +2 -3
  109. dkist_processing_common/tests/test_trial_catalog.py +83 -8
  110. dkist_processing_common/tests/test_trial_output_data.py +46 -73
  111. dkist_processing_common/tests/test_workflow_task_base.py +8 -10
  112. dkist_processing_common/tests/test_write_l1.py +298 -76
  113. dkist_processing_common-12.1.0rc1.dist-info/METADATA +265 -0
  114. dkist_processing_common-12.1.0rc1.dist-info/RECORD +134 -0
  115. {dkist_processing_common-10.5.4.dist-info → dkist_processing_common-12.1.0rc1.dist-info}/WHEEL +1 -1
  116. docs/conf.py +1 -0
  117. docs/index.rst +1 -1
  118. docs/landing_page.rst +13 -0
  119. dkist_processing_common/tasks/mixin/input_dataset.py +0 -166
  120. dkist_processing_common-10.5.4.dist-info/METADATA +0 -175
  121. dkist_processing_common-10.5.4.dist-info/RECORD +0 -112
  122. {dkist_processing_common-10.5.4.dist-info → dkist_processing_common-12.1.0rc1.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,18 @@
1
1
  """Time parser."""
2
+
2
3
  from datetime import datetime
3
4
  from datetime import timezone
5
+ from enum import StrEnum
4
6
  from typing import Callable
5
- from typing import Hashable
6
7
  from typing import Type
7
8
 
8
9
  import numpy as np
9
10
 
10
11
  from dkist_processing_common.models.constants import BudName
12
+ from dkist_processing_common.models.fits_access import MetadataKey
13
+ from dkist_processing_common.models.flower_pot import ListStem
14
+ from dkist_processing_common.models.flower_pot import SetStem
11
15
  from dkist_processing_common.models.flower_pot import SpilledDirt
12
- from dkist_processing_common.models.flower_pot import Stem
13
16
  from dkist_processing_common.models.tags import EXP_TIME_ROUND_DIGITS
14
17
  from dkist_processing_common.models.tags import StemName
15
18
  from dkist_processing_common.models.task_name import TaskName
@@ -19,7 +22,6 @@ from dkist_processing_common.parsers.single_value_single_key_flower import (
19
22
  )
20
23
  from dkist_processing_common.parsers.task import passthrough_header_ip_task
21
24
  from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
22
- from dkist_processing_common.parsers.unique_bud import UniqueBud
23
25
 
24
26
 
25
27
  class ObsIpStartTimeBud(TaskUniqueBud):
@@ -27,21 +29,56 @@ class ObsIpStartTimeBud(TaskUniqueBud):
27
29
 
28
30
  def __init__(self):
29
31
  super().__init__(
30
- constant_name=BudName.obs_ip_start_time.value,
31
- metadata_key="ip_start_time",
32
- ip_task_type=TaskName.observe.value,
32
+ constant_name=BudName.obs_ip_start_time,
33
+ metadata_key=MetadataKey.ip_start_time,
34
+ ip_task_types=TaskName.observe,
33
35
  )
34
36
 
35
37
 
36
- class CadenceBudBase(UniqueBud):
37
- """Base class for all Cadence Buds."""
38
+ class TaskDatetimeBudBase(ListStem):
39
+ """
40
+ Base class for making datetime-related buds.
38
41
 
39
- def __init__(self, constant_name: str):
40
- super().__init__(constant_name, metadata_key="time_obs")
42
+ Returns a tuple of sorted values converted from datetimes to unix seconds.
43
+
44
+ Complicated parsing of the header into a task type can be achieved by passing in a different
45
+ header task parsing function.
46
+
47
+ Parameters
48
+ ----------
49
+ stem_name
50
+ The name for the constant to be defined
51
+
52
+ metadata_key
53
+ The metadata key associated with the constant
54
+
55
+ ip_task_types
56
+ Only consider objects whose parsed header IP task type matches a string in this list
57
+
58
+ header_type_parsing_func
59
+ The function used to convert a header into an IP task type
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ stem_name: str,
65
+ metadata_key: str | StrEnum,
66
+ ip_task_types: str | list[str],
67
+ task_type_parsing_function: Callable = passthrough_header_ip_task,
68
+ ):
69
+ super().__init__(stem_name=stem_name)
70
+
71
+ if isinstance(metadata_key, StrEnum):
72
+ metadata_key = metadata_key.name
73
+ self.metadata_key = metadata_key
74
+ if isinstance(ip_task_types, str):
75
+ ip_task_types = [ip_task_types]
76
+ self.ip_task_types = [task.casefold() for task in ip_task_types]
77
+ self.header_parsing_function = task_type_parsing_function
41
78
 
42
79
  def setter(self, fits_obj: L0FitsAccess) -> float | Type[SpilledDirt]:
43
80
  """
44
- If the file is an observe file, its DATE-OBS value is stored as unix seconds.
81
+ Store the metadata key datetime value as unix seconds if the task type is in the desired types.
45
82
 
46
83
  Parameters
47
84
  ----------
@@ -49,106 +86,141 @@ class CadenceBudBase(UniqueBud):
49
86
  The input fits object
50
87
  Returns
51
88
  -------
52
- The observe time in seconds
89
+ The datetime in seconds
53
90
  """
54
- if fits_obj.ip_task_type.casefold() == TaskName.observe.value.casefold():
91
+ task = self.header_parsing_function(fits_obj)
92
+
93
+ if task.casefold() in self.ip_task_types:
55
94
  return (
56
95
  datetime.fromisoformat(getattr(fits_obj, self.metadata_key))
57
96
  .replace(tzinfo=timezone.utc)
58
97
  .timestamp()
59
98
  )
99
+
60
100
  return SpilledDirt
61
101
 
102
+ def getter(self) -> tuple[float, ...]:
103
+ """
104
+ Return a tuple of sorted times in unix seconds.
105
+
106
+ Returns
107
+ -------
108
+ A tuple that is sorted times in unix seconds
109
+ """
110
+ return tuple(sorted(self.value_list))
111
+
112
+
113
+ class CadenceBudBase(TaskDatetimeBudBase):
114
+ """Base class for all Cadence Buds."""
115
+
116
+ def __init__(self, constant_name: str):
117
+ super().__init__(
118
+ stem_name=constant_name,
119
+ metadata_key=MetadataKey.time_obs,
120
+ ip_task_types=TaskName.observe,
121
+ )
122
+
62
123
 
63
124
  class AverageCadenceBud(CadenceBudBase):
64
125
  """Class for the average cadence Bud."""
65
126
 
66
127
  def __init__(self):
67
- super().__init__(constant_name=BudName.average_cadence.value)
128
+ super().__init__(constant_name=BudName.average_cadence)
68
129
 
69
- def getter(self, key) -> np.float64:
130
+ def getter(self) -> np.float64:
70
131
  """
71
132
  Return the mean cadence between frames.
72
133
 
73
- Parameters
74
- ----------
75
- key
76
- The input key
77
-
78
134
  Returns
79
135
  -------
80
136
  The mean value of the cadences of the input frames
81
137
  """
82
- return np.mean(np.diff(sorted(list(self.key_to_petal_dict.values()))))
138
+ return np.mean(np.diff(super().getter()))
83
139
 
84
140
 
85
141
  class MaximumCadenceBud(CadenceBudBase):
86
142
  """Class for the maximum cadence bud."""
87
143
 
88
144
  def __init__(self):
89
- super().__init__(constant_name=BudName.maximum_cadence.value)
145
+ super().__init__(constant_name=BudName.maximum_cadence)
90
146
 
91
- def getter(self, key) -> np.float64:
147
+ def getter(self) -> np.float64:
92
148
  """
93
149
  Return the maximum cadence between frames.
94
150
 
95
- Parameters
96
- ----------
97
- key
98
- The input key
99
-
100
151
  Returns
101
152
  -------
102
153
  The maximum cadence between frames
103
154
  """
104
- return np.max(np.diff(sorted(list(self.key_to_petal_dict.values()))))
155
+ return np.max(np.diff(super().getter()))
105
156
 
106
157
 
107
158
  class MinimumCadenceBud(CadenceBudBase):
108
159
  """Class for the minimum cadence bud."""
109
160
 
110
161
  def __init__(self):
111
- super().__init__(constant_name=BudName.minimum_cadence.value)
162
+ super().__init__(constant_name=BudName.minimum_cadence)
112
163
 
113
- def getter(self, key) -> np.float64:
164
+ def getter(self) -> np.float64:
114
165
  """
115
166
  Return the minimum cadence between frames.
116
167
 
117
- Parameters
118
- ----------
119
- key
120
- The input key
121
-
122
168
  Returns
123
169
  -------
124
170
  The minimum cadence between frames
125
171
  """
126
- return np.min(np.diff(sorted(list(self.key_to_petal_dict.values()))))
172
+ return np.min(np.diff(super().getter()))
127
173
 
128
174
 
129
175
  class VarianceCadenceBud(CadenceBudBase):
130
176
  """Class for the variance cadence Bud."""
131
177
 
132
178
  def __init__(self):
133
- super().__init__(constant_name=BudName.variance_cadence.value)
179
+ super().__init__(constant_name=BudName.variance_cadence)
134
180
 
135
- def getter(self, key) -> np.float64:
181
+ def getter(self) -> np.float64:
136
182
  """
137
183
  Return the cadence variance between frames.
138
184
 
139
- Parameters
140
- ----------
141
- key
142
- The input key
143
185
  Returns
144
186
  -------
145
187
  Return the variance of the cadences over the input frames
146
188
  """
147
- return np.var(np.diff(sorted(list(self.key_to_petal_dict.values()))))
189
+ return np.var(np.diff(super().getter()))
148
190
 
149
191
 
150
- class TimeFlowerBase(SingleValueSingleKeyFlower):
151
- """Base task for SingleValueSingleKeyFlowers that need to round their values to avoid value jitter."""
192
+ class TaskDateBeginBud(TaskDatetimeBudBase):
193
+ """Class for the date begin task Bud."""
194
+
195
+ def __init__(
196
+ self,
197
+ constant_name: str,
198
+ ip_task_types: str | list[str],
199
+ task_type_parsing_function: Callable = passthrough_header_ip_task,
200
+ ):
201
+ super().__init__(
202
+ stem_name=constant_name,
203
+ metadata_key=MetadataKey.time_obs,
204
+ ip_task_types=ip_task_types,
205
+ task_type_parsing_function=task_type_parsing_function,
206
+ )
207
+
208
+ def getter(self) -> str:
209
+ """
210
+ Return the earliest date begin for the ip task type converted from unix seconds to datetime string.
211
+
212
+ Returns
213
+ -------
214
+ Return the minimum date begin as a datetime string
215
+ """
216
+ # super().getter() returns a sorted list
217
+ min_time = super().getter()[0]
218
+ min_time_dt = datetime.fromtimestamp(min_time, tz=timezone.utc)
219
+ return min_time_dt.strftime("%Y-%m-%dT%H:%M:%S.%f")
220
+
221
+
222
+ class RoundTimeFlowerBase(SingleValueSingleKeyFlower):
223
+ """Base flower for SingleValueSingleKeyFlowers that need to round their values to avoid value jitter."""
152
224
 
153
225
  def setter(self, fits_obj: L0FitsAccess):
154
226
  """
@@ -166,92 +238,126 @@ class TimeFlowerBase(SingleValueSingleKeyFlower):
166
238
  return round(raw_value, EXP_TIME_ROUND_DIGITS)
167
239
 
168
240
 
169
- class ExposureTimeFlower(TimeFlowerBase):
241
+ class ExposureTimeFlower(RoundTimeFlowerBase):
170
242
  """For tagging the frame FPA exposure time."""
171
243
 
172
244
  def __init__(self):
173
245
  super().__init__(
174
- tag_stem_name=StemName.exposure_time.value, metadata_key="fpa_exposure_time_ms"
246
+ tag_stem_name=StemName.exposure_time, metadata_key=MetadataKey.fpa_exposure_time_ms
175
247
  )
176
248
 
177
249
 
178
- class ReadoutExpTimeFlower(TimeFlowerBase):
250
+ class ReadoutExpTimeFlower(RoundTimeFlowerBase):
179
251
  """For tagging the exposure time of each readout that contributes to an FPA."""
180
252
 
181
253
  def __init__(self):
182
254
  super().__init__(
183
- tag_stem_name=StemName.readout_exp_time.value,
184
- metadata_key="sensor_readout_exposure_time_ms",
255
+ tag_stem_name=StemName.readout_exp_time,
256
+ metadata_key=MetadataKey.sensor_readout_exposure_time_ms,
185
257
  )
186
258
 
187
259
 
188
- class TaskTimeBudBase(Stem):
260
+ class TaskRoundTimeBudBase(SetStem):
189
261
  """
190
- Base class for making time-related buds that are computed for a specific task type.
262
+ Base class for making buds that need a set of rounded times for computing for specific task types.
191
263
 
192
- By "time-related" we mean values that generally need rounding when ingested into the database.
264
+ Metadata key values are already floats. Returns tuple of sorted unique rounded values.
193
265
 
194
266
  Complicated parsing of the header into a task type can be achieved by passing in a different
195
267
  header task parsing function.
268
+
269
+ Parameters
270
+ ----------
271
+ stem_name
272
+ The name for the constant to be defined
273
+
274
+ metadata_key
275
+ The metadata key associated with the constant
276
+
277
+ ip_task_types
278
+ Only consider objects whose parsed header IP task type matches a string in this list
279
+
280
+ header_task_parsing_func
281
+ The function used to convert a header into an IP task type
196
282
  """
197
283
 
198
284
  def __init__(
199
285
  self,
200
286
  stem_name: str,
201
- metadata_key: str,
202
- ip_task_type: str,
287
+ metadata_key: str | StrEnum,
288
+ ip_task_types: str | list[str],
203
289
  header_task_parsing_func: Callable = passthrough_header_ip_task,
204
290
  ):
205
291
  super().__init__(stem_name=stem_name)
292
+
293
+ if isinstance(metadata_key, StrEnum):
294
+ metadata_key = metadata_key.name
206
295
  self.metadata_key = metadata_key
207
- self.ip_task_type = ip_task_type
296
+ if isinstance(ip_task_types, str):
297
+ ip_task_types = [ip_task_types]
298
+ self.ip_task_types = [task.casefold() for task in ip_task_types]
208
299
  self.header_parsing_function = header_task_parsing_func
209
300
 
210
- def setter(self, fits_obj: L0FitsAccess):
211
- """Return the desired metadata key only if the parsed task type matches the Bud's task type."""
301
+ def setter(self, fits_obj: L0FitsAccess) -> float | Type[SpilledDirt]:
302
+ """
303
+ Store the metadata key value if the parsed task type is in the desired types.
304
+
305
+ Parameters
306
+ ----------
307
+ fits_obj
308
+ The input fits object
309
+ Returns
310
+ -------
311
+ The rounded time
312
+ """
212
313
  task = self.header_parsing_function(fits_obj)
213
314
 
214
- if task.casefold() == self.ip_task_type.casefold():
315
+ if task.casefold() in self.ip_task_types:
215
316
  raw_value = getattr(fits_obj, self.metadata_key)
216
317
  return round(raw_value, EXP_TIME_ROUND_DIGITS)
217
318
 
218
319
  return SpilledDirt
219
320
 
220
- def getter(self, key: Hashable) -> tuple[float, ...]:
221
- """Return a tuple of all the unique values found."""
222
- value_tuple = tuple(sorted(set(self.key_to_petal_dict.values())))
223
- return value_tuple
321
+ def getter(self) -> tuple[float, ...]:
322
+ """
323
+ Return a tuple of the sorted unique values found.
324
+
325
+ Returns
326
+ -------
327
+ A tuple that is the sorted set of unique times
328
+ """
329
+ return tuple(sorted(self.value_set))
224
330
 
225
331
 
226
- class TaskExposureTimesBud(TaskTimeBudBase):
332
+ class TaskExposureTimesBud(TaskRoundTimeBudBase):
227
333
  """Produce a tuple of all FPA exposure times present in the dataset for a specific ip task type."""
228
334
 
229
335
  def __init__(
230
336
  self,
231
337
  stem_name: str,
232
- ip_task_type: str,
338
+ ip_task_types: str | list[str],
233
339
  header_task_parsing_func: Callable = passthrough_header_ip_task,
234
340
  ):
235
341
  super().__init__(
236
342
  stem_name=stem_name,
237
- metadata_key="fpa_exposure_time_ms",
238
- ip_task_type=ip_task_type,
343
+ metadata_key=MetadataKey.fpa_exposure_time_ms,
344
+ ip_task_types=ip_task_types,
239
345
  header_task_parsing_func=header_task_parsing_func,
240
346
  )
241
347
 
242
348
 
243
- class TaskReadoutExpTimesBud(TaskTimeBudBase):
349
+ class TaskReadoutExpTimesBud(TaskRoundTimeBudBase):
244
350
  """Produce a tuple of all sensor readout exposure times present in the dataset for a specific task type."""
245
351
 
246
352
  def __init__(
247
353
  self,
248
354
  stem_name: str,
249
- ip_task_type: str,
355
+ ip_task_types: str | list[str],
250
356
  header_task_parsing_func: Callable = passthrough_header_ip_task,
251
357
  ):
252
358
  super().__init__(
253
359
  stem_name=stem_name,
254
- metadata_key="sensor_readout_exposure_time_ms",
255
- ip_task_type=ip_task_type,
360
+ metadata_key=MetadataKey.sensor_readout_exposure_time_ms,
361
+ ip_task_types=ip_task_types,
256
362
  header_task_parsing_func=header_task_parsing_func,
257
363
  )
@@ -1,15 +1,17 @@
1
1
  """Pre-made flower that reads a single header key from all files and raises a ValueError if it is not unique."""
2
+
3
+ from enum import StrEnum
2
4
  from typing import Callable
3
5
 
6
+ from dkist_processing_common.models.flower_pot import SetStem
4
7
  from dkist_processing_common.models.flower_pot import SpilledDirt
5
- from dkist_processing_common.models.flower_pot import Stem
6
8
  from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
7
9
  from dkist_processing_common.parsers.task import passthrough_header_ip_task
8
10
 
9
11
 
10
- class UniqueBud(Stem):
12
+ class UniqueBud(SetStem):
11
13
  """
12
- Pre-made flower that reads a single header key from all files and raises a ValueError if it is not unique.
14
+ Pre-made `SetStem` that reads a single header key from all files and raises a ValueError if it is not unique.
13
15
 
14
16
  Parameters
15
17
  ----------
@@ -23,9 +25,11 @@ class UniqueBud(Stem):
23
25
  def __init__(
24
26
  self,
25
27
  constant_name: str,
26
- metadata_key: str,
28
+ metadata_key: str | StrEnum,
27
29
  ):
28
30
  super().__init__(stem_name=constant_name)
31
+ if isinstance(metadata_key, StrEnum):
32
+ metadata_key = metadata_key.name
29
33
  self.metadata_key = metadata_key
30
34
 
31
35
  def setter(self, fits_obj: L0FitsAccess):
@@ -42,29 +46,22 @@ class UniqueBud(Stem):
42
46
  """
43
47
  return getattr(fits_obj, self.metadata_key)
44
48
 
45
- def getter(self, key):
49
+ def getter(self):
46
50
  """
47
51
  Get the value for this key and raise an error if it is not unique.
48
52
 
49
- Parameters
50
- ----------
51
- key
52
- The input key
53
53
  Returns
54
54
  -------
55
55
  The value associated with this input key
56
56
  """
57
- value_set = set(self.key_to_petal_dict.values())
58
- if len(value_set) > 1:
59
- raise ValueError(
60
- f"Multiple {self.stem_name} values found for key {key}. Values: {value_set}"
61
- )
62
- return value_set.pop()
57
+ if len(self.value_set) > 1:
58
+ raise ValueError(f"Multiple {self.stem_name} values found! Values: {self.value_set}")
59
+ return self.value_set.pop()
63
60
 
64
61
 
65
62
  class TaskUniqueBud(UniqueBud):
66
63
  """
67
- Subclass of `UniqueBud` that only considers objects that have a specific task type.
64
+ Subclass of `UniqueBud` that only considers objects that have specific task types.
68
65
 
69
66
  Parameters
70
67
  ----------
@@ -74,8 +71,8 @@ class TaskUniqueBud(UniqueBud):
74
71
  metadata_key
75
72
  The metadata key associated with the constant
76
73
 
77
- ip_task_type
78
- Only consider objects whose parsed header IP task type matches this string
74
+ ip_task_types
75
+ Only consider objects whose parsed header IP task type matches a string in this list
79
76
 
80
77
  task_type_parsing_function
81
78
  The function used to convert a header into an IP task type
@@ -84,20 +81,22 @@ class TaskUniqueBud(UniqueBud):
84
81
  def __init__(
85
82
  self,
86
83
  constant_name: str,
87
- metadata_key: str,
88
- ip_task_type: str,
84
+ metadata_key: str | StrEnum,
85
+ ip_task_types: str | list[str],
89
86
  task_type_parsing_function: Callable = passthrough_header_ip_task,
90
87
  ):
91
88
  super().__init__(constant_name=constant_name, metadata_key=metadata_key)
92
89
 
93
- self.ip_task_type = ip_task_type.casefold()
90
+ if isinstance(ip_task_types, str):
91
+ ip_task_types = [ip_task_types]
92
+ self.ip_task_types = [task.casefold() for task in ip_task_types]
94
93
  self.parsing_function = task_type_parsing_function
95
94
 
96
95
  def setter(self, fits_obj: L0FitsAccess):
97
96
  """Ingest an object only if its parsed IP task type matches what's desired."""
98
97
  task = self.parsing_function(fits_obj)
99
98
 
100
- if task.casefold() == self.ip_task_type:
99
+ if task.casefold() in self.ip_task_types:
101
100
  return super().setter(fits_obj)
102
101
 
103
102
  return SpilledDirt
@@ -1,5 +1,7 @@
1
1
  """Bud to get the wavelength of observe frames."""
2
+
2
3
  from dkist_processing_common.models.constants import BudName
4
+ from dkist_processing_common.models.fits_access import MetadataKey
3
5
  from dkist_processing_common.models.task_name import TaskName
4
6
  from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
5
7
 
@@ -9,7 +11,7 @@ class ObserveWavelengthBud(TaskUniqueBud):
9
11
 
10
12
  def __init__(self):
11
13
  super().__init__(
12
- constant_name=BudName.wavelength.value,
13
- metadata_key="wavelength",
14
- ip_task_type=TaskName.observe.value,
14
+ constant_name=BudName.wavelength,
15
+ metadata_key=MetadataKey.wavelength,
16
+ ip_task_types=TaskName.observe,
15
17
  )
@@ -1,11 +1,12 @@
1
1
  """Common tasks and bases."""
2
- from dkist_processing_common.tasks.base import * # noreorder
2
+
3
+ from dkist_processing_common.tasks.base import * # isort: skip
3
4
  from dkist_processing_common.tasks.assemble_movie import *
4
5
  from dkist_processing_common.tasks.l1_output_data import *
5
6
  from dkist_processing_common.tasks.parse_l0_input_data import *
6
7
  from dkist_processing_common.tasks.quality_metrics import *
7
8
  from dkist_processing_common.tasks.teardown import *
8
9
  from dkist_processing_common.tasks.transfer_input_data import *
9
- from dkist_processing_common.tasks.write_l1 import *
10
10
  from dkist_processing_common.tasks.trial_catalog import *
11
11
  from dkist_processing_common.tasks.trial_output_data import *
12
+ from dkist_processing_common.tasks.write_l1 import *
@@ -1,11 +1,12 @@
1
1
  """Task(s) for assembling a browse movie."""
2
+
2
3
  import logging
3
4
  from abc import ABC
4
5
  from abc import abstractmethod
6
+ from importlib.resources import files
5
7
  from typing import Literal
6
8
 
7
9
  import numpy as np
8
- import pkg_resources
9
10
  from matplotlib import colormaps
10
11
  from moviepy import VideoClip
11
12
  from PIL import Image
@@ -66,7 +67,7 @@ class AssembleMovie(WorkflowTaskBase, ABC):
66
67
  MINIMUM_DURATION = 10 # seconds
67
68
  MAXIMUM_DURATION = 60 # seconds
68
69
  FPS = 15
69
- FONT_FILE = pkg_resources.resource_filename("dkist_processing_common", "fonts/Lato-Regular.ttf")
70
+ FONT_FILE = files("dkist_processing_common").joinpath("fonts/Lato-Regular.ttf")
70
71
  TEXT_MARGIN_PX = 5
71
72
  MPL_COLOR_MAP = "viridis"
72
73
 
@@ -162,7 +163,7 @@ class AssembleMovie(WorkflowTaskBase, ABC):
162
163
  relative_movie_path = f"{self.constants.dataset_id}_browse_movie.mp4"
163
164
  absolute_movie_path = str(self.scratch.absolute_path(relative_movie_path))
164
165
 
165
- with self.apm_processing_step("Assembling movie frames"):
166
+ with self.telemetry_span("Assembling movie frames"):
166
167
  clip.write_videofile(absolute_movie_path, fps=self.FPS, codec="libx264", audio=False)
167
168
 
168
169
  self.tag(path=absolute_movie_path, tags=[Tag.movie(), Tag.output()])