dkist-processing-trend 0.1.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.
- changelog/.gitempty +0 -0
- dkist_processing_trend/__init__.py +10 -0
- dkist_processing_trend/config.py +11 -0
- dkist_processing_trend/models/__init__.py +1 -0
- dkist_processing_trend/models/constants.py +143 -0
- dkist_processing_trend/models/fit_options.py +15 -0
- dkist_processing_trend/models/fits_access.py +65 -0
- dkist_processing_trend/models/instrument.py +35 -0
- dkist_processing_trend/models/instrument_options.py +35 -0
- dkist_processing_trend/models/parameters.py +134 -0
- dkist_processing_trend/models/tags.py +117 -0
- dkist_processing_trend/models/task_name.py +20 -0
- dkist_processing_trend/parsers/__init__.py +1 -0
- dkist_processing_trend/parsers/arm_id.py +103 -0
- dkist_processing_trend/parsers/instrument_unique_bud.py +40 -0
- dkist_processing_trend/parsers/time.py +27 -0
- dkist_processing_trend/parsers/trend_l0_fits_access.py +123 -0
- dkist_processing_trend/tasks/__init__.py +27 -0
- dkist_processing_trend/tasks/arm_task_factory.py +57 -0
- dkist_processing_trend/tasks/dark.py +56 -0
- dkist_processing_trend/tasks/gain.py +74 -0
- dkist_processing_trend/tasks/initialize_arm_tasks.py +50 -0
- dkist_processing_trend/tasks/parse.py +169 -0
- dkist_processing_trend/tasks/prepare_fit_data_base.py +257 -0
- dkist_processing_trend/tasks/run_pac_fitter.py +392 -0
- dkist_processing_trend/tasks/trend_base.py +97 -0
- dkist_processing_trend/tasks/trend_output_data.py +167 -0
- dkist_processing_trend/tasks/visp/__init__.py +6 -0
- dkist_processing_trend/tasks/visp/visp_dmpd.py +410 -0
- dkist_processing_trend/tasks/visp/visp_extract_beam.py +14 -0
- dkist_processing_trend/tasks/visp/visp_geometric.py +260 -0
- dkist_processing_trend/tasks/visp/visp_prep_fit_data.py +162 -0
- dkist_processing_trend/tasks/visp/visp_process_demod.py +236 -0
- dkist_processing_trend/tasks/write_trend.py +663 -0
- dkist_processing_trend/tests/__init__.py +1 -0
- dkist_processing_trend/tests/conftest.py +718 -0
- dkist_processing_trend/tests/local_trial_workflows/__init__.py +0 -0
- dkist_processing_trend/tests/local_trial_workflows/l0_to_trend_visp_polcal.py +294 -0
- dkist_processing_trend/tests/local_trial_workflows/local_trial_helpers.py +488 -0
- dkist_processing_trend/tests/test_arm_task_factory.py +82 -0
- dkist_processing_trend/tests/test_base_tasks.py +86 -0
- dkist_processing_trend/tests/test_constants.py +120 -0
- dkist_processing_trend/tests/test_dark.py +97 -0
- dkist_processing_trend/tests/test_gain.py +135 -0
- dkist_processing_trend/tests/test_parameters.py +149 -0
- dkist_processing_trend/tests/test_parse.py +276 -0
- dkist_processing_trend/tests/test_prep_fit_data_base.py +233 -0
- dkist_processing_trend/tests/test_publish_catalog_messages.py +45 -0
- dkist_processing_trend/tests/test_run_pac_fitter.py +371 -0
- dkist_processing_trend/tests/test_stems.py +75 -0
- dkist_processing_trend/tests/test_transfer_output_data.py +76 -0
- dkist_processing_trend/tests/test_trend_fits_access.py +173 -0
- dkist_processing_trend/tests/test_visp.py +874 -0
- dkist_processing_trend/tests/test_workflows.py +10 -0
- dkist_processing_trend/tests/test_write_trend.py +460 -0
- dkist_processing_trend/workflows/__init__.py +3 -0
- dkist_processing_trend/workflows/visp.py +58 -0
- dkist_processing_trend-0.1.0.dist-info/METADATA +549 -0
- dkist_processing_trend-0.1.0.dist-info/RECORD +66 -0
- dkist_processing_trend-0.1.0.dist-info/WHEEL +5 -0
- dkist_processing_trend-0.1.0.dist-info/top_level.txt +3 -0
- docs/conf.py +57 -0
- docs/index.rst +10 -0
- docs/l0_to_trend_visp_polcal.rst +4 -0
- docs/landing_page.rst +11 -0
- docs/requirements_table.rst +8 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
from dataclasses import asdict
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from random import randint
|
|
7
|
+
|
|
8
|
+
import asdf
|
|
9
|
+
from astropy.io import fits
|
|
10
|
+
from dkist_header_validator import spec122_validator
|
|
11
|
+
from dkist_processing_common.codecs.basemodel import basemodel_encoder
|
|
12
|
+
from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
|
|
13
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
14
|
+
from dkist_processing_common.tasks.base import WorkflowTaskBase
|
|
15
|
+
from dkist_service_configuration.logging import logger
|
|
16
|
+
|
|
17
|
+
from dkist_processing_trend.models.constants import TrendBudName
|
|
18
|
+
from dkist_processing_trend.models.tags import TrendTag
|
|
19
|
+
from dkist_processing_trend.models.task_name import TrendTaskName
|
|
20
|
+
from dkist_processing_trend.tests.conftest import TrendInputDatasetParameterValues
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def translate_task(instrument: str, suffix: str):
|
|
24
|
+
class Translate122To214L0(WorkflowTaskBase):
|
|
25
|
+
def run(self) -> None:
|
|
26
|
+
raw_dir = (
|
|
27
|
+
Path(self.scratch.scratch_base_path)
|
|
28
|
+
/ f"{instrument.upper()}{self.recipe_run_id:03n}"
|
|
29
|
+
)
|
|
30
|
+
if not self.scratch.workflow_base_path.exists():
|
|
31
|
+
self.scratch.workflow_base_path.mkdir(parents=True, exist_ok=False)
|
|
32
|
+
|
|
33
|
+
if not raw_dir.exists():
|
|
34
|
+
raise FileNotFoundError(
|
|
35
|
+
f"Expected to find a raw {instrument.upper()}{self.recipe_run_id:03n} folder in {self.scratch.scratch_base_path}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
for file in raw_dir.glob(f"*.{suffix}"):
|
|
39
|
+
translated_file_name = self.scratch.workflow_base_path / os.path.basename(file)
|
|
40
|
+
logger.info(f"Translating {file} -> {translated_file_name}")
|
|
41
|
+
with fits.open(file, memmap=False) as hdl:
|
|
42
|
+
# Handle both compressed and uncompressed files...
|
|
43
|
+
hdu = hdl[1] if len(hdl) > 1 else hdl[0]
|
|
44
|
+
header = spec122_validator.validate_and_translate_to_214_l0(
|
|
45
|
+
hdu.header, return_type=fits.HDUList
|
|
46
|
+
)[0].header
|
|
47
|
+
|
|
48
|
+
comp_hdu = fits.CompImageHDU(header=header, data=hdu.data)
|
|
49
|
+
comp_hdl = fits.HDUList([fits.PrimaryHDU(), comp_hdu])
|
|
50
|
+
comp_hdl.writeto(translated_file_name, overwrite=True)
|
|
51
|
+
|
|
52
|
+
comp_hdl.close()
|
|
53
|
+
del comp_hdl
|
|
54
|
+
|
|
55
|
+
return Translate122To214L0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def tag_inputs_task(suffix: str):
|
|
59
|
+
class TagInputs(WorkflowTaskBase):
|
|
60
|
+
def run(self) -> None:
|
|
61
|
+
logger.info(f"Looking in {self.scratch.workflow_base_path.absolute()}")
|
|
62
|
+
input_file_list = list(self.scratch.workflow_base_path.glob(f"*.{suffix}"))
|
|
63
|
+
if len(input_file_list) == 0:
|
|
64
|
+
raise FileNotFoundError(
|
|
65
|
+
f"Did not find any files matching '*.{suffix}' in {self.scratch.workflow_base_path}"
|
|
66
|
+
)
|
|
67
|
+
for file in input_file_list:
|
|
68
|
+
logger.info(f"Found {file}")
|
|
69
|
+
self.tag(path=file, tags=[TrendTag.input(), TrendTag.frame()])
|
|
70
|
+
|
|
71
|
+
return TagInputs
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def input_dataset_parameter_task(parameter_data: TrendInputDatasetParameterValues):
|
|
75
|
+
|
|
76
|
+
class CreateInputDatasetParameterDocument(WorkflowTaskBase):
|
|
77
|
+
def run(self) -> None:
|
|
78
|
+
relative_path = "input_dataset_parameters.json"
|
|
79
|
+
self.write(
|
|
80
|
+
data=InputDatasetPartDocumentList(
|
|
81
|
+
doc_list=self.input_dataset_document_simple_parameters_part
|
|
82
|
+
),
|
|
83
|
+
relative_path=relative_path,
|
|
84
|
+
tags=TrendTag.input_dataset_parameters(),
|
|
85
|
+
encoder=basemodel_encoder,
|
|
86
|
+
overwrite=True,
|
|
87
|
+
)
|
|
88
|
+
logger.info(f"Wrote input dataset parameter doc to {relative_path}")
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def input_dataset_document_simple_parameters_part(self):
|
|
92
|
+
parameters_list = []
|
|
93
|
+
value_id = randint(1000, 2000)
|
|
94
|
+
for pn, pv in asdict(parameter_data).items():
|
|
95
|
+
values = [
|
|
96
|
+
{
|
|
97
|
+
"parameterValueId": value_id,
|
|
98
|
+
"parameterValue": json.dumps(pv),
|
|
99
|
+
"parameterValueStartDate": "1946-11-20",
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
parameter = {"parameterName": pn, "parameterValues": values}
|
|
103
|
+
parameters_list.append(parameter)
|
|
104
|
+
|
|
105
|
+
return parameters_list
|
|
106
|
+
|
|
107
|
+
return CreateInputDatasetParameterDocument
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def save_parsing_task(
|
|
111
|
+
tag_list: list[str], save_file: str, save_file_tags: bool = True, save_constants: bool = True
|
|
112
|
+
):
|
|
113
|
+
class SaveParsing(WorkflowTaskBase):
|
|
114
|
+
"""Save the result of parsing (constants and tags) to an asdf file."""
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def relative_save_file(self) -> str:
|
|
118
|
+
return save_file
|
|
119
|
+
|
|
120
|
+
def run(self):
|
|
121
|
+
if save_file_tags:
|
|
122
|
+
file_tag_dict = self.get_input_tags()
|
|
123
|
+
else:
|
|
124
|
+
logger.info("Skipping saving of file tags")
|
|
125
|
+
file_tag_dict = dict()
|
|
126
|
+
if save_constants:
|
|
127
|
+
constant_dict = self.get_constants()
|
|
128
|
+
else:
|
|
129
|
+
logger.info("Skipping saving of constants")
|
|
130
|
+
constant_dict = dict()
|
|
131
|
+
|
|
132
|
+
full_save_file = self.scratch.workflow_base_path / self.relative_save_file
|
|
133
|
+
tree = {"file_tag_dict": file_tag_dict, "constants_dict": constant_dict}
|
|
134
|
+
af = asdf.AsdfFile(tree)
|
|
135
|
+
af.write_to(full_save_file)
|
|
136
|
+
logger.info(f"Saved input tags to {full_save_file}")
|
|
137
|
+
|
|
138
|
+
def get_input_tags(self) -> dict[str, list[str]]:
|
|
139
|
+
file_tag_dict = dict()
|
|
140
|
+
path_list = self.read(tags=tag_list)
|
|
141
|
+
for p in path_list:
|
|
142
|
+
tags = self.tags(p)
|
|
143
|
+
file_tag_dict[str(p)] = tags
|
|
144
|
+
|
|
145
|
+
return file_tag_dict
|
|
146
|
+
|
|
147
|
+
def get_constants(self) -> dict[str, str | float | list]:
|
|
148
|
+
constants_dict = dict()
|
|
149
|
+
for c in self.constants._db_dict.keys():
|
|
150
|
+
constants_dict[c] = self.constants._db_dict[c]
|
|
151
|
+
|
|
152
|
+
return constants_dict
|
|
153
|
+
|
|
154
|
+
return SaveParsing
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def load_parsing_task(save_file: str):
|
|
158
|
+
class LoadParsing(WorkflowTaskBase):
|
|
159
|
+
"""Load tags and constants into the database."""
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def relative_save_file(self) -> str:
|
|
163
|
+
return save_file
|
|
164
|
+
|
|
165
|
+
def run(self):
|
|
166
|
+
full_save_file = self.scratch.workflow_base_path / self.relative_save_file
|
|
167
|
+
with asdf.open(full_save_file) as af:
|
|
168
|
+
file_tag_dict = af.tree["file_tag_dict"]
|
|
169
|
+
self.tag_input_files(file_tag_dict)
|
|
170
|
+
|
|
171
|
+
constants_dict = af.tree["constants_dict"]
|
|
172
|
+
self.populate_constants(constants_dict)
|
|
173
|
+
|
|
174
|
+
logger.info(f"Loaded tags and constants from {full_save_file}")
|
|
175
|
+
|
|
176
|
+
def tag_input_files(self, file_tag_dict: dict[str, list[str]]):
|
|
177
|
+
for f, t in file_tag_dict.items():
|
|
178
|
+
if not os.path.exists(f):
|
|
179
|
+
pass
|
|
180
|
+
# raise FileNotFoundError(f"Expected to find {f}, but it doesn't exist.")
|
|
181
|
+
else:
|
|
182
|
+
self.tag(path=f, tags=t)
|
|
183
|
+
|
|
184
|
+
def populate_constants(self, constants_dict: dict[str, str | int | float]) -> None:
|
|
185
|
+
# First we purge all constants because a previous load might have polluted the DB
|
|
186
|
+
self.constants._purge()
|
|
187
|
+
for c, v in constants_dict.items():
|
|
188
|
+
logger.info(f"Setting value of {c} to {v}")
|
|
189
|
+
self.constants._update({c: v})
|
|
190
|
+
|
|
191
|
+
return LoadParsing
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_arm_id_list(workflow_base_path: Path, parsing_file_name: str | Path) -> list[str | int]:
|
|
195
|
+
with asdf.open(workflow_base_path / parsing_file_name) as af:
|
|
196
|
+
constants = af.tree["constants_dict"]
|
|
197
|
+
arm_id_list = constants[TrendBudName.arm_id_list]
|
|
198
|
+
|
|
199
|
+
return arm_id_list
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class SaveTaskTags(WorkflowTaskBase):
|
|
203
|
+
"""Base task for saving all INTERMEDIATE files with the same 'TASK'"""
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def task_str(self) -> str:
|
|
207
|
+
return "TASK"
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def arm_id(self) -> str | int | None:
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def relative_save_file(self) -> str:
|
|
215
|
+
return "default_sav.asdf"
|
|
216
|
+
|
|
217
|
+
@property
|
|
218
|
+
def tag_lists_to_save(self) -> list[list[str]] | list[str]:
|
|
219
|
+
intermediate_tags = [TrendTag.task(self.task_str), TrendTag.intermediate()]
|
|
220
|
+
if self.arm_id is not None:
|
|
221
|
+
intermediate_tags.append(TrendTag.arm_id(self.arm_id))
|
|
222
|
+
return [intermediate_tags]
|
|
223
|
+
|
|
224
|
+
def run(self):
|
|
225
|
+
file_tag_dict = dict()
|
|
226
|
+
tag_list_list = self.tag_lists_to_save
|
|
227
|
+
if isinstance(tag_list_list[0], str):
|
|
228
|
+
tag_list_list = [tag_list_list]
|
|
229
|
+
|
|
230
|
+
for tags_to_save in tag_list_list:
|
|
231
|
+
path_list = self.read(tags=tags_to_save)
|
|
232
|
+
save_dir = self.scratch.workflow_base_path / Path(self.relative_save_file).stem
|
|
233
|
+
save_dir.mkdir(exist_ok=True)
|
|
234
|
+
for p in path_list:
|
|
235
|
+
copied_path = shutil.copy(str(p), save_dir)
|
|
236
|
+
tags = self.tags(p)
|
|
237
|
+
file_tag_dict[copied_path] = tags
|
|
238
|
+
|
|
239
|
+
full_save_file = self.scratch.workflow_base_path / self.relative_save_file
|
|
240
|
+
tree = {"file_tag_dict": file_tag_dict}
|
|
241
|
+
af = asdf.AsdfFile(tree)
|
|
242
|
+
af.write_to(full_save_file)
|
|
243
|
+
logger.info(f"Saved {self.task_str} to {full_save_file}")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class LoadTaskTags(WorkflowTaskBase):
|
|
247
|
+
"""Base task for loading file/tag associations from a previously saved set."""
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def relative_save_file(self) -> str:
|
|
251
|
+
return "default_sav.asdf"
|
|
252
|
+
|
|
253
|
+
def run(self):
|
|
254
|
+
full_save_file = self.scratch.workflow_base_path / self.relative_save_file
|
|
255
|
+
with asdf.open(full_save_file) as af:
|
|
256
|
+
for f, t in af.tree["file_tag_dict"].items():
|
|
257
|
+
self.tag(path=f, tags=t)
|
|
258
|
+
logger.info(f"Loaded database entries from {full_save_file}")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def save_dark_cal_task(arm_id: str | int):
|
|
262
|
+
class SaveDarkCal(SaveTaskTags):
|
|
263
|
+
@property
|
|
264
|
+
def task_str(self) -> str:
|
|
265
|
+
return TaskName.dark.value
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def arm_id(self):
|
|
269
|
+
return arm_id
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def relative_save_file(self) -> str:
|
|
273
|
+
return f"dark_cal_{arm_id}.asdf"
|
|
274
|
+
|
|
275
|
+
return SaveDarkCal
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def load_dark_cal_task(arm_id: str | int):
|
|
279
|
+
class LoadDarkCal(LoadTaskTags):
|
|
280
|
+
@property
|
|
281
|
+
def relative_save_file(self) -> str:
|
|
282
|
+
return f"dark_cal_{arm_id}.asdf"
|
|
283
|
+
|
|
284
|
+
return LoadDarkCal
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def save_gain_cal_task(arm_id: str | int):
|
|
288
|
+
class SaveGainCal(SaveTaskTags):
|
|
289
|
+
@property
|
|
290
|
+
def task_str(self) -> str:
|
|
291
|
+
return TaskName.gain.value
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def arm_id(self):
|
|
295
|
+
return arm_id
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def relative_save_file(self) -> str:
|
|
299
|
+
return f"gain_cal_{arm_id}.asdf"
|
|
300
|
+
|
|
301
|
+
return SaveGainCal
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def load_gain_cal_task(arm_id: str | int):
|
|
305
|
+
class LoadGainCal(LoadTaskTags):
|
|
306
|
+
@property
|
|
307
|
+
def relative_save_file(self) -> str:
|
|
308
|
+
return f"gain_cal_{arm_id}.asdf"
|
|
309
|
+
|
|
310
|
+
return LoadGainCal
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def save_prepped_cal_task(arm_id: str | int):
|
|
314
|
+
class SavePreppedData(SaveTaskTags):
|
|
315
|
+
@property
|
|
316
|
+
def tag_lists_to_save(self):
|
|
317
|
+
return [
|
|
318
|
+
[
|
|
319
|
+
TrendTag.intermediate(),
|
|
320
|
+
TrendTag.arm_id(self.arm_id),
|
|
321
|
+
TrendTag.task_local_pac_input(),
|
|
322
|
+
],
|
|
323
|
+
[
|
|
324
|
+
TrendTag.intermediate(),
|
|
325
|
+
TrendTag.arm_id(self.arm_id),
|
|
326
|
+
TrendTag.task_global_pac_input(),
|
|
327
|
+
],
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def arm_id(self):
|
|
332
|
+
return arm_id
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def relative_save_file(self) -> str:
|
|
336
|
+
return f"prepped_cal_{arm_id}.asdf"
|
|
337
|
+
|
|
338
|
+
return SavePreppedData
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def load_prepped_cal_task(arm_id: str | int):
|
|
342
|
+
class LoadPreppedData(LoadTaskTags):
|
|
343
|
+
@property
|
|
344
|
+
def relative_save_file(self) -> str:
|
|
345
|
+
return f"prepped_cal_{arm_id}.asdf"
|
|
346
|
+
|
|
347
|
+
return LoadPreppedData
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def save_pac_fit_task(arm_id: str | int):
|
|
351
|
+
class SavePacFit(SaveTaskTags):
|
|
352
|
+
@property
|
|
353
|
+
def tag_lists_to_save(self):
|
|
354
|
+
return [
|
|
355
|
+
[
|
|
356
|
+
TrendTag.intermediate(),
|
|
357
|
+
TrendTag.arm_id(self.arm_id),
|
|
358
|
+
TrendTag.task_calibration_sequence(),
|
|
359
|
+
],
|
|
360
|
+
[
|
|
361
|
+
TrendTag.intermediate(),
|
|
362
|
+
TrendTag.arm_id(self.arm_id),
|
|
363
|
+
TrendTag.task_best_fit_parameters(),
|
|
364
|
+
],
|
|
365
|
+
[
|
|
366
|
+
TrendTag.intermediate(),
|
|
367
|
+
TrendTag.arm_id(self.arm_id),
|
|
368
|
+
TrendTag.task_best_fit_demodulation_matrices(),
|
|
369
|
+
],
|
|
370
|
+
[
|
|
371
|
+
TrendTag.intermediate(),
|
|
372
|
+
TrendTag.arm_id(self.arm_id),
|
|
373
|
+
TrendTag.task_best_fit_flux(),
|
|
374
|
+
],
|
|
375
|
+
[
|
|
376
|
+
TrendTag.intermediate(),
|
|
377
|
+
TrendTag.arm_id(self.arm_id),
|
|
378
|
+
TrendTag.task_fit_residuals(),
|
|
379
|
+
],
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def arm_id(self):
|
|
384
|
+
return arm_id
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def relative_save_file(self) -> str:
|
|
388
|
+
return f"pac_fit_{arm_id}.asdf"
|
|
389
|
+
|
|
390
|
+
return SavePacFit
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def load_pac_fit_task(arm_id: str | int):
|
|
394
|
+
class LoadPacFit(LoadTaskTags):
|
|
395
|
+
@property
|
|
396
|
+
def relative_save_file(self) -> str:
|
|
397
|
+
return f"pac_fit_{arm_id}.asdf"
|
|
398
|
+
|
|
399
|
+
return LoadPacFit
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def save_processed_demod_cal_task(arm_id: str | int):
|
|
403
|
+
class SaveProcessedDemodCal(SaveTaskTags):
|
|
404
|
+
@property
|
|
405
|
+
def task_str(self) -> str:
|
|
406
|
+
return TrendTaskName.processed_demodulation_matrices.value
|
|
407
|
+
|
|
408
|
+
@property
|
|
409
|
+
def arm_id(self):
|
|
410
|
+
return arm_id
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def relative_save_file(self) -> str:
|
|
414
|
+
return f"processed_demod_cal_{arm_id}.asdf"
|
|
415
|
+
|
|
416
|
+
return SaveProcessedDemodCal
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def load_processed_demod_cal_task(arm_id: str | int):
|
|
420
|
+
class LoadGainCal(LoadTaskTags):
|
|
421
|
+
@property
|
|
422
|
+
def relative_save_file(self) -> str:
|
|
423
|
+
return f"processed_demod_cal_{arm_id}.asdf"
|
|
424
|
+
|
|
425
|
+
return LoadGainCal
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def save_dmpd_task(arm_id: str | int):
|
|
429
|
+
class SaveDmpd(SaveTaskTags):
|
|
430
|
+
@property
|
|
431
|
+
def tag_lists_to_save(self):
|
|
432
|
+
return [
|
|
433
|
+
[
|
|
434
|
+
TrendTag.intermediate(),
|
|
435
|
+
TrendTag.arm_id(self.arm_id),
|
|
436
|
+
TrendTag.task_single_beam_demodulated_polcal_data(),
|
|
437
|
+
],
|
|
438
|
+
[
|
|
439
|
+
TrendTag.intermediate(),
|
|
440
|
+
TrendTag.arm_id(self.arm_id),
|
|
441
|
+
TrendTag.task_demodulated_polcal_data(),
|
|
442
|
+
],
|
|
443
|
+
]
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def arm_id(self):
|
|
447
|
+
return arm_id
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def relative_save_file(self) -> str:
|
|
451
|
+
return f"dmpd_{arm_id}.asdf"
|
|
452
|
+
|
|
453
|
+
return SaveDmpd
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def load_dmpd_task(arm_id: str | int):
|
|
457
|
+
class LoadDmpd(LoadTaskTags):
|
|
458
|
+
@property
|
|
459
|
+
def relative_save_file(self) -> str:
|
|
460
|
+
return f"dmpd_{arm_id}.asdf"
|
|
461
|
+
|
|
462
|
+
return LoadDmpd
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def save_visp_geo_cal_task(arm_id: str | int):
|
|
466
|
+
class SaveVispGeoCal(SaveTaskTags):
|
|
467
|
+
@property
|
|
468
|
+
def task_str(self) -> str:
|
|
469
|
+
return TrendTaskName.visp_geometric_calibration.value
|
|
470
|
+
|
|
471
|
+
@property
|
|
472
|
+
def arm_id(self):
|
|
473
|
+
return arm_id
|
|
474
|
+
|
|
475
|
+
@property
|
|
476
|
+
def relative_save_file(self) -> str:
|
|
477
|
+
return f"visp_geo_cal_{arm_id}.asdf"
|
|
478
|
+
|
|
479
|
+
return SaveVispGeoCal
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def load_visp_geo_cal_task(arm_id: str | int):
|
|
483
|
+
class LoadVispGeoCal(LoadTaskTags):
|
|
484
|
+
@property
|
|
485
|
+
def relative_save_file(self) -> str:
|
|
486
|
+
return f"visp_geo_cal_{arm_id}.asdf"
|
|
487
|
+
|
|
488
|
+
return LoadVispGeoCal
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from subprocess import Popen
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from dkist_processing_core import TaskBase
|
|
5
|
+
|
|
6
|
+
from dkist_processing_trend.tasks.arm_task_factory import initialize_arm_task
|
|
7
|
+
from dkist_processing_trend.tasks.arm_task_factory import retreive_arm_task
|
|
8
|
+
from dkist_processing_trend.tasks.trend_base import TrendArmTaskBase
|
|
9
|
+
from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DummyArmTask(TrendArmTaskBase):
|
|
13
|
+
"""A real cool class"""
|
|
14
|
+
|
|
15
|
+
def run(self):
|
|
16
|
+
return 5
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize("arm_id", [pytest.param("foo", id="str"), pytest.param(3, id="int")])
|
|
20
|
+
def test_arm_task_factory(arm_id, recipe_run_id, link_constants_db):
|
|
21
|
+
"""
|
|
22
|
+
Given: A Task class that subclasses `TrendArmTaskBase`
|
|
23
|
+
When: Initializing a new version of that task with a pre-defined arm_id and then retreiving the new class
|
|
24
|
+
Then: The new class is a `TaskBase`, has the correct arm_id, retains the docstring of its parent, and is importable
|
|
25
|
+
from the parent's module.
|
|
26
|
+
"""
|
|
27
|
+
link_constants_db(
|
|
28
|
+
recipe_run_id,
|
|
29
|
+
TrendTestingConstantsDatabase(),
|
|
30
|
+
)
|
|
31
|
+
initialize_arm_task(DummyArmTask, arm_id=arm_id)
|
|
32
|
+
arm_task_class = retreive_arm_task(DummyArmTask, arm_id=arm_id)
|
|
33
|
+
assert issubclass(arm_task_class, DummyArmTask)
|
|
34
|
+
assert issubclass(arm_task_class, TaskBase)
|
|
35
|
+
assert arm_task_class.__doc__ == "A real cool class"
|
|
36
|
+
assert arm_task_class.__name__ == f"DummyArmTask{str(arm_id).capitalize()}"
|
|
37
|
+
assert arm_task_class.__qualname__ == f"DummyArmTask{str(arm_id).capitalize()}"
|
|
38
|
+
assert arm_task_class.__module__ == DummyArmTask.__module__
|
|
39
|
+
|
|
40
|
+
with arm_task_class(
|
|
41
|
+
recipe_run_id=recipe_run_id, workflow_version="version", workflow_name="name"
|
|
42
|
+
) as task:
|
|
43
|
+
assert task.arm_id == arm_id
|
|
44
|
+
assert task.run() == 5
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_uninitialized_arm_task_raises():
|
|
48
|
+
"""
|
|
49
|
+
Given: A Task class that subclasses `TrendArmTaskBase`
|
|
50
|
+
When: Trying to retrieve an "arm version" of that class, when the particular arm hasn't been initialized
|
|
51
|
+
Then: The correct error is raise
|
|
52
|
+
"""
|
|
53
|
+
initialize_arm_task(DummyArmTask, arm_id="foo")
|
|
54
|
+
with pytest.raises(AttributeError, match="Can't find DummyArmTaskBar"):
|
|
55
|
+
retreive_arm_task(DummyArmTask, arm_id="bar")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.parametrize(
|
|
59
|
+
"module_path, task_name",
|
|
60
|
+
[
|
|
61
|
+
pytest.param(
|
|
62
|
+
"tasks.visp.visp_process_demod", "VispProcessDemodulationMatrices1", id="visp"
|
|
63
|
+
),
|
|
64
|
+
pytest.param("tasks.dark", "DarkCalibrationSp", id="cryo"),
|
|
65
|
+
pytest.param("tasks.run_pac_fitter", "RunPacFitterHband", id="dlnirsp"),
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
def test_real_arm_factory_tasks_are_importable(module_path, task_name):
|
|
69
|
+
"""
|
|
70
|
+
Given: A python environment with `dkist_processing_trend` installed
|
|
71
|
+
When: Trying to import a task class that was defined with `initialize_arm_task` from its parent's module
|
|
72
|
+
Then: The import succeeds
|
|
73
|
+
"""
|
|
74
|
+
p = Popen(
|
|
75
|
+
args=[
|
|
76
|
+
"python",
|
|
77
|
+
"-c",
|
|
78
|
+
f"from dkist_processing_trend.{module_path} import {task_name}",
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
p.wait()
|
|
82
|
+
assert p.returncode == 0
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from dkist_processing_common.codecs.str import str_decoder
|
|
5
|
+
from dkist_processing_common.codecs.str import str_encoder
|
|
6
|
+
|
|
7
|
+
from dkist_processing_trend.models.constants import TrendConstants
|
|
8
|
+
from dkist_processing_trend.models.tags import TrendTag
|
|
9
|
+
from dkist_processing_trend.tasks.trend_base import TrendArmTaskBase
|
|
10
|
+
from dkist_processing_trend.tasks.trend_base import TrendTaskBase
|
|
11
|
+
from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def trend_science_task(recipe_run_id, link_constants_db):
|
|
16
|
+
class ScienceTask(TrendTaskBase):
|
|
17
|
+
def run(self): ...
|
|
18
|
+
|
|
19
|
+
link_constants_db(recipe_run_id, TrendTestingConstantsDatabase())
|
|
20
|
+
with ScienceTask(
|
|
21
|
+
recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
|
|
22
|
+
) as task:
|
|
23
|
+
yield task
|
|
24
|
+
task._purge
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ArmScienceTask(TrendArmTaskBase):
|
|
28
|
+
def run(self):
|
|
29
|
+
self.write(data="Ran .run!", tags=[TrendTag.task("ALL_DONE")], encoder=str_encoder)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def make_trend_arm_science_task(recipe_run_id, link_constants_db):
|
|
34
|
+
|
|
35
|
+
def make_task(arm_id):
|
|
36
|
+
link_constants_db(recipe_run_id, TrendTestingConstantsDatabase())
|
|
37
|
+
with ArmScienceTask(
|
|
38
|
+
arm_id=arm_id,
|
|
39
|
+
recipe_run_id=recipe_run_id,
|
|
40
|
+
workflow_name="name",
|
|
41
|
+
workflow_version="version",
|
|
42
|
+
) as task:
|
|
43
|
+
yield task
|
|
44
|
+
task._purge
|
|
45
|
+
|
|
46
|
+
return make_task
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_trend_base_task(trend_science_task):
|
|
50
|
+
"""
|
|
51
|
+
Given: A task class that is a subclass of `TrendTaskBase`
|
|
52
|
+
When: Instantiating the class
|
|
53
|
+
Then: The class' `.constants` property is the correct type
|
|
54
|
+
"""
|
|
55
|
+
task = trend_science_task
|
|
56
|
+
assert isinstance(task.constants, TrendConstants)
|
|
57
|
+
assert task.constants.arm_id_list == [1, 2, 3]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.parametrize(
|
|
61
|
+
"dataset_has_arm_data",
|
|
62
|
+
[pytest.param(False, id="arm_w_no_data"), pytest.param(True, id="arm_w_data")],
|
|
63
|
+
)
|
|
64
|
+
def test_trend_arm_base_task(make_trend_arm_science_task, dataset_has_arm_data):
|
|
65
|
+
"""
|
|
66
|
+
Given: A task class that is a subclass of `TrendArmTaskBase`
|
|
67
|
+
When: Instantiating that class
|
|
68
|
+
Then: The `arm_id` argument is correctly set as the `.arm_id` property
|
|
69
|
+
"""
|
|
70
|
+
constants_arm_id_list = TrendTestingConstantsDatabase().ARM_ID_LIST
|
|
71
|
+
if dataset_has_arm_data:
|
|
72
|
+
arm_id = random.choice(constants_arm_id_list)
|
|
73
|
+
else:
|
|
74
|
+
arm_id = "LUHILHDLLVUHFSHDUIAHDL"
|
|
75
|
+
assert arm_id not in constants_arm_id_list
|
|
76
|
+
|
|
77
|
+
task = next(make_trend_arm_science_task(arm_id=arm_id))
|
|
78
|
+
assert task.arm_id == arm_id
|
|
79
|
+
|
|
80
|
+
task()
|
|
81
|
+
|
|
82
|
+
if dataset_has_arm_data:
|
|
83
|
+
assert task.count(tags=[TrendTag.task("ALL_DONE")]) == 1
|
|
84
|
+
assert next(task.read(tags=[TrendTag.task("ALL_DONE")], decoder=str_decoder)) == "Ran .run!"
|
|
85
|
+
else:
|
|
86
|
+
assert task.count(tags=[TrendTag.task("ALL_DONE")]) == 0
|