dkist-processing-common 11.6.0rc1__py3-none-any.whl → 11.7.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- changelog/267.feature.1.rst +1 -0
- changelog/267.feature.2.rst +1 -0
- changelog/267.feature.rst +1 -0
- changelog/267.misc.rst +1 -0
- changelog/267.removal.1.rst +2 -0
- changelog/267.removal.rst +1 -0
- dkist_processing_common/models/constants.py +394 -16
- dkist_processing_common/models/fits_access.py +16 -25
- dkist_processing_common/models/telemetry.py +9 -2
- dkist_processing_common/parsers/average_bud.py +48 -0
- dkist_processing_common/parsers/experiment_id_bud.py +8 -4
- dkist_processing_common/parsers/id_bud.py +35 -17
- dkist_processing_common/parsers/l0_fits_access.py +3 -3
- dkist_processing_common/parsers/l1_fits_access.py +47 -21
- dkist_processing_common/parsers/near_bud.py +4 -4
- dkist_processing_common/parsers/proposal_id_bud.py +11 -5
- dkist_processing_common/parsers/single_value_single_key_flower.py +0 -1
- dkist_processing_common/parsers/time.py +141 -27
- dkist_processing_common/tasks/base.py +21 -3
- dkist_processing_common/tasks/parse_l0_input_data.py +289 -36
- dkist_processing_common/tests/test_fits_access.py +19 -44
- dkist_processing_common/tests/test_parse_l0_input_data.py +39 -5
- dkist_processing_common/tests/test_stems.py +127 -10
- dkist_processing_common/tests/test_task_parsing.py +6 -6
- {dkist_processing_common-11.6.0rc1.dist-info → dkist_processing_common-11.7.0rc1.dist-info}/METADATA +3 -3
- {dkist_processing_common-11.6.0rc1.dist-info → dkist_processing_common-11.7.0rc1.dist-info}/RECORD +28 -22
- changelog/268.misc.rst +0 -1
- {dkist_processing_common-11.6.0rc1.dist-info → dkist_processing_common-11.7.0rc1.dist-info}/WHEEL +0 -0
- {dkist_processing_common-11.6.0rc1.dist-info → dkist_processing_common-11.7.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -1,36 +1,27 @@
|
|
|
1
1
|
"""Base classes for ID bud parsing."""
|
|
2
2
|
|
|
3
3
|
from enum import StrEnum
|
|
4
|
+
from typing import Callable
|
|
4
5
|
from typing import Type
|
|
5
6
|
|
|
6
7
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
7
8
|
from dkist_processing_common.models.flower_pot import Stem
|
|
8
9
|
from dkist_processing_common.models.task_name import TaskName
|
|
9
10
|
from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
|
|
11
|
+
from dkist_processing_common.parsers.task import passthrough_header_ip_task
|
|
10
12
|
from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
class IdBud(TaskUniqueBud):
|
|
14
|
-
"""Base class for ID buds."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, constant_name: str, metadata_key: str | StrEnum):
|
|
17
|
-
super().__init__(
|
|
18
|
-
constant_name=constant_name,
|
|
19
|
-
metadata_key=metadata_key,
|
|
20
|
-
ip_task_types=TaskName.observe,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
15
|
class ContributingIdsBud(Stem):
|
|
25
16
|
"""Base class for contributing ID buds."""
|
|
26
17
|
|
|
27
|
-
def __init__(self,
|
|
28
|
-
super().__init__(stem_name=
|
|
18
|
+
def __init__(self, constant_name: str, metadata_key: str | StrEnum):
|
|
19
|
+
super().__init__(stem_name=constant_name)
|
|
29
20
|
if isinstance(metadata_key, StrEnum):
|
|
30
21
|
metadata_key = metadata_key.name
|
|
31
22
|
self.metadata_key = metadata_key
|
|
32
23
|
|
|
33
|
-
def setter(self, fits_obj: L0FitsAccess) -> str
|
|
24
|
+
def setter(self, fits_obj: L0FitsAccess) -> str:
|
|
34
25
|
"""
|
|
35
26
|
Set the id for any type of frame.
|
|
36
27
|
|
|
@@ -44,9 +35,9 @@ class ContributingIdsBud(Stem):
|
|
|
44
35
|
"""
|
|
45
36
|
return getattr(fits_obj, self.metadata_key)
|
|
46
37
|
|
|
47
|
-
def getter(self, key) -> tuple:
|
|
38
|
+
def getter(self, key) -> tuple[str, ...]:
|
|
48
39
|
"""
|
|
49
|
-
Get all ids seen
|
|
40
|
+
Get all ids seen for any type of frame.
|
|
50
41
|
|
|
51
42
|
Parameters
|
|
52
43
|
----------
|
|
@@ -55,6 +46,33 @@ class ContributingIdsBud(Stem):
|
|
|
55
46
|
|
|
56
47
|
Returns
|
|
57
48
|
-------
|
|
58
|
-
IDs from
|
|
49
|
+
IDs from all types of frames
|
|
59
50
|
"""
|
|
60
51
|
return tuple(set(self.key_to_petal_dict.values()))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TaskContributingIdsBud(ContributingIdsBud):
|
|
55
|
+
"""Base class for contributing ID buds for a particular task type."""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
constant_name: str,
|
|
60
|
+
metadata_key: str | StrEnum,
|
|
61
|
+
ip_task_types: str | list[str],
|
|
62
|
+
task_type_parsing_function: Callable = passthrough_header_ip_task,
|
|
63
|
+
):
|
|
64
|
+
super().__init__(constant_name=constant_name, metadata_key=metadata_key)
|
|
65
|
+
|
|
66
|
+
if isinstance(ip_task_types, str):
|
|
67
|
+
ip_task_types = [ip_task_types]
|
|
68
|
+
self.ip_task_types = [task.casefold() for task in ip_task_types]
|
|
69
|
+
self.parsing_function = task_type_parsing_function
|
|
70
|
+
|
|
71
|
+
def setter(self, fits_obj: L0FitsAccess) -> str | Type[SpilledDirt]:
|
|
72
|
+
"""Ingest an object only if its parsed IP task type matches what's desired."""
|
|
73
|
+
task = self.parsing_function(fits_obj)
|
|
74
|
+
|
|
75
|
+
if task.casefold() in self.ip_task_types:
|
|
76
|
+
return super().setter(fits_obj)
|
|
77
|
+
|
|
78
|
+
return SpilledDirt
|
|
@@ -27,6 +27,6 @@ class L0FitsAccess(L1FitsAccess):
|
|
|
27
27
|
auto_squeeze: bool = True,
|
|
28
28
|
):
|
|
29
29
|
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
30
|
-
self.
|
|
31
|
-
self.
|
|
32
|
-
self.
|
|
30
|
+
self.ip_task_type: str = self.header[MetadataKey.ip_task_type]
|
|
31
|
+
self.ip_start_time: str = self.header[MetadataKey.ip_start_time]
|
|
32
|
+
self.ip_end_time: str = self.header[MetadataKey.ip_end_time]
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
from astropy.io import fits
|
|
4
4
|
|
|
5
|
+
from dkist_processing_common.models.fits_access import HEADER_KEY_NOT_FOUND
|
|
5
6
|
from dkist_processing_common.models.fits_access import FitsAccessBase
|
|
6
7
|
from dkist_processing_common.models.fits_access import MetadataKey
|
|
7
8
|
|
|
9
|
+
NOT_A_FLOAT = -999
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
class L1FitsAccess(FitsAccessBase):
|
|
10
13
|
"""
|
|
@@ -28,25 +31,48 @@ class L1FitsAccess(FitsAccessBase):
|
|
|
28
31
|
):
|
|
29
32
|
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
30
33
|
|
|
31
|
-
self.
|
|
32
|
-
self.
|
|
33
|
-
self.
|
|
34
|
-
self.
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
37
|
-
self.
|
|
38
|
-
self.
|
|
39
|
-
self.
|
|
40
|
-
self.
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
self.
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
47
|
-
self.
|
|
48
|
-
self.
|
|
49
|
-
|
|
34
|
+
self.elevation: float = self.header[MetadataKey.elevation]
|
|
35
|
+
self.azimuth: float = self.header[MetadataKey.azimuth]
|
|
36
|
+
self.table_angle: float = self.header[MetadataKey.table_angle]
|
|
37
|
+
self.gos_level3_status: str = self.header[MetadataKey.gos_level3_status]
|
|
38
|
+
self.gos_level3_lamp_status: str = self.header[MetadataKey.gos_level3_lamp_status]
|
|
39
|
+
self.gos_polarizer_status: str = self.header[MetadataKey.gos_polarizer_status]
|
|
40
|
+
self.gos_retarder_status: str = self.header[MetadataKey.gos_retarder_status]
|
|
41
|
+
self.gos_level0_status: str = self.header[MetadataKey.gos_level0_status]
|
|
42
|
+
self.time_obs: str = self.header[MetadataKey.time_obs]
|
|
43
|
+
self.ip_id: str = self.header[MetadataKey.ip_id]
|
|
44
|
+
self.instrument: str = self.header[MetadataKey.instrument]
|
|
45
|
+
self.wavelength: float = self.header[MetadataKey.wavelength]
|
|
46
|
+
self.proposal_id: str = self.header[MetadataKey.proposal_id]
|
|
47
|
+
self.experiment_id: str = self.header[MetadataKey.experiment_id]
|
|
48
|
+
self.num_dsps_repeats: int = self.header[MetadataKey.num_dsps_repeats]
|
|
49
|
+
self.current_dsps_repeat: int = self.header[MetadataKey.current_dsps_repeat]
|
|
50
|
+
self.fpa_exposure_time_ms: float = self.header[MetadataKey.fpa_exposure_time_ms]
|
|
51
|
+
self.sensor_readout_exposure_time_ms: float = self.header[
|
|
52
|
+
MetadataKey.sensor_readout_exposure_time_ms
|
|
53
|
+
]
|
|
54
|
+
self.num_raw_frames_per_fpa: int = self.header[MetadataKey.num_raw_frames_per_fpa]
|
|
55
|
+
self.camera_id: str = self.header[MetadataKey.camera_id]
|
|
56
|
+
self.camera_name: str = self.header[MetadataKey.camera_name]
|
|
57
|
+
self.camera_bit_depth: int = self.header[MetadataKey.camera_bit_depth]
|
|
58
|
+
self.hardware_binning_x: int = self.header[MetadataKey.hardware_binning_x]
|
|
59
|
+
self.hardware_binning_y: int = self.header[MetadataKey.hardware_binning_y]
|
|
60
|
+
self.software_binning_x: int = self.header[MetadataKey.software_binning_x]
|
|
61
|
+
self.software_binning_y: int = self.header[MetadataKey.software_binning_y]
|
|
62
|
+
self.observing_program_execution_id: str = self.header[
|
|
63
|
+
MetadataKey.observing_program_execution_id
|
|
64
|
+
]
|
|
65
|
+
self.telescope_tracking_mode: str = self.header.get(
|
|
66
|
+
MetadataKey.telescope_tracking_mode, HEADER_KEY_NOT_FOUND
|
|
67
|
+
)
|
|
68
|
+
self.coude_table_tracking_mode: str = self.header.get(
|
|
69
|
+
MetadataKey.coude_table_tracking_mode, HEADER_KEY_NOT_FOUND
|
|
70
|
+
)
|
|
71
|
+
self.telescope_scanning_mode: str = self.header.get(
|
|
72
|
+
MetadataKey.telescope_scanning_mode, HEADER_KEY_NOT_FOUND
|
|
73
|
+
)
|
|
74
|
+
self.light_level: float = self.header[MetadataKey.light_level]
|
|
75
|
+
self.hls_version: str = self.header[MetadataKey.hls_version]
|
|
50
76
|
|
|
51
77
|
@property
|
|
52
78
|
def gos_polarizer_angle(self) -> float:
|
|
@@ -54,7 +80,7 @@ class L1FitsAccess(FitsAccessBase):
|
|
|
54
80
|
try:
|
|
55
81
|
return float(self.header[MetadataKey.gos_polarizer_angle])
|
|
56
82
|
except ValueError:
|
|
57
|
-
return
|
|
83
|
+
return NOT_A_FLOAT # The angle is only used if the polarizer is in the beam
|
|
58
84
|
|
|
59
85
|
@property
|
|
60
86
|
def gos_retarder_angle(self) -> float:
|
|
@@ -62,4 +88,4 @@ class L1FitsAccess(FitsAccessBase):
|
|
|
62
88
|
try:
|
|
63
89
|
return float(self.header[MetadataKey.gos_retarder_angle])
|
|
64
90
|
except ValueError:
|
|
65
|
-
return
|
|
91
|
+
return NOT_A_FLOAT # The angle is only used if the retarder is in the beam
|
|
@@ -66,14 +66,14 @@ class NearFloatBud(Stem):
|
|
|
66
66
|
-------
|
|
67
67
|
The mean value associated with this input key
|
|
68
68
|
"""
|
|
69
|
-
|
|
70
|
-
biggest_value = max(
|
|
71
|
-
smallest_value = min(
|
|
69
|
+
value_list = list(self.key_to_petal_dict.values())
|
|
70
|
+
biggest_value = max(value_list)
|
|
71
|
+
smallest_value = min(value_list)
|
|
72
72
|
if biggest_value - smallest_value > self.tolerance:
|
|
73
73
|
raise ValueError(
|
|
74
74
|
f"{self.stem_name} values are not close enough. Max: {biggest_value}, Min: {smallest_value}, Tolerance: {self.tolerance}"
|
|
75
75
|
)
|
|
76
|
-
return mean(
|
|
76
|
+
return mean(value_list)
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
class TaskNearFloatBud(NearFloatBud):
|
|
@@ -2,21 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
from dkist_processing_common.models.constants import BudName
|
|
4
4
|
from dkist_processing_common.models.fits_access import MetadataKey
|
|
5
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
5
6
|
from dkist_processing_common.parsers.id_bud import ContributingIdsBud
|
|
6
|
-
from dkist_processing_common.parsers.
|
|
7
|
+
from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
|
|
7
8
|
|
|
8
9
|
|
|
9
|
-
class ProposalIdBud(
|
|
10
|
+
class ProposalIdBud(TaskUniqueBud):
|
|
10
11
|
"""Class to create a Bud for the proposal_id."""
|
|
11
12
|
|
|
12
13
|
def __init__(self):
|
|
13
|
-
super().__init__(
|
|
14
|
+
super().__init__(
|
|
15
|
+
constant_name=BudName.proposal_id,
|
|
16
|
+
metadata_key=MetadataKey.proposal_id,
|
|
17
|
+
ip_task_types=TaskName.observe,
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class ContributingProposalIdsBud(ContributingIdsBud):
|
|
17
|
-
"""Class to create a Bud for the proposal_ids."""
|
|
22
|
+
"""Class to create a Bud for the supporting proposal_ids."""
|
|
18
23
|
|
|
19
24
|
def __init__(self):
|
|
20
25
|
super().__init__(
|
|
21
|
-
|
|
26
|
+
constant_name=BudName.contributing_proposal_ids,
|
|
27
|
+
metadata_key=MetadataKey.proposal_id,
|
|
22
28
|
)
|
|
@@ -22,7 +22,6 @@ from dkist_processing_common.parsers.single_value_single_key_flower import (
|
|
|
22
22
|
)
|
|
23
23
|
from dkist_processing_common.parsers.task import passthrough_header_ip_task
|
|
24
24
|
from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
|
|
25
|
-
from dkist_processing_common.parsers.unique_bud import UniqueBud
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
class ObsIpStartTimeBud(TaskUniqueBud):
|
|
@@ -36,15 +35,52 @@ class ObsIpStartTimeBud(TaskUniqueBud):
|
|
|
36
35
|
)
|
|
37
36
|
|
|
38
37
|
|
|
39
|
-
class
|
|
40
|
-
"""
|
|
38
|
+
class TaskDatetimeBudBase(Stem):
|
|
39
|
+
"""
|
|
40
|
+
Base class for making datetime-related buds.
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
key_to_petal_dict: dict[str, float]
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
stem_name: str,
|
|
67
|
+
metadata_key: str | StrEnum,
|
|
68
|
+
ip_task_types: str | list[str],
|
|
69
|
+
header_task_parsing_func: Callable = passthrough_header_ip_task,
|
|
70
|
+
):
|
|
71
|
+
super().__init__(stem_name=stem_name)
|
|
72
|
+
|
|
73
|
+
if isinstance(metadata_key, StrEnum):
|
|
74
|
+
metadata_key = metadata_key.name
|
|
75
|
+
self.metadata_key = metadata_key
|
|
76
|
+
if isinstance(ip_task_types, str):
|
|
77
|
+
ip_task_types = [ip_task_types]
|
|
78
|
+
self.ip_task_types = [task.casefold() for task in ip_task_types]
|
|
79
|
+
self.header_parsing_function = header_task_parsing_func
|
|
44
80
|
|
|
45
81
|
def setter(self, fits_obj: L0FitsAccess) -> float | Type[SpilledDirt]:
|
|
46
82
|
"""
|
|
47
|
-
|
|
83
|
+
Store the metadata key datetime value as unix seconds if the task type is in the desired types.
|
|
48
84
|
|
|
49
85
|
Parameters
|
|
50
86
|
----------
|
|
@@ -52,16 +88,45 @@ class CadenceBudBase(UniqueBud):
|
|
|
52
88
|
The input fits object
|
|
53
89
|
Returns
|
|
54
90
|
-------
|
|
55
|
-
The
|
|
91
|
+
The datetime in seconds
|
|
56
92
|
"""
|
|
57
|
-
|
|
93
|
+
task = self.header_parsing_function(fits_obj)
|
|
94
|
+
|
|
95
|
+
if task.casefold() in self.ip_task_types:
|
|
58
96
|
return (
|
|
59
97
|
datetime.fromisoformat(getattr(fits_obj, self.metadata_key))
|
|
60
98
|
.replace(tzinfo=timezone.utc)
|
|
61
99
|
.timestamp()
|
|
62
100
|
)
|
|
101
|
+
|
|
63
102
|
return SpilledDirt
|
|
64
103
|
|
|
104
|
+
def getter(self, key: Hashable) -> tuple[float, ...]:
|
|
105
|
+
"""
|
|
106
|
+
Return a tuple of sorted times in unix seconds.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
key
|
|
111
|
+
The input key
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
A tuple that is sorted times in unix seconds
|
|
116
|
+
"""
|
|
117
|
+
return tuple(sorted(list(self.key_to_petal_dict.values())))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class CadenceBudBase(TaskDatetimeBudBase):
|
|
121
|
+
"""Base class for all Cadence Buds."""
|
|
122
|
+
|
|
123
|
+
def __init__(self, constant_name: str):
|
|
124
|
+
super().__init__(
|
|
125
|
+
stem_name=constant_name,
|
|
126
|
+
metadata_key=MetadataKey.time_obs,
|
|
127
|
+
ip_task_types=TaskName.observe,
|
|
128
|
+
)
|
|
129
|
+
|
|
65
130
|
|
|
66
131
|
class AverageCadenceBud(CadenceBudBase):
|
|
67
132
|
"""Class for the average cadence Bud."""
|
|
@@ -82,7 +147,7 @@ class AverageCadenceBud(CadenceBudBase):
|
|
|
82
147
|
-------
|
|
83
148
|
The mean value of the cadences of the input frames
|
|
84
149
|
"""
|
|
85
|
-
return np.mean(np.diff(
|
|
150
|
+
return np.mean(np.diff(super().getter(key)))
|
|
86
151
|
|
|
87
152
|
|
|
88
153
|
class MaximumCadenceBud(CadenceBudBase):
|
|
@@ -104,7 +169,7 @@ class MaximumCadenceBud(CadenceBudBase):
|
|
|
104
169
|
-------
|
|
105
170
|
The maximum cadence between frames
|
|
106
171
|
"""
|
|
107
|
-
return np.max(np.diff(
|
|
172
|
+
return np.max(np.diff(super().getter(key)))
|
|
108
173
|
|
|
109
174
|
|
|
110
175
|
class MinimumCadenceBud(CadenceBudBase):
|
|
@@ -126,7 +191,7 @@ class MinimumCadenceBud(CadenceBudBase):
|
|
|
126
191
|
-------
|
|
127
192
|
The minimum cadence between frames
|
|
128
193
|
"""
|
|
129
|
-
return np.min(np.diff(
|
|
194
|
+
return np.min(np.diff(super().getter(key)))
|
|
130
195
|
|
|
131
196
|
|
|
132
197
|
class VarianceCadenceBud(CadenceBudBase):
|
|
@@ -147,11 +212,38 @@ class VarianceCadenceBud(CadenceBudBase):
|
|
|
147
212
|
-------
|
|
148
213
|
Return the variance of the cadences over the input frames
|
|
149
214
|
"""
|
|
150
|
-
return np.var(np.diff(
|
|
215
|
+
return np.var(np.diff(super().getter(key)))
|
|
216
|
+
|
|
151
217
|
|
|
218
|
+
class TaskDateBeginBud(TaskDatetimeBudBase):
|
|
219
|
+
"""Class for the date begin task Bud."""
|
|
152
220
|
|
|
153
|
-
|
|
154
|
-
|
|
221
|
+
def __init__(self, constant_name: str, ip_task_types: str | list[str]):
|
|
222
|
+
super().__init__(
|
|
223
|
+
stem_name=constant_name,
|
|
224
|
+
metadata_key=MetadataKey.time_obs,
|
|
225
|
+
ip_task_types=ip_task_types,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def getter(self, key) -> str:
|
|
229
|
+
"""
|
|
230
|
+
Return the earliest date begin for the ip task type converted from unix seconds to datetime string.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
key
|
|
235
|
+
The input key
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
Return the minimum date begin as a datetime string
|
|
239
|
+
"""
|
|
240
|
+
min_time = super().getter(key)[0]
|
|
241
|
+
min_time_dt = datetime.fromtimestamp(min_time, tz=timezone.utc)
|
|
242
|
+
return min_time_dt.strftime("%Y-%m-%dT%H:%M:%S.%f")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class RoundTimeFlowerBase(SingleValueSingleKeyFlower):
|
|
246
|
+
"""Base flower for SingleValueSingleKeyFlowers that need to round their values to avoid value jitter."""
|
|
155
247
|
|
|
156
248
|
def setter(self, fits_obj: L0FitsAccess):
|
|
157
249
|
"""
|
|
@@ -169,7 +261,7 @@ class TimeFlowerBase(SingleValueSingleKeyFlower):
|
|
|
169
261
|
return round(raw_value, EXP_TIME_ROUND_DIGITS)
|
|
170
262
|
|
|
171
263
|
|
|
172
|
-
class ExposureTimeFlower(
|
|
264
|
+
class ExposureTimeFlower(RoundTimeFlowerBase):
|
|
173
265
|
"""For tagging the frame FPA exposure time."""
|
|
174
266
|
|
|
175
267
|
def __init__(self):
|
|
@@ -178,7 +270,7 @@ class ExposureTimeFlower(TimeFlowerBase):
|
|
|
178
270
|
)
|
|
179
271
|
|
|
180
272
|
|
|
181
|
-
class ReadoutExpTimeFlower(
|
|
273
|
+
class ReadoutExpTimeFlower(RoundTimeFlowerBase):
|
|
182
274
|
"""For tagging the exposure time of each readout that contributes to an FPA."""
|
|
183
275
|
|
|
184
276
|
def __init__(self):
|
|
@@ -188,18 +280,18 @@ class ReadoutExpTimeFlower(TimeFlowerBase):
|
|
|
188
280
|
)
|
|
189
281
|
|
|
190
282
|
|
|
191
|
-
class
|
|
283
|
+
class TaskRoundTimeBudBase(Stem):
|
|
192
284
|
"""
|
|
193
|
-
Base class for making
|
|
285
|
+
Base class for making buds that need a set of rounded times for computing for specific task types.
|
|
194
286
|
|
|
195
|
-
|
|
287
|
+
Metadata key values are already floats. Returns tuple of sorted unique rounded values.
|
|
196
288
|
|
|
197
289
|
Complicated parsing of the header into a task type can be achieved by passing in a different
|
|
198
290
|
header task parsing function.
|
|
199
291
|
|
|
200
292
|
Parameters
|
|
201
293
|
----------
|
|
202
|
-
|
|
294
|
+
stem_name
|
|
203
295
|
The name for the constant to be defined
|
|
204
296
|
|
|
205
297
|
metadata_key
|
|
@@ -212,6 +304,8 @@ class TaskTimeBudBase(Stem):
|
|
|
212
304
|
The function used to convert a header into an IP task type
|
|
213
305
|
"""
|
|
214
306
|
|
|
307
|
+
key_to_petal_dict: dict[str, float]
|
|
308
|
+
|
|
215
309
|
def __init__(
|
|
216
310
|
self,
|
|
217
311
|
stem_name: str,
|
|
@@ -229,8 +323,18 @@ class TaskTimeBudBase(Stem):
|
|
|
229
323
|
self.ip_task_types = [task.casefold() for task in ip_task_types]
|
|
230
324
|
self.header_parsing_function = header_task_parsing_func
|
|
231
325
|
|
|
232
|
-
def setter(self, fits_obj: L0FitsAccess):
|
|
233
|
-
"""
|
|
326
|
+
def setter(self, fits_obj: L0FitsAccess) -> float | Type[SpilledDirt]:
|
|
327
|
+
"""
|
|
328
|
+
Store the metadata key value if the parsed task type is in the desired types.
|
|
329
|
+
|
|
330
|
+
Parameters
|
|
331
|
+
----------
|
|
332
|
+
fits_obj
|
|
333
|
+
The input fits object
|
|
334
|
+
Returns
|
|
335
|
+
-------
|
|
336
|
+
The rounded time
|
|
337
|
+
"""
|
|
234
338
|
task = self.header_parsing_function(fits_obj)
|
|
235
339
|
|
|
236
340
|
if task.casefold() in self.ip_task_types:
|
|
@@ -240,12 +344,22 @@ class TaskTimeBudBase(Stem):
|
|
|
240
344
|
return SpilledDirt
|
|
241
345
|
|
|
242
346
|
def getter(self, key: Hashable) -> tuple[float, ...]:
|
|
243
|
-
"""
|
|
244
|
-
|
|
245
|
-
|
|
347
|
+
"""
|
|
348
|
+
Return a tuple of the sorted unique values found.
|
|
349
|
+
|
|
350
|
+
Parameters
|
|
351
|
+
----------
|
|
352
|
+
key
|
|
353
|
+
The input key
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
A tuple that is the sorted set of unique times
|
|
358
|
+
"""
|
|
359
|
+
return tuple(sorted(set(self.key_to_petal_dict.values())))
|
|
246
360
|
|
|
247
361
|
|
|
248
|
-
class TaskExposureTimesBud(
|
|
362
|
+
class TaskExposureTimesBud(TaskRoundTimeBudBase):
|
|
249
363
|
"""Produce a tuple of all FPA exposure times present in the dataset for a specific ip task type."""
|
|
250
364
|
|
|
251
365
|
def __init__(
|
|
@@ -262,7 +376,7 @@ class TaskExposureTimesBud(TaskTimeBudBase):
|
|
|
262
376
|
)
|
|
263
377
|
|
|
264
378
|
|
|
265
|
-
class TaskReadoutExpTimesBud(
|
|
379
|
+
class TaskReadoutExpTimesBud(TaskRoundTimeBudBase):
|
|
266
380
|
"""Produce a tuple of all sensor readout exposure times present in the dataset for a specific task type."""
|
|
267
381
|
|
|
268
382
|
def __init__(
|
|
@@ -10,14 +10,12 @@ from typing import Any
|
|
|
10
10
|
from typing import Generator
|
|
11
11
|
from typing import Iterable
|
|
12
12
|
from typing import Type
|
|
13
|
-
from typing import TypeAlias
|
|
14
13
|
|
|
15
14
|
from dkist_processing_core import TaskBase
|
|
16
15
|
from opentelemetry.metrics import CallbackOptions
|
|
17
16
|
from opentelemetry.metrics import Counter
|
|
18
17
|
from opentelemetry.metrics import ObservableGauge
|
|
19
18
|
from opentelemetry.metrics import Observation
|
|
20
|
-
from pydantic import BaseModel
|
|
21
19
|
|
|
22
20
|
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
23
21
|
from dkist_processing_common._util.tags import TagDB
|
|
@@ -27,13 +25,14 @@ from dkist_processing_common.config import common_configurations
|
|
|
27
25
|
from dkist_processing_common.models.constants import ConstantsBase
|
|
28
26
|
from dkist_processing_common.models.tags import StemName
|
|
29
27
|
from dkist_processing_common.models.tags import Tag
|
|
28
|
+
from dkist_processing_common.models.telemetry import ObservableProgress
|
|
30
29
|
from dkist_processing_common.tasks.mixin.metadata_store import MetadataStoreMixin
|
|
31
30
|
|
|
32
31
|
__all__ = ["WorkflowTaskBase", "tag_type_hint"]
|
|
33
32
|
|
|
34
33
|
logger = logging.getLogger(__name__)
|
|
35
34
|
|
|
36
|
-
tag_type_hint
|
|
35
|
+
tag_type_hint = Iterable[str] | str
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
class WorkflowTaskBase(TaskBase, MetadataStoreMixin, ABC):
|
|
@@ -92,6 +91,20 @@ class WorkflowTaskBase(TaskBase, MetadataStoreMixin, ABC):
|
|
|
92
91
|
unit="1",
|
|
93
92
|
description="The number of writes executed in the processing stack.",
|
|
94
93
|
)
|
|
94
|
+
self.outer_loop_progress = ObservableProgress()
|
|
95
|
+
self.outer_loop_progress_gauge: ObservableGauge = self.meter.create_observable_gauge(
|
|
96
|
+
name=self.format_metric_name("tasks.outer.loop.progress"),
|
|
97
|
+
description="The progress of a task through the main processing loop.",
|
|
98
|
+
callbacks=[lambda options: self.outer_loop_run_progress(options)],
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def outer_loop_run_progress(
|
|
102
|
+
self, options: CallbackOptions
|
|
103
|
+
) -> Generator[Observation, None, None]:
|
|
104
|
+
"""Observe the progress of the current task as a percentage."""
|
|
105
|
+
yield Observation(
|
|
106
|
+
self.outer_loop_progress.percent_complete, attributes=self.base_telemetry_attributes
|
|
107
|
+
)
|
|
95
108
|
|
|
96
109
|
@property
|
|
97
110
|
def constants_model_class(self) -> Type[ConstantsBase]:
|
|
@@ -135,6 +148,11 @@ class WorkflowTaskBase(TaskBase, MetadataStoreMixin, ABC):
|
|
|
135
148
|
with self.telemetry_span("Record Provenance"):
|
|
136
149
|
self._record_provenance()
|
|
137
150
|
|
|
151
|
+
def post_run(self) -> None:
|
|
152
|
+
"""Execute and post-task bookkeeping required."""
|
|
153
|
+
super().post_run()
|
|
154
|
+
self.outer_loop_progress.set_complete()
|
|
155
|
+
|
|
138
156
|
def read(
|
|
139
157
|
self, tags: tag_type_hint, decoder: callable = path_decoder, **decoder_kwargs
|
|
140
158
|
) -> Generator[Any, None, None]:
|