dkist-processing-common 12.0.0rc5__py3-none-any.whl → 12.2.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.
- dkist_processing_common/codecs/fits.py +27 -6
- dkist_processing_common/models/constants.py +16 -10
- dkist_processing_common/models/extras.py +35 -0
- dkist_processing_common/models/flower_pot.py +230 -9
- dkist_processing_common/models/tags.py +13 -0
- dkist_processing_common/parsers/average_bud.py +0 -2
- dkist_processing_common/parsers/cs_step.py +10 -10
- dkist_processing_common/parsers/id_bud.py +8 -10
- dkist_processing_common/parsers/lookup_bud.py +7 -11
- dkist_processing_common/parsers/near_bud.py +7 -12
- dkist_processing_common/parsers/retarder.py +9 -13
- dkist_processing_common/parsers/time.py +19 -55
- dkist_processing_common/parsers/unique_bud.py +7 -14
- dkist_processing_common/tasks/l1_output_data.py +23 -14
- dkist_processing_common/tasks/output_data_base.py +25 -4
- dkist_processing_common/tasks/parse_l0_input_data.py +4 -2
- dkist_processing_common/tasks/transfer_input_data.py +1 -0
- dkist_processing_common/tasks/write_extra.py +333 -0
- dkist_processing_common/tasks/write_l1.py +2 -55
- dkist_processing_common/tasks/write_l1_base.py +67 -0
- dkist_processing_common/tests/test_codecs.py +57 -11
- dkist_processing_common/tests/test_construct_dataset_extras.py +224 -0
- dkist_processing_common/tests/test_flower_pot.py +147 -5
- dkist_processing_common/tests/test_output_data_base.py +24 -2
- dkist_processing_common/tests/test_parse_l0_input_data.py +28 -4
- dkist_processing_common/tests/test_stems.py +140 -193
- dkist_processing_common/tests/test_transfer_l1_output_data.py +1 -0
- dkist_processing_common/tests/test_trial_catalog.py +2 -0
- dkist_processing_common/tests/test_workflow_task_base.py +0 -11
- dkist_processing_common/tests/test_write_l1.py +0 -1
- {dkist_processing_common-12.0.0rc5.dist-info → dkist_processing_common-12.2.0.dist-info}/METADATA +4 -4
- {dkist_processing_common-12.0.0rc5.dist-info → dkist_processing_common-12.2.0.dist-info}/RECORD +34 -31
- {dkist_processing_common-12.0.0rc5.dist-info → dkist_processing_common-12.2.0.dist-info}/WHEEL +1 -1
- changelog/288.misc.rst +0 -1
- {dkist_processing_common-12.0.0rc5.dist-info → dkist_processing_common-12.2.0.dist-info}/top_level.txt +0 -0
|
@@ -31,15 +31,30 @@ def fits_hdulist_encoder(hdu_list: fits.HDUList) -> bytes:
|
|
|
31
31
|
return iobase_encoder(file_obj)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
def fits_hdu_decoder(
|
|
34
|
+
def fits_hdu_decoder(
|
|
35
|
+
path: Path,
|
|
36
|
+
hdu: int | None = None,
|
|
37
|
+
checksum: bool = True,
|
|
38
|
+
disable_image_compression: bool = False,
|
|
39
|
+
) -> fits.PrimaryHDU | fits.CompImageHDU:
|
|
35
40
|
"""Read a Path with `fits` to produce an `HDUList`."""
|
|
36
|
-
hdu_list = fits.open(
|
|
41
|
+
hdu_list = fits.open(
|
|
42
|
+
path, checksum=checksum, disable_image_compression=disable_image_compression
|
|
43
|
+
)
|
|
37
44
|
return _extract_hdu(hdu_list, hdu)
|
|
38
45
|
|
|
39
46
|
|
|
40
|
-
def fits_array_decoder(
|
|
47
|
+
def fits_array_decoder(
|
|
48
|
+
path: Path,
|
|
49
|
+
hdu: int | None = None,
|
|
50
|
+
auto_squeeze: bool = True,
|
|
51
|
+
checksum: bool = True,
|
|
52
|
+
disable_image_compression: bool = False,
|
|
53
|
+
) -> np.ndarray:
|
|
41
54
|
"""Read a Path with `fits` and return the `.data` property."""
|
|
42
|
-
hdu = fits_hdu_decoder(
|
|
55
|
+
hdu = fits_hdu_decoder(
|
|
56
|
+
path, hdu=hdu, checksum=checksum, disable_image_compression=disable_image_compression
|
|
57
|
+
)
|
|
43
58
|
data = hdu.data
|
|
44
59
|
|
|
45
60
|
# This conditional is explicitly to catch summit data with a dummy first axis for WCS
|
|
@@ -50,10 +65,16 @@ def fits_array_decoder(path: Path, hdu: int | None = None, auto_squeeze: bool =
|
|
|
50
65
|
|
|
51
66
|
|
|
52
67
|
def fits_access_decoder(
|
|
53
|
-
path: Path,
|
|
68
|
+
path: Path,
|
|
69
|
+
fits_access_class: Type[FitsAccessBase],
|
|
70
|
+
checksum: bool = True,
|
|
71
|
+
disable_image_compression: bool = False,
|
|
72
|
+
**fits_access_kwargs,
|
|
54
73
|
) -> FitsAccessBase:
|
|
55
74
|
"""Read a Path with `fits` and ingest into a `FitsAccessBase`-type object."""
|
|
56
|
-
hdu = fits_hdu_decoder(
|
|
75
|
+
hdu = fits_hdu_decoder(
|
|
76
|
+
path, checksum=checksum, disable_image_compression=disable_image_compression
|
|
77
|
+
)
|
|
57
78
|
return fits_access_class(hdu=hdu, name=str(path), **fits_access_kwargs)
|
|
58
79
|
|
|
59
80
|
|
|
@@ -285,18 +285,24 @@ class ConstantsBase:
|
|
|
285
285
|
def dark_observing_program_execution_ids(self) -> list[str]:
|
|
286
286
|
"""Return the observing program execution ids constant for the dark task."""
|
|
287
287
|
observing_programs = self._db_dict[BudName.dark_observing_program_execution_ids]
|
|
288
|
+
if isinstance(observing_programs, str):
|
|
289
|
+
observing_programs = [observing_programs]
|
|
288
290
|
return list(observing_programs)
|
|
289
291
|
|
|
290
292
|
@property
|
|
291
293
|
def solar_gain_observing_program_execution_ids(self) -> list[str]:
|
|
292
294
|
"""Return the observing program execution ids constant for the solar_gain task."""
|
|
293
295
|
observing_programs = self._db_dict[BudName.solar_gain_observing_program_execution_ids]
|
|
296
|
+
if isinstance(observing_programs, str):
|
|
297
|
+
observing_programs = [observing_programs]
|
|
294
298
|
return list(observing_programs)
|
|
295
299
|
|
|
296
300
|
@property
|
|
297
301
|
def polcal_observing_program_execution_ids(self) -> list[str]:
|
|
298
302
|
"""Return the observing program execution ids constant."""
|
|
299
303
|
observing_programs = self._db_dict[BudName.polcal_observing_program_execution_ids]
|
|
304
|
+
if isinstance(observing_programs, str):
|
|
305
|
+
observing_programs = [observing_programs]
|
|
300
306
|
return list(observing_programs)
|
|
301
307
|
|
|
302
308
|
@property
|
|
@@ -333,8 +339,8 @@ class ConstantsBase:
|
|
|
333
339
|
def dark_num_raw_frames_per_fpa(self) -> dict[float, list]:
|
|
334
340
|
"""Return the dictionary of exposure times to number of raw frames per fpa."""
|
|
335
341
|
raw_return = self._db_dict[BudName.dark_num_raw_frames_per_fpa]
|
|
336
|
-
# convert
|
|
337
|
-
return {k: v for k, v in raw_return}
|
|
342
|
+
# convert JSON string keys back to float
|
|
343
|
+
return {float(k): v for k, v in raw_return.items()}
|
|
338
344
|
|
|
339
345
|
@property
|
|
340
346
|
def solar_gain_num_raw_frames_per_fpa(self) -> int:
|
|
@@ -467,14 +473,14 @@ class ConstantsBase:
|
|
|
467
473
|
return self._db_dict[BudName.solar_gain_gos_polarizer_status]
|
|
468
474
|
|
|
469
475
|
@property
|
|
470
|
-
def dark_gos_polarizer_angle(self) ->
|
|
476
|
+
def dark_gos_polarizer_angle(self) -> str:
|
|
471
477
|
"""Return the gos polarizer angle constant for the dark task."""
|
|
472
|
-
return self._db_dict[BudName.dark_gos_polarizer_angle]
|
|
478
|
+
return str(self._db_dict[BudName.dark_gos_polarizer_angle])
|
|
473
479
|
|
|
474
480
|
@property
|
|
475
|
-
def solar_gain_gos_polarizer_angle(self) ->
|
|
481
|
+
def solar_gain_gos_polarizer_angle(self) -> str:
|
|
476
482
|
"""Return the gos polarizer angle constant for the solar gain task."""
|
|
477
|
-
return self._db_dict[BudName.solar_gain_gos_polarizer_angle]
|
|
483
|
+
return str(self._db_dict[BudName.solar_gain_gos_polarizer_angle])
|
|
478
484
|
|
|
479
485
|
@property
|
|
480
486
|
def dark_gos_retarder_status(self) -> str:
|
|
@@ -487,14 +493,14 @@ class ConstantsBase:
|
|
|
487
493
|
return self._db_dict[BudName.solar_gain_gos_retarder_status]
|
|
488
494
|
|
|
489
495
|
@property
|
|
490
|
-
def dark_gos_retarder_angle(self) ->
|
|
496
|
+
def dark_gos_retarder_angle(self) -> str:
|
|
491
497
|
"""Return the gos retarder angle constant for the dark task."""
|
|
492
|
-
return self._db_dict[BudName.dark_gos_retarder_angle]
|
|
498
|
+
return str(self._db_dict[BudName.dark_gos_retarder_angle])
|
|
493
499
|
|
|
494
500
|
@property
|
|
495
|
-
def solar_gain_gos_retarder_angle(self) ->
|
|
501
|
+
def solar_gain_gos_retarder_angle(self) -> str:
|
|
496
502
|
"""Return the gos retarder angle constant for the solar gain task."""
|
|
497
|
-
return self._db_dict[BudName.solar_gain_gos_retarder_angle]
|
|
503
|
+
return str(self._db_dict[BudName.solar_gain_gos_retarder_angle])
|
|
498
504
|
|
|
499
505
|
@property
|
|
500
506
|
def dark_gos_level0_status(self) -> str:
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Autocomplete access to dataset extra header sections."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DatasetExtraHeaderSection(StrEnum):
|
|
7
|
+
"""Enum defining the possible header sections for dataset extras."""
|
|
8
|
+
|
|
9
|
+
common = "common"
|
|
10
|
+
aggregate = "aggregate"
|
|
11
|
+
iptask = "iptask"
|
|
12
|
+
gos = "gos"
|
|
13
|
+
wavecal = "wavecal"
|
|
14
|
+
atlas = "atlas"
|
|
15
|
+
test = "test"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DatasetExtraType(StrEnum):
|
|
19
|
+
"""Enum defining options for dataset extra names."""
|
|
20
|
+
|
|
21
|
+
dark = "DARK"
|
|
22
|
+
background_light = "BACKGROUND LIGHT"
|
|
23
|
+
solar_gain = "SOLAR GAIN"
|
|
24
|
+
characteristic_spectra = "CHARACTERISTIC SPECTRA"
|
|
25
|
+
modulation_state_offsets = "MODULATION STATE OFFSETS"
|
|
26
|
+
beam_angles = "BEAM ANGLES"
|
|
27
|
+
spectral_curvature_shifts = "SPECTRAL CURVATURE SHIFTS"
|
|
28
|
+
wavelength_calibration_input_spectrum = "WAVELENGTH CALIBRATION INPUT SPECTRUM"
|
|
29
|
+
wavelength_calibration_reference_spectrum = "WAVELENGTH CALIBRATION REFERENCE SPECTRUM"
|
|
30
|
+
reference_wavelength_vector = "REFERENCE WAVELENGTH VECTOR"
|
|
31
|
+
demodulation_matrices = "DEMODULATION MATRICES"
|
|
32
|
+
polcal_as_science = "POLCAL AS SCIENCE"
|
|
33
|
+
bad_pixel_map = "BAD PIXEL MAP"
|
|
34
|
+
beam_offsets = "BEAM OFFSETS"
|
|
35
|
+
spectral_curvature_scales = "SPECTRAL CURVATURE SCALES"
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Framework for grouping multiple keys and values with arbitrary logic.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
Stem
|
|
4
|
+
The key components are:
|
|
5
|
+
**Stem:** ABC for groupings that depend on both the key and (maybe) value. Subgroups (Petals) are implied but not enforced.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**ListStem:** ABC for groups that depend on value only. More limited, but faster than `Stem` for cases where the keys don't matter.
|
|
8
|
+
|
|
9
|
+
**SetStem:** ABC for groups that depend on value only and the values are well represented by a `set`. Even more limited, but faster than `Stem` for cases where the keys don't matter.
|
|
10
|
+
|
|
11
|
+
**FlowerPot:** Container for Stem children (Flowers)
|
|
8
12
|
"""
|
|
9
13
|
|
|
10
14
|
from __future__ import annotations
|
|
@@ -17,7 +21,7 @@ from typing import Any
|
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
class FlowerPot:
|
|
20
|
-
"""Base class to hold multiple sets (
|
|
24
|
+
"""Base class to hold multiple sets (Stems) of key, value pairs."""
|
|
21
25
|
|
|
22
26
|
def __init__(self):
|
|
23
27
|
self.stems: list[Stem] = list()
|
|
@@ -55,9 +59,9 @@ class FlowerPot:
|
|
|
55
59
|
|
|
56
60
|
class SpilledDirt:
|
|
57
61
|
"""
|
|
58
|
-
A custom class for when a
|
|
62
|
+
A custom class for when a Stem wants the FlowerPot to skip that particular key/value.
|
|
59
63
|
|
|
60
|
-
Exists because None, False, [], (), etc. etc. are all valid
|
|
64
|
+
Exists because None, False, [], (), etc. etc. are all valid Stem return values
|
|
61
65
|
"""
|
|
62
66
|
|
|
63
67
|
|
|
@@ -90,7 +94,7 @@ class Petal:
|
|
|
90
94
|
|
|
91
95
|
class Stem(ABC):
|
|
92
96
|
"""
|
|
93
|
-
Base
|
|
97
|
+
Base class for grouping keys via arbitrary logic on the total collection of keys and values.
|
|
94
98
|
|
|
95
99
|
Parameters
|
|
96
100
|
----------
|
|
@@ -133,6 +137,15 @@ class Stem(ABC):
|
|
|
133
137
|
|
|
134
138
|
return self._petal_cache
|
|
135
139
|
|
|
140
|
+
@property
|
|
141
|
+
def can_be_picked(self) -> bool:
|
|
142
|
+
"""
|
|
143
|
+
Return True if there are any values to be picked.
|
|
144
|
+
|
|
145
|
+
A `Stem` could have no values even after dirt is added if all of the results were `SpilledDirt`.
|
|
146
|
+
"""
|
|
147
|
+
return len(self.petals) > 0
|
|
148
|
+
|
|
136
149
|
def _generate_petal_list(self) -> None:
|
|
137
150
|
"""
|
|
138
151
|
Generate a list of petals.
|
|
@@ -146,7 +159,7 @@ class Stem(ABC):
|
|
|
146
159
|
changes, `key_to_petal_dict`, is unhashable.
|
|
147
160
|
"""
|
|
148
161
|
petal_to_key_dict = defaultdict(list)
|
|
149
|
-
for key
|
|
162
|
+
for key in self.key_to_petal_dict.keys():
|
|
150
163
|
petal = self.getter(key)
|
|
151
164
|
petal_to_key_dict[petal].append(key)
|
|
152
165
|
|
|
@@ -180,7 +193,7 @@ class Stem(ABC):
|
|
|
180
193
|
@abstractmethod
|
|
181
194
|
def getter(self, key: Hashable) -> Hashable:
|
|
182
195
|
"""
|
|
183
|
-
Logic to apply to all ingested values when picking the
|
|
196
|
+
Logic to apply to all ingested values when picking the Stem.
|
|
184
197
|
|
|
185
198
|
Implemented in derived class.
|
|
186
199
|
|
|
@@ -193,3 +206,211 @@ class Stem(ABC):
|
|
|
193
206
|
The value
|
|
194
207
|
"""
|
|
195
208
|
pass
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class ListStem(ABC):
|
|
212
|
+
"""
|
|
213
|
+
Base class for collecting and applying logic to values in a `list` with a `Stem`-like interface.
|
|
214
|
+
|
|
215
|
+
Unlike the full `Stem`, this class does NOT retain information about the keys and thus does no grouping of keys based
|
|
216
|
+
on values. The direct consequence of this is that the `.petals` property is undefined and will raise an ``AttributeError``
|
|
217
|
+
if accessed. This also means there is no need to invert the `key_to_petal_dict` (because it doesn't exist), which,
|
|
218
|
+
in turn, means there is no need to run the `getter` for every key. The result is that the `bud` property only needs
|
|
219
|
+
one call to `getter`. Thus, the calculation of a single value derived from all values (i.e., `bud`) is much faster
|
|
220
|
+
than using a full `Stem`.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
stem_name
|
|
225
|
+
The name to be associated with the stem
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
def __init__(self, stem_name: Any):
|
|
229
|
+
self.stem_name = stem_name
|
|
230
|
+
self.value_list: list = list()
|
|
231
|
+
self._need_to_compute_bud_value: bool = True
|
|
232
|
+
|
|
233
|
+
def update(self, key: Any, value: Any) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Ingest a single key/value pair. Note that the ``key`` is not used.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
key
|
|
240
|
+
The key (unused)
|
|
241
|
+
|
|
242
|
+
value
|
|
243
|
+
The value
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
None
|
|
248
|
+
"""
|
|
249
|
+
result = self.setter(value)
|
|
250
|
+
if result is not SpilledDirt:
|
|
251
|
+
self.value_list.append(result)
|
|
252
|
+
self._need_to_compute_bud_value = True
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
def petals(self) -> None:
|
|
256
|
+
"""Raise an error because `ListStem` does not retain key information and therefore cannot group keys."""
|
|
257
|
+
raise AttributeError(
|
|
258
|
+
f"{self.__class__.__name__} subclasses ListStem and therefore does not define the `petals` property"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def can_be_picked(self) -> bool:
|
|
263
|
+
"""
|
|
264
|
+
Return True if there are any values to be picked.
|
|
265
|
+
|
|
266
|
+
A `Stem` could have no values even after dirt is added if all of the results were `SpilledDirt`.
|
|
267
|
+
"""
|
|
268
|
+
return len(self.value_list) > 0
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def bud(self) -> Petal:
|
|
272
|
+
"""Return the result of `getter` packaged in a `Petal` object."""
|
|
273
|
+
if self._need_to_compute_bud_value:
|
|
274
|
+
self._value_cache = self.getter()
|
|
275
|
+
self._need_to_compute_bud_value = False
|
|
276
|
+
|
|
277
|
+
return Petal((self._value_cache, "LISTSTEM_NOT_USED"))
|
|
278
|
+
|
|
279
|
+
@abstractmethod
|
|
280
|
+
def setter(self, value: Any) -> Any:
|
|
281
|
+
"""
|
|
282
|
+
Logic to apply to a single value pair on ingest.
|
|
283
|
+
|
|
284
|
+
Implemented in derived class.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
value
|
|
289
|
+
The value to be added
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
Any
|
|
294
|
+
"""
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
@abstractmethod
|
|
298
|
+
def getter(self) -> Any:
|
|
299
|
+
"""
|
|
300
|
+
Logic to apply to all ingested values when computing the `bud`.
|
|
301
|
+
|
|
302
|
+
Implemented in derived class.
|
|
303
|
+
|
|
304
|
+
Returns
|
|
305
|
+
-------
|
|
306
|
+
The value of the bud
|
|
307
|
+
"""
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class SetStem(ABC):
|
|
312
|
+
"""
|
|
313
|
+
Base class for collecting and applying logic to values in a `set` with a `Stem`-like interface.
|
|
314
|
+
|
|
315
|
+
Unlike the full `Stem`, this class does NOT retain information about the keys and thus does no grouping of keys based
|
|
316
|
+
on values. The direct consequence of this is that the `.petals` property is undefined and will raise an ``AttributeError``
|
|
317
|
+
if accessed. This also means there is no need to invert the `key_to_petal_dict` (because it doesn't exist), which,
|
|
318
|
+
in turn, means there is no need to run the `getter` for every key. The result is that the `bud` property only needs
|
|
319
|
+
one call to `getter`. Combined with the efficiency of storing values in a `set`, the calculation of a single value
|
|
320
|
+
derived from all values (i.e., `bud`) is much faster than using a full `Stem`.
|
|
321
|
+
|
|
322
|
+
.. Note::
|
|
323
|
+
The use of a `set` as the underlying storage mechanism means information regarding how many times a particular value
|
|
324
|
+
is present will be lost. It also means the return type of `setter` must be hashable. Both of these constraints can
|
|
325
|
+
be avoided by using `ListStem`, which still gets a significant speedup over `Stem` by dropping key information.
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
stem_name
|
|
331
|
+
The name to be associated with the stem
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
def __init__(self, stem_name: Any):
|
|
335
|
+
self.stem_name = stem_name
|
|
336
|
+
self.value_set: set = set()
|
|
337
|
+
self._need_to_compute_bud_value: bool = True
|
|
338
|
+
|
|
339
|
+
def update(self, key: Any, value: Any) -> None:
|
|
340
|
+
"""
|
|
341
|
+
Ingest a single key/value pair. Note that the ``key`` is not used.
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
----------
|
|
345
|
+
key
|
|
346
|
+
The key (unused)
|
|
347
|
+
|
|
348
|
+
value
|
|
349
|
+
The value
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
None
|
|
354
|
+
"""
|
|
355
|
+
result = self.setter(value)
|
|
356
|
+
if result is not SpilledDirt:
|
|
357
|
+
self.value_set.add(result)
|
|
358
|
+
self._need_to_compute_bud_value = True
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def petals(self) -> None:
|
|
362
|
+
"""Raise an error because `SetStem` does not retain key information and therefore cannot group keys."""
|
|
363
|
+
raise AttributeError(
|
|
364
|
+
f"{self.__class__.__name__} subclasses SetStem and therefore does not define the `petals` property"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def can_be_picked(self) -> bool:
|
|
369
|
+
"""
|
|
370
|
+
Return True if there are any values to be picked.
|
|
371
|
+
|
|
372
|
+
A `Stem` could have no values even after dirt is added if all of the results were `SpilledDirt`.
|
|
373
|
+
"""
|
|
374
|
+
return len(self.value_set) > 0
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def bud(self) -> Petal:
|
|
378
|
+
"""Return the result of `getter` packaged in a `Petal` object."""
|
|
379
|
+
if self._need_to_compute_bud_value:
|
|
380
|
+
self._value_cache = self.getter()
|
|
381
|
+
self._need_to_compute_bud_value = False
|
|
382
|
+
|
|
383
|
+
return Petal((self._value_cache, "SETSTEM_NOT_USED"))
|
|
384
|
+
|
|
385
|
+
@abstractmethod
|
|
386
|
+
def setter(self, value: Any) -> Hashable:
|
|
387
|
+
"""
|
|
388
|
+
Logic to apply to a single value pair on ingest.
|
|
389
|
+
|
|
390
|
+
Must return a Hashable object because the result will be stored in a `set`.
|
|
391
|
+
|
|
392
|
+
Implemented in derived class.
|
|
393
|
+
|
|
394
|
+
Parameters
|
|
395
|
+
----------
|
|
396
|
+
value
|
|
397
|
+
The value to be added
|
|
398
|
+
|
|
399
|
+
Returns
|
|
400
|
+
-------
|
|
401
|
+
Any
|
|
402
|
+
"""
|
|
403
|
+
pass
|
|
404
|
+
|
|
405
|
+
@abstractmethod
|
|
406
|
+
def getter(self) -> Any:
|
|
407
|
+
"""
|
|
408
|
+
Logic to apply to all ingested values when computing the `bud`.
|
|
409
|
+
|
|
410
|
+
Implemented in derived class.
|
|
411
|
+
|
|
412
|
+
Returns
|
|
413
|
+
-------
|
|
414
|
+
The value of the bud
|
|
415
|
+
"""
|
|
416
|
+
pass
|
|
@@ -38,6 +38,8 @@ class StemName(StrEnum):
|
|
|
38
38
|
dataset_inventory = "DATASET_INVENTORY"
|
|
39
39
|
asdf = "ASDF"
|
|
40
40
|
quality_report = "QUALITY_REPORT"
|
|
41
|
+
# Dataset extras
|
|
42
|
+
extra = "EXTRA"
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
class Tag:
|
|
@@ -450,3 +452,14 @@ class Tag:
|
|
|
450
452
|
An asdf tag
|
|
451
453
|
"""
|
|
452
454
|
return cls.format_tag(StemName.asdf)
|
|
455
|
+
|
|
456
|
+
@classmethod
|
|
457
|
+
def extra(cls) -> str:
|
|
458
|
+
"""
|
|
459
|
+
Return a dataset extra tag.
|
|
460
|
+
|
|
461
|
+
Returns
|
|
462
|
+
-------
|
|
463
|
+
A dataset extra tag
|
|
464
|
+
"""
|
|
465
|
+
return cls.format_tag(StemName.extra)
|
|
@@ -7,6 +7,7 @@ from datetime import timezone
|
|
|
7
7
|
from typing import Type
|
|
8
8
|
|
|
9
9
|
from dkist_processing_common.models.constants import BudName
|
|
10
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
10
11
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
11
12
|
from dkist_processing_common.models.flower_pot import Stem
|
|
12
13
|
from dkist_processing_common.models.tags import StemName
|
|
@@ -103,6 +104,7 @@ class CSStepFlower(Stem):
|
|
|
103
104
|
def __init__(self, max_cs_step_time_sec: float):
|
|
104
105
|
super().__init__(stem_name=StemName.cs_step)
|
|
105
106
|
self.max_cs_step_time_sec = max_cs_step_time_sec
|
|
107
|
+
self.CS_step_set = set()
|
|
106
108
|
|
|
107
109
|
def setter(self, fits_obj: L0FitsAccess) -> CSStep | Type[SpilledDirt]:
|
|
108
110
|
"""
|
|
@@ -119,7 +121,10 @@ class CSStepFlower(Stem):
|
|
|
119
121
|
"""
|
|
120
122
|
if fits_obj.ip_task_type != "polcal":
|
|
121
123
|
return SpilledDirt
|
|
122
|
-
|
|
124
|
+
|
|
125
|
+
cs_step = CSStep(fits_obj, max_cs_time_sec=self.max_cs_step_time_sec)
|
|
126
|
+
self.CS_step_set.add(cs_step)
|
|
127
|
+
return cs_step
|
|
123
128
|
|
|
124
129
|
def getter(self, key) -> str | float | int:
|
|
125
130
|
"""
|
|
@@ -133,11 +138,11 @@ class CSStepFlower(Stem):
|
|
|
133
138
|
-------
|
|
134
139
|
The cs step for the given key
|
|
135
140
|
"""
|
|
136
|
-
unique_steps = sorted(
|
|
141
|
+
unique_steps = sorted(self.CS_step_set)
|
|
137
142
|
return unique_steps.index(self.key_to_petal_dict[key])
|
|
138
143
|
|
|
139
144
|
|
|
140
|
-
class NumCSStepBud(
|
|
145
|
+
class NumCSStepBud(SetStem):
|
|
141
146
|
"""
|
|
142
147
|
The total number of CS Steps present in a dataset.
|
|
143
148
|
|
|
@@ -168,17 +173,12 @@ class NumCSStepBud(Stem):
|
|
|
168
173
|
return SpilledDirt
|
|
169
174
|
return CSStep(fits_obj, max_cs_time_sec=self.max_cs_step_time_sec)
|
|
170
175
|
|
|
171
|
-
def getter(self
|
|
176
|
+
def getter(self) -> int:
|
|
172
177
|
"""
|
|
173
178
|
Return the number of CS Steps present.
|
|
174
179
|
|
|
175
|
-
Parameters
|
|
176
|
-
----------
|
|
177
|
-
key
|
|
178
|
-
The input key
|
|
179
180
|
Returns
|
|
180
181
|
-------
|
|
181
182
|
The number of cs steps associated with the key
|
|
182
183
|
"""
|
|
183
|
-
|
|
184
|
-
return len(value_set)
|
|
184
|
+
return len(self.value_set)
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"""Base classes for ID bud parsing."""
|
|
2
2
|
|
|
3
|
+
from collections import Counter
|
|
3
4
|
from enum import StrEnum
|
|
4
5
|
from typing import Callable
|
|
5
6
|
from typing import Type
|
|
6
7
|
|
|
8
|
+
from dkist_processing_common.models.flower_pot import ListStem
|
|
7
9
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
8
|
-
from dkist_processing_common.models.flower_pot import Stem
|
|
9
10
|
from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
|
|
10
11
|
from dkist_processing_common.parsers.task import passthrough_header_ip_task
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class ContributingIdsBud(
|
|
14
|
+
class ContributingIdsBud(ListStem):
|
|
14
15
|
"""Base class for contributing ID buds."""
|
|
15
16
|
|
|
16
17
|
def __init__(self, constant_name: str, metadata_key: str | StrEnum):
|
|
@@ -33,20 +34,17 @@ class ContributingIdsBud(Stem):
|
|
|
33
34
|
"""
|
|
34
35
|
return getattr(fits_obj, self.metadata_key)
|
|
35
36
|
|
|
36
|
-
def getter(self
|
|
37
|
+
def getter(self) -> tuple[str, ...]:
|
|
37
38
|
"""
|
|
38
|
-
Get all ids seen for any type of frame.
|
|
39
|
-
|
|
40
|
-
Parameters
|
|
41
|
-
----------
|
|
42
|
-
key
|
|
43
|
-
The input key
|
|
39
|
+
Get all ids seen for any type of frame, sorted by the number of appearances of that ID.
|
|
44
40
|
|
|
45
41
|
Returns
|
|
46
42
|
-------
|
|
47
43
|
IDs from all types of frames
|
|
48
44
|
"""
|
|
49
|
-
|
|
45
|
+
counts = Counter(self.value_list) # Count the number of appearances of each ID
|
|
46
|
+
sorted_ids = tuple(str(item) for item, count in counts.most_common())
|
|
47
|
+
return sorted_ids
|
|
50
48
|
|
|
51
49
|
|
|
52
50
|
class TaskContributingIdsBud(ContributingIdsBud):
|
|
@@ -6,14 +6,14 @@ from typing import Any
|
|
|
6
6
|
from typing import Callable
|
|
7
7
|
from typing import DefaultDict
|
|
8
8
|
|
|
9
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
9
10
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
10
|
-
from dkist_processing_common.models.flower_pot import Stem
|
|
11
11
|
from dkist_processing_common.models.tags import EXP_TIME_ROUND_DIGITS
|
|
12
12
|
from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
|
|
13
13
|
from dkist_processing_common.parsers.task import passthrough_header_ip_task
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class TimeLookupBud(
|
|
16
|
+
class TimeLookupBud(SetStem):
|
|
17
17
|
"""
|
|
18
18
|
Bud that reads two header keys from all files and creates a dictionary mapping a time KEY value to sets of a VALUE value.
|
|
19
19
|
|
|
@@ -64,20 +64,16 @@ class TimeLookupBud(Stem):
|
|
|
64
64
|
self.mapping[rounded_key].add(value)
|
|
65
65
|
return None
|
|
66
66
|
|
|
67
|
-
def getter(self
|
|
67
|
+
def getter(self):
|
|
68
68
|
"""
|
|
69
|
-
Get the dictionary mapping created by the setter converted
|
|
69
|
+
Get the dictionary mapping created by the setter with values converted to JSON-able lists.
|
|
70
70
|
|
|
71
|
-
Parameters
|
|
72
|
-
----------
|
|
73
|
-
key
|
|
74
|
-
The input key
|
|
75
71
|
Returns
|
|
76
72
|
-------
|
|
77
|
-
The mapping dictionary converted
|
|
73
|
+
The mapping dictionary with values converted to JSON-able lists
|
|
78
74
|
"""
|
|
79
|
-
|
|
80
|
-
return
|
|
75
|
+
mapping_lists = {k: list(v) for k, v in self.mapping.items()}
|
|
76
|
+
return mapping_lists
|
|
81
77
|
|
|
82
78
|
|
|
83
79
|
class TaskTimeLookupBud(TimeLookupBud):
|