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.
- changelog/280.misc.rst +1 -0
- changelog/282.feature.2.rst +2 -0
- changelog/282.feature.rst +2 -0
- changelog/284.feature.rst +1 -0
- changelog/285.feature.rst +2 -0
- changelog/285.misc.rst +2 -0
- changelog/286.feature.rst +2 -0
- changelog/287.misc.rst +1 -0
- dkist_processing_common/__init__.py +1 -0
- dkist_processing_common/_util/constants.py +1 -0
- dkist_processing_common/_util/graphql.py +1 -0
- dkist_processing_common/_util/scratch.py +9 -9
- dkist_processing_common/_util/tags.py +1 -0
- dkist_processing_common/codecs/array.py +20 -0
- dkist_processing_common/codecs/asdf.py +9 -3
- dkist_processing_common/codecs/basemodel.py +22 -0
- dkist_processing_common/codecs/bytes.py +1 -0
- dkist_processing_common/codecs/fits.py +37 -9
- dkist_processing_common/codecs/iobase.py +1 -0
- dkist_processing_common/codecs/json.py +1 -0
- dkist_processing_common/codecs/path.py +1 -0
- dkist_processing_common/codecs/quality.py +1 -1
- dkist_processing_common/codecs/str.py +1 -0
- dkist_processing_common/config.py +64 -25
- dkist_processing_common/manual.py +6 -8
- dkist_processing_common/models/constants.py +373 -37
- dkist_processing_common/models/dkist_location.py +27 -0
- dkist_processing_common/models/fits_access.py +48 -0
- dkist_processing_common/models/flower_pot.py +231 -9
- dkist_processing_common/models/fried_parameter.py +41 -0
- dkist_processing_common/models/graphql.py +66 -75
- dkist_processing_common/models/input_dataset.py +117 -0
- dkist_processing_common/models/message.py +1 -1
- dkist_processing_common/models/message_queue_binding.py +1 -1
- dkist_processing_common/models/metric_code.py +2 -0
- dkist_processing_common/models/parameters.py +65 -28
- dkist_processing_common/models/quality.py +50 -5
- dkist_processing_common/models/tags.py +23 -21
- dkist_processing_common/models/task_name.py +3 -2
- dkist_processing_common/models/telemetry.py +28 -0
- dkist_processing_common/models/wavelength.py +3 -1
- dkist_processing_common/parsers/average_bud.py +46 -0
- dkist_processing_common/parsers/cs_step.py +13 -12
- dkist_processing_common/parsers/dsps_repeat.py +6 -4
- dkist_processing_common/parsers/experiment_id_bud.py +12 -4
- dkist_processing_common/parsers/id_bud.py +42 -27
- dkist_processing_common/parsers/l0_fits_access.py +5 -3
- dkist_processing_common/parsers/l1_fits_access.py +51 -23
- dkist_processing_common/parsers/lookup_bud.py +125 -0
- dkist_processing_common/parsers/near_bud.py +21 -20
- dkist_processing_common/parsers/observing_program_id_bud.py +24 -0
- dkist_processing_common/parsers/proposal_id_bud.py +13 -5
- dkist_processing_common/parsers/quality.py +2 -0
- dkist_processing_common/parsers/retarder.py +32 -0
- dkist_processing_common/parsers/single_value_single_key_flower.py +6 -1
- dkist_processing_common/parsers/task.py +8 -6
- dkist_processing_common/parsers/time.py +178 -72
- dkist_processing_common/parsers/unique_bud.py +21 -22
- dkist_processing_common/parsers/wavelength.py +5 -3
- dkist_processing_common/tasks/__init__.py +3 -2
- dkist_processing_common/tasks/assemble_movie.py +4 -3
- dkist_processing_common/tasks/base.py +59 -60
- dkist_processing_common/tasks/l1_output_data.py +54 -53
- dkist_processing_common/tasks/mixin/globus.py +24 -27
- dkist_processing_common/tasks/mixin/interservice_bus.py +1 -0
- dkist_processing_common/tasks/mixin/metadata_store.py +108 -243
- dkist_processing_common/tasks/mixin/object_store.py +22 -0
- dkist_processing_common/tasks/mixin/quality/__init__.py +1 -0
- dkist_processing_common/tasks/mixin/quality/_base.py +8 -1
- dkist_processing_common/tasks/mixin/quality/_metrics.py +166 -14
- dkist_processing_common/tasks/output_data_base.py +4 -3
- dkist_processing_common/tasks/parse_l0_input_data.py +277 -15
- dkist_processing_common/tasks/quality_metrics.py +9 -9
- dkist_processing_common/tasks/teardown.py +7 -7
- dkist_processing_common/tasks/transfer_input_data.py +67 -69
- dkist_processing_common/tasks/trial_catalog.py +77 -17
- dkist_processing_common/tasks/trial_output_data.py +16 -17
- dkist_processing_common/tasks/write_l1.py +102 -72
- dkist_processing_common/tests/conftest.py +32 -173
- dkist_processing_common/tests/mock_metadata_store.py +271 -0
- dkist_processing_common/tests/test_assemble_movie.py +4 -4
- dkist_processing_common/tests/test_assemble_quality.py +32 -4
- dkist_processing_common/tests/test_base.py +5 -19
- dkist_processing_common/tests/test_codecs.py +103 -12
- dkist_processing_common/tests/test_constants.py +15 -0
- dkist_processing_common/tests/test_dkist_location.py +15 -0
- dkist_processing_common/tests/test_fits_access.py +56 -19
- dkist_processing_common/tests/test_flower_pot.py +147 -5
- dkist_processing_common/tests/test_fried_parameter.py +27 -0
- dkist_processing_common/tests/test_input_dataset.py +78 -361
- dkist_processing_common/tests/test_interservice_bus.py +1 -0
- dkist_processing_common/tests/test_interservice_bus_mixin.py +1 -1
- dkist_processing_common/tests/test_manual_processing.py +33 -0
- dkist_processing_common/tests/test_output_data_base.py +5 -7
- dkist_processing_common/tests/test_parameters.py +71 -22
- dkist_processing_common/tests/test_parse_l0_input_data.py +115 -32
- dkist_processing_common/tests/test_publish_catalog_messages.py +2 -24
- dkist_processing_common/tests/test_quality.py +1 -0
- dkist_processing_common/tests/test_quality_mixin.py +255 -23
- dkist_processing_common/tests/test_scratch.py +2 -1
- dkist_processing_common/tests/test_stems.py +511 -168
- dkist_processing_common/tests/test_submit_dataset_metadata.py +3 -7
- dkist_processing_common/tests/test_tags.py +1 -0
- dkist_processing_common/tests/test_task_name.py +1 -1
- dkist_processing_common/tests/test_task_parsing.py +17 -7
- dkist_processing_common/tests/test_teardown.py +28 -24
- dkist_processing_common/tests/test_transfer_input_data.py +270 -125
- dkist_processing_common/tests/test_transfer_l1_output_data.py +2 -3
- dkist_processing_common/tests/test_trial_catalog.py +83 -8
- dkist_processing_common/tests/test_trial_output_data.py +46 -73
- dkist_processing_common/tests/test_workflow_task_base.py +8 -10
- dkist_processing_common/tests/test_write_l1.py +298 -76
- dkist_processing_common-12.1.0rc1.dist-info/METADATA +265 -0
- dkist_processing_common-12.1.0rc1.dist-info/RECORD +134 -0
- {dkist_processing_common-10.5.4.dist-info → dkist_processing_common-12.1.0rc1.dist-info}/WHEEL +1 -1
- docs/conf.py +1 -0
- docs/index.rst +1 -1
- docs/landing_page.rst +13 -0
- dkist_processing_common/tasks/mixin/input_dataset.py +0 -166
- dkist_processing_common-10.5.4.dist-info/METADATA +0 -175
- dkist_processing_common-10.5.4.dist-info/RECORD +0 -112
- {dkist_processing_common-10.5.4.dist-info → dkist_processing_common-12.1.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Data structures for messages placed on the interservice bus."""
|
|
2
|
+
|
|
2
3
|
from typing import Type
|
|
3
4
|
|
|
4
5
|
from pydantic import Field
|
|
5
6
|
from talus import MessageBodyBase
|
|
6
7
|
from talus import PublishMessageBase
|
|
7
8
|
|
|
8
|
-
|
|
9
9
|
########################
|
|
10
10
|
# Message Body Schemas #
|
|
11
11
|
########################
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Binding between a queue and a message to be published."""
|
|
2
|
+
|
|
2
3
|
from talus import Binding
|
|
3
4
|
from talus import Queue
|
|
4
5
|
|
|
@@ -7,7 +8,6 @@ from dkist_processing_common.models.message import CatalogFrameMessage
|
|
|
7
8
|
from dkist_processing_common.models.message import CatalogObjectMessage
|
|
8
9
|
from dkist_processing_common.models.message import CreateQualityReportMessage
|
|
9
10
|
|
|
10
|
-
|
|
11
11
|
catalog_frame_queue = Queue(
|
|
12
12
|
name="catalog.frame.q", arguments=common_configurations.isb_queue_arguments
|
|
13
13
|
)
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"""Base class for parameter-parsing object."""
|
|
2
|
+
|
|
2
3
|
import logging
|
|
4
|
+
from contextlib import contextmanager
|
|
3
5
|
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
4
7
|
from typing import Any
|
|
8
|
+
from typing import Callable
|
|
5
9
|
from typing import Literal
|
|
6
10
|
|
|
7
11
|
import numpy as np
|
|
8
12
|
import scipy.interpolate as spi
|
|
9
|
-
from astropy.io import fits
|
|
10
13
|
|
|
11
|
-
from dkist_processing_common.
|
|
14
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
15
|
+
from dkist_processing_common.codecs.array import array_decoder
|
|
16
|
+
from dkist_processing_common.codecs.basemodel import basemodel_decoder
|
|
17
|
+
from dkist_processing_common.codecs.fits import fits_array_decoder
|
|
18
|
+
from dkist_processing_common.models.input_dataset import InputDatasetFilePointer
|
|
19
|
+
from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
|
|
20
|
+
from dkist_processing_common.models.tags import Tag
|
|
12
21
|
|
|
13
22
|
logger = logging.getLogger(__name__)
|
|
14
23
|
|
|
@@ -24,9 +33,9 @@ class ParameterBase:
|
|
|
24
33
|
|
|
25
34
|
To use in an instrument pipeline a subclass is required. Here's a simple, but complete example::
|
|
26
35
|
|
|
27
|
-
class InstParameters(ParameterBase)
|
|
28
|
-
def __init__(self,
|
|
29
|
-
super().__init__(
|
|
36
|
+
class InstParameters(ParameterBase):
|
|
37
|
+
def __init__(self, scratch, some_other_parameters):
|
|
38
|
+
super().__init__(scratch=scratch)
|
|
30
39
|
self._thing = self._some_function(some_other_parameters)
|
|
31
40
|
|
|
32
41
|
@property
|
|
@@ -34,7 +43,7 @@ class ParameterBase:
|
|
|
34
43
|
return self._find_most_recent_past_value("some_parameter_name")
|
|
35
44
|
|
|
36
45
|
@property
|
|
37
|
-
def
|
|
46
|
+
def complicated_parameter(self):
|
|
38
47
|
return self._some_complicated_parsing_function("complicated_parameter_name", another_argument)
|
|
39
48
|
|
|
40
49
|
|
|
@@ -55,15 +64,16 @@ class ParameterBase:
|
|
|
55
64
|
workflow_version=workflow_version,
|
|
56
65
|
)
|
|
57
66
|
|
|
58
|
-
self.parameters = InstParameters(self.
|
|
67
|
+
self.parameters = InstParameters(scratch=self.scratch) #<------ This is the important line
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
ParameterBase needs the task scratch in order to read the parameters document written at input dataset
|
|
70
|
+
transfer. Note that the first argument to the ConstantsSubclass will *always* be scratch, but additional
|
|
71
|
+
arguments can be passed if the subclass requires them.
|
|
62
72
|
|
|
63
73
|
Parameters
|
|
64
74
|
----------
|
|
65
|
-
|
|
66
|
-
The
|
|
75
|
+
scratch
|
|
76
|
+
The task scratch WorkflowFileSystem instance
|
|
67
77
|
|
|
68
78
|
obs_ip_start_time
|
|
69
79
|
A string containing the start date of the Observe IP task type frames. Must be in isoformat.
|
|
@@ -74,25 +84,53 @@ class ParameterBase:
|
|
|
74
84
|
|
|
75
85
|
def __init__(
|
|
76
86
|
self,
|
|
77
|
-
|
|
87
|
+
scratch: WorkflowFileSystem,
|
|
78
88
|
obs_ip_start_time: str | None = None,
|
|
79
89
|
**kwargs,
|
|
80
90
|
):
|
|
91
|
+
self.scratch = scratch
|
|
92
|
+
input_dataset_parameter_model = self._get_parameters_doc_from_file()
|
|
93
|
+
input_dataset_parameters = {}
|
|
94
|
+
if input_dataset_parameter_model is not None:
|
|
95
|
+
input_dataset_parameters = {
|
|
96
|
+
p.parameter_name: p.parameter_values for p in input_dataset_parameter_model.doc_list
|
|
97
|
+
}
|
|
81
98
|
self.input_dataset_parameters = input_dataset_parameters
|
|
99
|
+
|
|
82
100
|
if obs_ip_start_time is not None:
|
|
83
101
|
# Specifically `not None` because we want to error normally on badly formatted strings (including "").
|
|
84
102
|
self._obs_ip_start_datetime = datetime.fromisoformat(obs_ip_start_time)
|
|
85
103
|
else:
|
|
86
104
|
logger.info(
|
|
87
105
|
"WARNING: "
|
|
88
|
-
"The task containing this parameters object did not provide an obs ip start time
|
|
89
|
-
"
|
|
106
|
+
"The task containing this parameters object did not provide an obs ip start time, "
|
|
107
|
+
"which really only makes sense for Parsing tasks."
|
|
90
108
|
)
|
|
91
109
|
|
|
92
110
|
for parent_class in self.__class__.__bases__:
|
|
93
111
|
if hasattr(parent_class, "is_param_mixin"):
|
|
94
112
|
parent_class.__init__(self, **kwargs)
|
|
95
113
|
|
|
114
|
+
def _read_parameter_file(
|
|
115
|
+
self, tag: str, decoder: Callable[[Path], Any], **decoder_kwargs
|
|
116
|
+
) -> Any:
|
|
117
|
+
"""Read any file in the task scratch instance."""
|
|
118
|
+
paths = list(self.scratch.find_all(tags=tag))
|
|
119
|
+
if len(paths) == 0:
|
|
120
|
+
logger.info(f"WARNING: There is no parameter file for {tag = }")
|
|
121
|
+
if len(paths) == 1:
|
|
122
|
+
return decoder(paths[0], **decoder_kwargs)
|
|
123
|
+
if len(paths) > 1:
|
|
124
|
+
raise ValueError(f"There is more than one parameter file for {tag = }: {paths}")
|
|
125
|
+
|
|
126
|
+
def _get_parameters_doc_from_file(self) -> InputDatasetPartDocumentList:
|
|
127
|
+
"""Get parameters doc saved at the TransferL0Data task."""
|
|
128
|
+
tag = Tag.input_dataset_parameters()
|
|
129
|
+
parameters_from_file = self._read_parameter_file(
|
|
130
|
+
tag=tag, decoder=basemodel_decoder, model=InputDatasetPartDocumentList
|
|
131
|
+
)
|
|
132
|
+
return parameters_from_file
|
|
133
|
+
|
|
96
134
|
def _find_most_recent_past_value(
|
|
97
135
|
self,
|
|
98
136
|
parameter_name: str,
|
|
@@ -113,20 +151,19 @@ class ParameterBase:
|
|
|
113
151
|
)
|
|
114
152
|
return result
|
|
115
153
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return result
|
|
154
|
+
def _load_param_value_from_fits(
|
|
155
|
+
self, param_obj: InputDatasetFilePointer, hdu: int = 0
|
|
156
|
+
) -> np.ndarray:
|
|
157
|
+
"""Return the data associated with a tagged parameter file saved in FITS format."""
|
|
158
|
+
tag = param_obj.file_pointer.tag
|
|
159
|
+
param_value = self._read_parameter_file(tag=tag, decoder=fits_array_decoder, hdu=hdu)
|
|
160
|
+
return param_value
|
|
161
|
+
|
|
162
|
+
def _load_param_value_from_numpy_save(self, param_obj: InputDatasetFilePointer) -> np.ndarray:
|
|
163
|
+
"""Return the data associated with a tagged parameter file saved in numpy format."""
|
|
164
|
+
tag = param_obj.file_pointer.tag
|
|
165
|
+
param_value = self._read_parameter_file(tag=tag, decoder=array_decoder)
|
|
166
|
+
return param_value
|
|
130
167
|
|
|
131
168
|
|
|
132
169
|
class _ParamMixinBase:
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"""Support classes used to create a quality report."""
|
|
2
|
+
|
|
2
3
|
from typing import Any
|
|
3
4
|
|
|
4
5
|
from pydantic import BaseModel
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
from pydantic import field_validator
|
|
8
|
+
from pydantic_core.core_schema import ValidationInfo
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
class Plot2D(BaseModel):
|
|
@@ -13,6 +17,50 @@ class Plot2D(BaseModel):
|
|
|
13
17
|
series_name: str | None = None
|
|
14
18
|
ylabel_horizontal: bool = False
|
|
15
19
|
ylim: tuple[float, float] | None = None
|
|
20
|
+
plot_kwargs: dict[str, dict[str, Any]] = Field(default_factory=dict)
|
|
21
|
+
sort_series: bool = True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VerticalMultiPanePlot2D(BaseModel):
|
|
25
|
+
"""
|
|
26
|
+
Support class to hold a multi-pane plot with plots stacked vertically.
|
|
27
|
+
|
|
28
|
+
This type of metric is really geared towards plots that share an X axis and have no gap between them. If you just
|
|
29
|
+
want two separate plots it's probably better to use a list of `Plot2D` objects.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
top_to_bottom_plot_list: list[Plot2D]
|
|
33
|
+
match_x_axes: bool = True
|
|
34
|
+
no_gap: bool = True
|
|
35
|
+
top_to_bottom_height_ratios: list[float] | None = None
|
|
36
|
+
|
|
37
|
+
@field_validator("top_to_bottom_height_ratios")
|
|
38
|
+
@classmethod
|
|
39
|
+
def ensure_same_number_of_height_ratios_and_plots(
|
|
40
|
+
cls, height_ratios: list[float] | None, info: ValidationInfo
|
|
41
|
+
) -> list[float]:
|
|
42
|
+
"""
|
|
43
|
+
Make sure that the number of height ratios is the same as the number of plots.
|
|
44
|
+
|
|
45
|
+
Also populates default, same-size ratios if no ratios were given.
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
plot_list = info.data["top_to_bottom_plot_list"]
|
|
49
|
+
except KeyError:
|
|
50
|
+
# The plot list didn't validate for some reason. We're about to error anyway.
|
|
51
|
+
return [1.0]
|
|
52
|
+
|
|
53
|
+
num_plots = len(plot_list)
|
|
54
|
+
if height_ratios is None:
|
|
55
|
+
return [1.0] * num_plots
|
|
56
|
+
|
|
57
|
+
if len(height_ratios) != num_plots:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"The number of items in `top_to_bottom_height_ratios` list ({len(height_ratios)}) is not "
|
|
60
|
+
f"the same as the number of plots ({num_plots})"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return height_ratios
|
|
16
64
|
|
|
17
65
|
|
|
18
66
|
class SimpleTable(BaseModel):
|
|
@@ -57,11 +105,7 @@ class PlotRaincloud(BaseModel):
|
|
|
57
105
|
|
|
58
106
|
|
|
59
107
|
class ReportMetric(BaseModel):
|
|
60
|
-
"""
|
|
61
|
-
A Quality Report is made up of a list of metrics with the schema defined by this class.
|
|
62
|
-
|
|
63
|
-
Additionally, this class can produce a Flowable or List of Flowables to be render the metric in the PDF Report
|
|
64
|
-
"""
|
|
108
|
+
"""A Quality Report is made up of a list of metrics with the schema defined by this class."""
|
|
65
109
|
|
|
66
110
|
name: str
|
|
67
111
|
description: str
|
|
@@ -69,6 +113,7 @@ class ReportMetric(BaseModel):
|
|
|
69
113
|
facet: str | None = None
|
|
70
114
|
statement: str | list[str] | None = None
|
|
71
115
|
plot_data: Plot2D | list[Plot2D] | None = None
|
|
116
|
+
multi_plot_data: VerticalMultiPanePlot2D | None = None
|
|
72
117
|
histogram_data: PlotHistogram | list[PlotHistogram] | None = None
|
|
73
118
|
table_data: SimpleTable | list[SimpleTable] | None = None
|
|
74
119
|
modmat_data: ModulationMatrixHistograms | None = None
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Components of the Tag model. Stem + Optional Suffix = Tag."""
|
|
2
|
+
|
|
2
3
|
from enum import Enum
|
|
4
|
+
from enum import StrEnum
|
|
3
5
|
|
|
4
6
|
from dkist_processing_common.models.task_name import TaskName
|
|
5
7
|
|
|
@@ -7,7 +9,7 @@ from dkist_processing_common.models.task_name import TaskName
|
|
|
7
9
|
EXP_TIME_ROUND_DIGITS: int = 6
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
class StemName(
|
|
12
|
+
class StemName(StrEnum):
|
|
11
13
|
"""Controlled list of Tag Stems."""
|
|
12
14
|
|
|
13
15
|
output = "OUTPUT"
|
|
@@ -42,7 +44,7 @@ class Tag:
|
|
|
42
44
|
"""Controlled methods for creating tags from stems + optional suffixes."""
|
|
43
45
|
|
|
44
46
|
@staticmethod
|
|
45
|
-
def format_tag(stem: StemName | str, *parts):
|
|
47
|
+
def format_tag(stem: StemName | str, *parts) -> str:
|
|
46
48
|
"""
|
|
47
49
|
Create a formatted tag sting given the input parts.
|
|
48
50
|
|
|
@@ -63,7 +65,7 @@ class Tag:
|
|
|
63
65
|
|
|
64
66
|
# Static Tags
|
|
65
67
|
@classmethod
|
|
66
|
-
def movie_frame(cls):
|
|
68
|
+
def movie_frame(cls) -> str:
|
|
67
69
|
"""
|
|
68
70
|
Return a movie frame tag.
|
|
69
71
|
|
|
@@ -74,7 +76,7 @@ class Tag:
|
|
|
74
76
|
return cls.format_tag(StemName.movie_frame)
|
|
75
77
|
|
|
76
78
|
@classmethod
|
|
77
|
-
def input(cls):
|
|
79
|
+
def input(cls) -> str:
|
|
78
80
|
"""
|
|
79
81
|
Return an input tag.
|
|
80
82
|
|
|
@@ -96,7 +98,7 @@ class Tag:
|
|
|
96
98
|
return cls.format_tag(StemName.calibrated)
|
|
97
99
|
|
|
98
100
|
@classmethod
|
|
99
|
-
def output(cls):
|
|
101
|
+
def output(cls) -> str:
|
|
100
102
|
"""
|
|
101
103
|
Return an output tag.
|
|
102
104
|
|
|
@@ -107,7 +109,7 @@ class Tag:
|
|
|
107
109
|
return cls.format_tag(StemName.output)
|
|
108
110
|
|
|
109
111
|
@classmethod
|
|
110
|
-
def frame(cls):
|
|
112
|
+
def frame(cls) -> str:
|
|
111
113
|
"""
|
|
112
114
|
Return a frame tag.
|
|
113
115
|
|
|
@@ -118,7 +120,7 @@ class Tag:
|
|
|
118
120
|
return cls.format_tag(StemName.frame)
|
|
119
121
|
|
|
120
122
|
@classmethod
|
|
121
|
-
def intermediate(cls):
|
|
123
|
+
def intermediate(cls) -> str:
|
|
122
124
|
"""
|
|
123
125
|
Return an intermediate tag.
|
|
124
126
|
|
|
@@ -129,7 +131,7 @@ class Tag:
|
|
|
129
131
|
return cls.format_tag(StemName.intermediate)
|
|
130
132
|
|
|
131
133
|
@classmethod
|
|
132
|
-
def input_dataset_observe_frames(cls):
|
|
134
|
+
def input_dataset_observe_frames(cls) -> str:
|
|
133
135
|
"""
|
|
134
136
|
Return an input dataset observe frames tag.
|
|
135
137
|
|
|
@@ -140,7 +142,7 @@ class Tag:
|
|
|
140
142
|
return cls.format_tag(StemName.input_dataset, "observe_frames")
|
|
141
143
|
|
|
142
144
|
@classmethod
|
|
143
|
-
def input_dataset_calibration_frames(cls):
|
|
145
|
+
def input_dataset_calibration_frames(cls) -> str:
|
|
144
146
|
"""
|
|
145
147
|
Return an input dataset calibration frames tag.
|
|
146
148
|
|
|
@@ -151,7 +153,7 @@ class Tag:
|
|
|
151
153
|
return cls.format_tag(StemName.input_dataset, "calibration_frames")
|
|
152
154
|
|
|
153
155
|
@classmethod
|
|
154
|
-
def input_dataset_parameters(cls):
|
|
156
|
+
def input_dataset_parameters(cls) -> str:
|
|
155
157
|
"""
|
|
156
158
|
Return an input dataset parameters tag.
|
|
157
159
|
|
|
@@ -162,7 +164,7 @@ class Tag:
|
|
|
162
164
|
return cls.format_tag(StemName.input_dataset, "parameters")
|
|
163
165
|
|
|
164
166
|
@classmethod
|
|
165
|
-
def movie(cls):
|
|
167
|
+
def movie(cls) -> str:
|
|
166
168
|
"""
|
|
167
169
|
Return a movie tag.
|
|
168
170
|
|
|
@@ -230,7 +232,7 @@ class Tag:
|
|
|
230
232
|
|
|
231
233
|
@classmethod
|
|
232
234
|
def task_demodulation_matrices(cls) -> str:
|
|
233
|
-
"""Tags intermediate demodulation
|
|
235
|
+
"""Tags intermediate demodulation matrix calibration objects."""
|
|
234
236
|
return cls.task(TaskName.demodulation_matrices.value)
|
|
235
237
|
|
|
236
238
|
@classmethod
|
|
@@ -254,13 +256,13 @@ class Tag:
|
|
|
254
256
|
return cls.task(TaskName.geometric_offsets.value)
|
|
255
257
|
|
|
256
258
|
@classmethod
|
|
257
|
-
def
|
|
259
|
+
def task_geometric_spectral_shifts(cls) -> str:
|
|
258
260
|
"""Tags intermediate geometric spectral shift calibration objects."""
|
|
259
261
|
return cls.task(TaskName.geometric_spectral_shifts.value)
|
|
260
262
|
|
|
261
263
|
# Dynamic Tags
|
|
262
264
|
@classmethod
|
|
263
|
-
def task(cls, ip_task_type: str):
|
|
265
|
+
def task(cls, ip_task_type: str) -> str:
|
|
264
266
|
"""
|
|
265
267
|
Return a task tag for the given task type.
|
|
266
268
|
|
|
@@ -275,7 +277,7 @@ class Tag:
|
|
|
275
277
|
return cls.format_tag(StemName.task, ip_task_type)
|
|
276
278
|
|
|
277
279
|
@classmethod
|
|
278
|
-
def cs_step(cls, n: int):
|
|
280
|
+
def cs_step(cls, n: int) -> str:
|
|
279
281
|
"""
|
|
280
282
|
Return a cs step tag for the given cs_step number.
|
|
281
283
|
|
|
@@ -291,7 +293,7 @@ class Tag:
|
|
|
291
293
|
return cls.format_tag(StemName.cs_step, n)
|
|
292
294
|
|
|
293
295
|
@classmethod
|
|
294
|
-
def modstate(cls, n: int):
|
|
296
|
+
def modstate(cls, n: int) -> str:
|
|
295
297
|
"""
|
|
296
298
|
Return a modstate tag for the given modstate number.
|
|
297
299
|
|
|
@@ -323,7 +325,7 @@ class Tag:
|
|
|
323
325
|
return cls.format_tag(StemName.stokes, stokes_state)
|
|
324
326
|
|
|
325
327
|
@classmethod
|
|
326
|
-
def dsps_repeat(cls, dsps_repeat_number: int):
|
|
328
|
+
def dsps_repeat(cls, dsps_repeat_number: int) -> str:
|
|
327
329
|
"""
|
|
328
330
|
Return a dsps repeat tag for the given dsps_repeat number.
|
|
329
331
|
|
|
@@ -428,18 +430,18 @@ class Tag:
|
|
|
428
430
|
return cls.format_tag(StemName.workflow_task, class_name)
|
|
429
431
|
|
|
430
432
|
@classmethod
|
|
431
|
-
def dataset_inventory(cls):
|
|
433
|
+
def dataset_inventory(cls) -> str:
|
|
432
434
|
"""
|
|
433
|
-
Return
|
|
435
|
+
Return a dataset_inventory tag.
|
|
434
436
|
|
|
435
437
|
Returns
|
|
436
438
|
-------
|
|
437
|
-
|
|
439
|
+
A dataset_inventory tag
|
|
438
440
|
"""
|
|
439
441
|
return cls.format_tag(StemName.dataset_inventory)
|
|
440
442
|
|
|
441
443
|
@classmethod
|
|
442
|
-
def asdf(cls):
|
|
444
|
+
def asdf(cls) -> str:
|
|
443
445
|
"""
|
|
444
446
|
Return an asdf tag.
|
|
445
447
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Models to support telemetry data."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ObservableProgress(BaseModel, validate_assignment=True):
|
|
7
|
+
"""Container for tracking progress for a metering instrument e.g. task progress."""
|
|
8
|
+
|
|
9
|
+
current: int = 0
|
|
10
|
+
total: int = 0
|
|
11
|
+
|
|
12
|
+
def increment(self, step: int = 1) -> None:
|
|
13
|
+
"""Increment the current progress by the given step."""
|
|
14
|
+
self.current += step
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def percent_complete(self) -> float:
|
|
18
|
+
"""Return the percent complete as a float between 0 and 100."""
|
|
19
|
+
if self.total > 0:
|
|
20
|
+
return (self.current / self.total) * 100
|
|
21
|
+
return 0.0
|
|
22
|
+
|
|
23
|
+
def set_complete(self):
|
|
24
|
+
"""Set the current progress to the total."""
|
|
25
|
+
if self.total == 0:
|
|
26
|
+
self.total = self.current = 1
|
|
27
|
+
else:
|
|
28
|
+
self.current = self.total
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Support classes for manipulating wavelengths."""
|
|
2
|
+
|
|
2
3
|
import astropy.units as u
|
|
3
4
|
from pydantic import BaseModel
|
|
4
5
|
from pydantic import ConfigDict
|
|
6
|
+
from pydantic import ValidationInfo
|
|
5
7
|
from pydantic import field_validator
|
|
6
8
|
from pydantic import model_validator
|
|
7
|
-
from pydantic import ValidationInfo
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class WavelengthRange(BaseModel):
|
|
@@ -26,3 +27,4 @@ class WavelengthRange(BaseModel):
|
|
|
26
27
|
"""Validate that the max wavelength is greater than the min wavelength."""
|
|
27
28
|
if self.min > self.max:
|
|
28
29
|
raise ValueError("min is greater than max. Values may be reversed.")
|
|
30
|
+
return self
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Pre-made flower that reads a single header key from all files with specific task types and returns the average."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from statistics import mean
|
|
5
|
+
from typing import Callable
|
|
6
|
+
from typing import Hashable
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from dkist_processing_common.parsers.near_bud import TaskNearFloatBud
|
|
11
|
+
from dkist_processing_common.parsers.task import passthrough_header_ip_task
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TaskAverageBud(TaskNearFloatBud):
|
|
15
|
+
"""
|
|
16
|
+
Pre-made bud that returns the average of a single header key from all files with specific task types.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
constant_name
|
|
21
|
+
The name for the constant to be defined
|
|
22
|
+
|
|
23
|
+
metadata_key
|
|
24
|
+
The metadata key associated with the constant
|
|
25
|
+
|
|
26
|
+
ip_task_types
|
|
27
|
+
Only consider objects whose parsed header IP task type matches a string in this list
|
|
28
|
+
|
|
29
|
+
task_type_parsing_function
|
|
30
|
+
The function used to convert a header into an IP task type
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
constant_name: str,
|
|
36
|
+
metadata_key: str | StrEnum,
|
|
37
|
+
ip_task_types: str | list[str],
|
|
38
|
+
task_type_parsing_function: Callable = passthrough_header_ip_task,
|
|
39
|
+
):
|
|
40
|
+
super().__init__(
|
|
41
|
+
constant_name=constant_name,
|
|
42
|
+
metadata_key=metadata_key,
|
|
43
|
+
ip_task_types=ip_task_types,
|
|
44
|
+
tolerance=np.inf,
|
|
45
|
+
task_type_parsing_function=task_type_parsing_function,
|
|
46
|
+
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Classes supporting Calibration Sequence steps."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
from datetime import datetime
|
|
@@ -6,6 +7,7 @@ from datetime import timezone
|
|
|
6
7
|
from typing import Type
|
|
7
8
|
|
|
8
9
|
from dkist_processing_common.models.constants import BudName
|
|
10
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
9
11
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
10
12
|
from dkist_processing_common.models.flower_pot import Stem
|
|
11
13
|
from dkist_processing_common.models.tags import StemName
|
|
@@ -100,8 +102,9 @@ class CSStepFlower(Stem):
|
|
|
100
102
|
"""
|
|
101
103
|
|
|
102
104
|
def __init__(self, max_cs_step_time_sec: float):
|
|
103
|
-
super().__init__(stem_name=StemName.cs_step
|
|
105
|
+
super().__init__(stem_name=StemName.cs_step)
|
|
104
106
|
self.max_cs_step_time_sec = max_cs_step_time_sec
|
|
107
|
+
self.CS_step_set = set()
|
|
105
108
|
|
|
106
109
|
def setter(self, fits_obj: L0FitsAccess) -> CSStep | Type[SpilledDirt]:
|
|
107
110
|
"""
|
|
@@ -118,7 +121,10 @@ class CSStepFlower(Stem):
|
|
|
118
121
|
"""
|
|
119
122
|
if fits_obj.ip_task_type != "polcal":
|
|
120
123
|
return SpilledDirt
|
|
121
|
-
|
|
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
|
|
122
128
|
|
|
123
129
|
def getter(self, key) -> str | float | int:
|
|
124
130
|
"""
|
|
@@ -132,11 +138,11 @@ class CSStepFlower(Stem):
|
|
|
132
138
|
-------
|
|
133
139
|
The cs step for the given key
|
|
134
140
|
"""
|
|
135
|
-
unique_steps = sorted(
|
|
141
|
+
unique_steps = sorted(self.CS_step_set)
|
|
136
142
|
return unique_steps.index(self.key_to_petal_dict[key])
|
|
137
143
|
|
|
138
144
|
|
|
139
|
-
class NumCSStepBud(
|
|
145
|
+
class NumCSStepBud(SetStem):
|
|
140
146
|
"""
|
|
141
147
|
The total number of CS Steps present in a dataset.
|
|
142
148
|
|
|
@@ -147,7 +153,7 @@ class NumCSStepBud(Stem):
|
|
|
147
153
|
"""
|
|
148
154
|
|
|
149
155
|
def __init__(self, max_cs_step_time_sec: float):
|
|
150
|
-
super().__init__(stem_name=BudName.num_cs_steps
|
|
156
|
+
super().__init__(stem_name=BudName.num_cs_steps)
|
|
151
157
|
self.max_cs_step_time_sec = max_cs_step_time_sec
|
|
152
158
|
|
|
153
159
|
def setter(self, fits_obj: L0FitsAccess) -> CSStep | Type[SpilledDirt]:
|
|
@@ -167,17 +173,12 @@ class NumCSStepBud(Stem):
|
|
|
167
173
|
return SpilledDirt
|
|
168
174
|
return CSStep(fits_obj, max_cs_time_sec=self.max_cs_step_time_sec)
|
|
169
175
|
|
|
170
|
-
def getter(self
|
|
176
|
+
def getter(self) -> int:
|
|
171
177
|
"""
|
|
172
178
|
Return the number of CS Steps present.
|
|
173
179
|
|
|
174
|
-
Parameters
|
|
175
|
-
----------
|
|
176
|
-
key
|
|
177
|
-
The input key
|
|
178
180
|
Returns
|
|
179
181
|
-------
|
|
180
182
|
The number of cs steps associated with the key
|
|
181
183
|
"""
|
|
182
|
-
|
|
183
|
-
return len(value_set)
|
|
184
|
+
return len(self.value_set)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Classes supporting the Data Set Parameters Set (DSPS) Repeat parameter."""
|
|
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.flower_pot import SpilledDirt
|
|
4
6
|
from dkist_processing_common.models.tags import StemName
|
|
5
7
|
from dkist_processing_common.models.task_name import TaskName
|
|
@@ -15,9 +17,9 @@ class TotalDspsRepeatsBud(TaskUniqueBud):
|
|
|
15
17
|
|
|
16
18
|
def __init__(self):
|
|
17
19
|
super().__init__(
|
|
18
|
-
constant_name=BudName.num_dsps_repeats
|
|
19
|
-
metadata_key=
|
|
20
|
-
|
|
20
|
+
constant_name=BudName.num_dsps_repeats,
|
|
21
|
+
metadata_key=MetadataKey.num_dsps_repeats,
|
|
22
|
+
ip_task_types=TaskName.observe,
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
|
|
@@ -26,7 +28,7 @@ class DspsRepeatNumberFlower(SingleValueSingleKeyFlower):
|
|
|
26
28
|
|
|
27
29
|
def __init__(self):
|
|
28
30
|
super().__init__(
|
|
29
|
-
tag_stem_name=StemName.dsps_repeat
|
|
31
|
+
tag_stem_name=StemName.dsps_repeat, metadata_key=MetadataKey.current_dsps_repeat
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
def setter(self, fits_obj: L0FitsAccess):
|