dkist-processing-cryonirsp 1.3.4__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.

Potentially problematic release.


This version of dkist-processing-cryonirsp might be problematic. Click here for more details.

Files changed (111) hide show
  1. changelog/.gitempty +0 -0
  2. dkist_processing_cryonirsp/__init__.py +11 -0
  3. dkist_processing_cryonirsp/config.py +12 -0
  4. dkist_processing_cryonirsp/models/__init__.py +1 -0
  5. dkist_processing_cryonirsp/models/constants.py +248 -0
  6. dkist_processing_cryonirsp/models/exposure_conditions.py +26 -0
  7. dkist_processing_cryonirsp/models/parameters.py +296 -0
  8. dkist_processing_cryonirsp/models/tags.py +168 -0
  9. dkist_processing_cryonirsp/models/task_name.py +14 -0
  10. dkist_processing_cryonirsp/parsers/__init__.py +1 -0
  11. dkist_processing_cryonirsp/parsers/cryonirsp_l0_fits_access.py +111 -0
  12. dkist_processing_cryonirsp/parsers/cryonirsp_l1_fits_access.py +30 -0
  13. dkist_processing_cryonirsp/parsers/exposure_conditions.py +163 -0
  14. dkist_processing_cryonirsp/parsers/map_repeats.py +40 -0
  15. dkist_processing_cryonirsp/parsers/measurements.py +55 -0
  16. dkist_processing_cryonirsp/parsers/modstates.py +31 -0
  17. dkist_processing_cryonirsp/parsers/optical_density_filters.py +40 -0
  18. dkist_processing_cryonirsp/parsers/polarimetric_check.py +120 -0
  19. dkist_processing_cryonirsp/parsers/scan_step.py +412 -0
  20. dkist_processing_cryonirsp/parsers/time.py +80 -0
  21. dkist_processing_cryonirsp/parsers/wavelength.py +26 -0
  22. dkist_processing_cryonirsp/tasks/__init__.py +19 -0
  23. dkist_processing_cryonirsp/tasks/assemble_movie.py +202 -0
  24. dkist_processing_cryonirsp/tasks/bad_pixel_map.py +96 -0
  25. dkist_processing_cryonirsp/tasks/beam_boundaries_base.py +279 -0
  26. dkist_processing_cryonirsp/tasks/ci_beam_boundaries.py +55 -0
  27. dkist_processing_cryonirsp/tasks/ci_science.py +169 -0
  28. dkist_processing_cryonirsp/tasks/cryonirsp_base.py +67 -0
  29. dkist_processing_cryonirsp/tasks/dark.py +98 -0
  30. dkist_processing_cryonirsp/tasks/gain.py +251 -0
  31. dkist_processing_cryonirsp/tasks/instrument_polarization.py +447 -0
  32. dkist_processing_cryonirsp/tasks/l1_output_data.py +44 -0
  33. dkist_processing_cryonirsp/tasks/linearity_correction.py +582 -0
  34. dkist_processing_cryonirsp/tasks/make_movie_frames.py +302 -0
  35. dkist_processing_cryonirsp/tasks/mixin/__init__.py +1 -0
  36. dkist_processing_cryonirsp/tasks/mixin/beam_access.py +52 -0
  37. dkist_processing_cryonirsp/tasks/mixin/corrections.py +177 -0
  38. dkist_processing_cryonirsp/tasks/mixin/intermediate_frame.py +193 -0
  39. dkist_processing_cryonirsp/tasks/mixin/linearized_frame.py +309 -0
  40. dkist_processing_cryonirsp/tasks/mixin/shift_measurements.py +297 -0
  41. dkist_processing_cryonirsp/tasks/parse.py +281 -0
  42. dkist_processing_cryonirsp/tasks/quality_metrics.py +271 -0
  43. dkist_processing_cryonirsp/tasks/science_base.py +511 -0
  44. dkist_processing_cryonirsp/tasks/sp_beam_boundaries.py +270 -0
  45. dkist_processing_cryonirsp/tasks/sp_dispersion_axis_correction.py +484 -0
  46. dkist_processing_cryonirsp/tasks/sp_geometric.py +585 -0
  47. dkist_processing_cryonirsp/tasks/sp_science.py +299 -0
  48. dkist_processing_cryonirsp/tasks/sp_solar_gain.py +475 -0
  49. dkist_processing_cryonirsp/tasks/trial_output_data.py +61 -0
  50. dkist_processing_cryonirsp/tasks/write_l1.py +1033 -0
  51. dkist_processing_cryonirsp/tests/__init__.py +1 -0
  52. dkist_processing_cryonirsp/tests/conftest.py +456 -0
  53. dkist_processing_cryonirsp/tests/header_models.py +592 -0
  54. dkist_processing_cryonirsp/tests/local_trial_workflows/__init__.py +0 -0
  55. dkist_processing_cryonirsp/tests/local_trial_workflows/l0_cals_only.py +541 -0
  56. dkist_processing_cryonirsp/tests/local_trial_workflows/l0_to_l1.py +615 -0
  57. dkist_processing_cryonirsp/tests/local_trial_workflows/linearize_only.py +96 -0
  58. dkist_processing_cryonirsp/tests/local_trial_workflows/local_trial_helpers.py +592 -0
  59. dkist_processing_cryonirsp/tests/test_assemble_movie.py +144 -0
  60. dkist_processing_cryonirsp/tests/test_assemble_qualilty.py +517 -0
  61. dkist_processing_cryonirsp/tests/test_bad_pixel_maps.py +115 -0
  62. dkist_processing_cryonirsp/tests/test_ci_beam_boundaries.py +106 -0
  63. dkist_processing_cryonirsp/tests/test_ci_science.py +355 -0
  64. dkist_processing_cryonirsp/tests/test_corrections.py +126 -0
  65. dkist_processing_cryonirsp/tests/test_cryo_base.py +202 -0
  66. dkist_processing_cryonirsp/tests/test_cryo_constants.py +76 -0
  67. dkist_processing_cryonirsp/tests/test_dark.py +287 -0
  68. dkist_processing_cryonirsp/tests/test_gain.py +278 -0
  69. dkist_processing_cryonirsp/tests/test_instrument_polarization.py +531 -0
  70. dkist_processing_cryonirsp/tests/test_linearity_correction.py +245 -0
  71. dkist_processing_cryonirsp/tests/test_make_movie_frames.py +111 -0
  72. dkist_processing_cryonirsp/tests/test_parameters.py +266 -0
  73. dkist_processing_cryonirsp/tests/test_parse.py +1439 -0
  74. dkist_processing_cryonirsp/tests/test_quality.py +203 -0
  75. dkist_processing_cryonirsp/tests/test_sp_beam_boundaries.py +112 -0
  76. dkist_processing_cryonirsp/tests/test_sp_dispersion_axis_correction.py +155 -0
  77. dkist_processing_cryonirsp/tests/test_sp_geometric.py +319 -0
  78. dkist_processing_cryonirsp/tests/test_sp_make_movie_frames.py +121 -0
  79. dkist_processing_cryonirsp/tests/test_sp_science.py +483 -0
  80. dkist_processing_cryonirsp/tests/test_sp_solar.py +198 -0
  81. dkist_processing_cryonirsp/tests/test_trial_create_quality_report.py +79 -0
  82. dkist_processing_cryonirsp/tests/test_trial_output_data.py +251 -0
  83. dkist_processing_cryonirsp/tests/test_workflows.py +9 -0
  84. dkist_processing_cryonirsp/tests/test_write_l1.py +436 -0
  85. dkist_processing_cryonirsp/workflows/__init__.py +2 -0
  86. dkist_processing_cryonirsp/workflows/ci_l0_processing.py +77 -0
  87. dkist_processing_cryonirsp/workflows/sp_l0_processing.py +84 -0
  88. dkist_processing_cryonirsp/workflows/trial_workflows.py +190 -0
  89. dkist_processing_cryonirsp-1.3.4.dist-info/METADATA +194 -0
  90. dkist_processing_cryonirsp-1.3.4.dist-info/RECORD +111 -0
  91. dkist_processing_cryonirsp-1.3.4.dist-info/WHEEL +5 -0
  92. dkist_processing_cryonirsp-1.3.4.dist-info/top_level.txt +4 -0
  93. docs/Makefile +134 -0
  94. docs/bad_pixel_calibration.rst +47 -0
  95. docs/beam_angle_calculation.rst +53 -0
  96. docs/beam_boundary_computation.rst +88 -0
  97. docs/changelog.rst +7 -0
  98. docs/ci_science_calibration.rst +33 -0
  99. docs/conf.py +52 -0
  100. docs/index.rst +21 -0
  101. docs/l0_to_l1_cryonirsp_ci-full-trial.rst +10 -0
  102. docs/l0_to_l1_cryonirsp_ci.rst +10 -0
  103. docs/l0_to_l1_cryonirsp_sp-full-trial.rst +10 -0
  104. docs/l0_to_l1_cryonirsp_sp.rst +10 -0
  105. docs/linearization.rst +43 -0
  106. docs/make.bat +170 -0
  107. docs/requirements.txt +1 -0
  108. docs/requirements_table.rst +8 -0
  109. docs/scientific_changelog.rst +10 -0
  110. docs/sp_science_calibration.rst +59 -0
  111. licenses/LICENSE.rst +11 -0
@@ -0,0 +1,96 @@
1
+ import argparse
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ from dkist_processing_common.manual import ManualProcessing
6
+
7
+ from dkist_processing_cryonirsp.models.tags import CryonirspTag
8
+ from dkist_processing_cryonirsp.tasks.linearity_correction import LinearityCorrection
9
+ from dkist_processing_cryonirsp.tasks.parse import ParseL0CryonirspRampData
10
+ from dkist_processing_cryonirsp.tests.local_trial_workflows.local_trial_helpers import (
11
+ create_input_dataset_parameter_document,
12
+ )
13
+ from dkist_processing_cryonirsp.tests.local_trial_workflows.local_trial_helpers import (
14
+ save_parsing_task,
15
+ )
16
+ from dkist_processing_cryonirsp.tests.local_trial_workflows.local_trial_helpers import (
17
+ SaveLinearizedFiles,
18
+ )
19
+ from dkist_processing_cryonirsp.tests.local_trial_workflows.local_trial_helpers import (
20
+ tag_inputs_task,
21
+ )
22
+ from dkist_processing_cryonirsp.tests.local_trial_workflows.local_trial_helpers import (
23
+ translate_122_to_214_task,
24
+ )
25
+
26
+
27
+ def main(
28
+ scratch_path: str,
29
+ suffix: str = "dat",
30
+ recipe_run_id: int = 2,
31
+ skip_translation: bool = False,
32
+ param_path: Path = None,
33
+ ):
34
+ with ManualProcessing(
35
+ workflow_path=Path(scratch_path),
36
+ recipe_run_id=recipe_run_id,
37
+ testing=True,
38
+ workflow_name="cryonirsp-l0-pipeline", # need sperate workflows for CI and SP?
39
+ workflow_version="GROGU",
40
+ ) as manual_processing_run:
41
+ if not skip_translation:
42
+ manual_processing_run.run_task(task=translate_122_to_214_task(suffix))
43
+
44
+ manual_processing_run.run_task(
45
+ task=create_input_dataset_parameter_document(param_path=param_path)
46
+ )
47
+ manual_processing_run.run_task(task=tag_inputs_task(suffix))
48
+ manual_processing_run.run_task(task=ParseL0CryonirspRampData)
49
+ manual_processing_run.run_task(
50
+ task=save_parsing_task(
51
+ tag_list=[CryonirspTag.input(), CryonirspTag.frame()],
52
+ save_file="input_parsing.asdf",
53
+ save_file_tags=False,
54
+ )
55
+ )
56
+ manual_processing_run.run_task(task=LinearityCorrection)
57
+ manual_processing_run.run_task(task=SaveLinearizedFiles)
58
+
59
+
60
+ if __name__ == "__main__":
61
+ parser = argparse.ArgumentParser(
62
+ description="Run an end-to-end test of the Cryonirsp DC Science pipeline"
63
+ )
64
+ parser.add_argument("scratch_path", help="Location to use as the DC 'scratch' disk")
65
+ parser.add_argument(
66
+ "-i",
67
+ "--run-id",
68
+ help="Which subdir to use. This will become the recipe run id",
69
+ type=int,
70
+ default=4,
71
+ )
72
+ parser.add_argument("--suffix", help="File suffix to treat as INPUT frames", default="FITS")
73
+ parser.add_argument(
74
+ "-T",
75
+ "--skip-translation",
76
+ help="Skip the translation of raw 122 l0 frames to 214 l0",
77
+ action="store_true",
78
+ )
79
+ parser.add_argument(
80
+ "-p",
81
+ "--param-path",
82
+ help="Path to parameter directory",
83
+ type=str,
84
+ default=None,
85
+ )
86
+
87
+ args = parser.parse_args()
88
+ sys.exit(
89
+ main(
90
+ scratch_path=args.scratch_path,
91
+ suffix=args.suffix,
92
+ recipe_run_id=args.run_id,
93
+ skip_translation=args.skip_translation,
94
+ param_path=Path(args.param_path),
95
+ )
96
+ )
@@ -0,0 +1,592 @@
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.models.task_name import TaskName
12
+ from dkist_processing_common.tasks import WorkflowTaskBase
13
+ from dkist_processing_common.tasks.mixin.globus import GlobusTransferItem
14
+ from dkist_service_configuration.logging import logger
15
+
16
+ from dkist_processing_cryonirsp.models.tags import CryonirspTag
17
+ from dkist_processing_cryonirsp.models.task_name import CryonirspTaskName
18
+ from dkist_processing_cryonirsp.tasks import TransferCryoTrialData
19
+ from dkist_processing_cryonirsp.tasks.cryonirsp_base import CryonirspTaskBase
20
+ from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
21
+
22
+
23
+ # These are the workflow versions of the polyfit coefficient parameters
24
+ WORKFLOW_LINEARIZATION_POLYFIT_COEFFS_CI = [
25
+ 1.1565851e00,
26
+ -1.1925439e-05,
27
+ 2.6748548e-10,
28
+ -2.7366326e-15,
29
+ ]
30
+
31
+ WORKFLOW_LINEARIZATION_POLYFIT_COEFFS_SP = [
32
+ 1.1421334e00,
33
+ -1.0536260e-05,
34
+ 2.2702712e-10,
35
+ -2.5632187e-15,
36
+ ]
37
+
38
+
39
+ def save_parsing_task(
40
+ tag_list: [str], save_file: str, save_file_tags: bool = True, save_constants: bool = True
41
+ ):
42
+ class SaveParsing(WorkflowTaskBase):
43
+ """Save the result of parsing (constants and tags) to an asdf file."""
44
+
45
+ @property
46
+ def relative_save_file(self) -> str:
47
+ return save_file
48
+
49
+ def run(self):
50
+ if save_file_tags:
51
+ file_tag_dict = self.get_input_tags()
52
+ else:
53
+ logger.info("Skipping saving of file tags")
54
+ file_tag_dict = dict()
55
+ if save_constants:
56
+ constant_dict = self.get_constants()
57
+ else:
58
+ logger.info("Skipping saving of constants")
59
+ constant_dict = dict()
60
+
61
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
62
+ tree = {"file_tag_dict": file_tag_dict, "constants_dict": constant_dict}
63
+ af = asdf.AsdfFile(tree)
64
+ af.write_to(full_save_file)
65
+ logger.info(f"Saved input tags to {full_save_file}")
66
+
67
+ def get_input_tags(self) -> dict[str, list[str]]:
68
+ file_tag_dict = dict()
69
+ path_list = self.read(tags=tag_list)
70
+ for p in path_list:
71
+ tags = self.tags(p)
72
+ file_tag_dict[str(p)] = tags
73
+
74
+ return file_tag_dict
75
+
76
+ def get_constants(self) -> dict[str, str | float | list]:
77
+ constants_dict = dict()
78
+ for c in self.constants._db_dict.keys():
79
+ constants_dict[c] = self.constants._db_dict[c]
80
+
81
+ return constants_dict
82
+
83
+ return SaveParsing
84
+
85
+
86
+ def load_parsing_task(save_file: str):
87
+ class LoadParsing(WorkflowTaskBase):
88
+ """Load tags and constants into the database."""
89
+
90
+ @property
91
+ def relative_save_file(self) -> str:
92
+ return save_file
93
+
94
+ def run(self):
95
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
96
+ with asdf.open(full_save_file) as af:
97
+ file_tag_dict = af.tree["file_tag_dict"]
98
+ self.tag_input_files(file_tag_dict)
99
+
100
+ constants_dict = af.tree["constants_dict"]
101
+ self.populate_constants(constants_dict)
102
+
103
+ logger.info(f"Loaded tags and constants from {full_save_file}")
104
+
105
+ def tag_input_files(self, file_tag_dict: dict[str, list[str]]):
106
+ for f, t in file_tag_dict.items():
107
+ if not os.path.exists(f):
108
+ raise FileNotFoundError(f"Expected to find {f}, but it doesn't exist.")
109
+ self.tag(path=f, tags=t)
110
+
111
+ def populate_constants(self, constants_dict: dict[str, str | int | float]) -> None:
112
+ # First we purge all constants because a previous load might have polluted the DB
113
+ self.constants._purge()
114
+ for c, v in constants_dict.items():
115
+ logger.info(f"Setting value of {c} to {v}")
116
+ self.constants._update({c: v})
117
+
118
+ return LoadParsing
119
+
120
+
121
+ class SaveLinearizedFiles(WorkflowTaskBase):
122
+ """Save linearized files and their tags to a directory and asdf file."""
123
+
124
+ @property
125
+ def relative_save_file(self) -> str:
126
+ return "linearized.asdf"
127
+
128
+ def run(self):
129
+ file_tag_dict = dict()
130
+ path_list = self.read(tags=[CryonirspTag.linearized()])
131
+ save_dir = self.scratch.workflow_base_path / Path(self.relative_save_file).stem
132
+ save_dir.mkdir(exist_ok=True)
133
+ for p in path_list:
134
+ copied_path = shutil.move(str(p), save_dir)
135
+ tags = self.tags(p)
136
+ file_tag_dict[copied_path] = tags
137
+
138
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
139
+ tree = {"file_tag_dict": file_tag_dict}
140
+ af = asdf.AsdfFile(tree)
141
+ af.write_to(full_save_file)
142
+ logger.info(f"Saved linearized tags to {full_save_file}")
143
+
144
+
145
+ class LoadLinearizedFiles(WorkflowTaskBase):
146
+ """Load linearized tags that point to previously saved files."""
147
+
148
+ @property
149
+ def relative_save_file(self) -> str:
150
+ return "linearized.asdf"
151
+
152
+ def run(self):
153
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
154
+ with asdf.open(full_save_file) as af:
155
+ for f, t in af.tree["file_tag_dict"].items():
156
+ # This is so any of the old (un-moved) files still tagged in the db are removed from the db
157
+ current_files = self.read(tags=t)
158
+ for current_file in current_files:
159
+ self.remove_tags(current_file, t)
160
+
161
+ self.tag(path=f, tags=t)
162
+ logger.info(f"Loaded linearized files entries from {full_save_file}")
163
+
164
+
165
+ class SaveTaskTags(WorkflowTaskBase):
166
+ @property
167
+ def task_str(self) -> str:
168
+ return "TASK"
169
+
170
+ @property
171
+ def relative_save_file(self) -> str:
172
+ return "default_sav.asdf"
173
+
174
+ @property
175
+ def tag_lists_to_save(self) -> list[list[str]] | list[str]:
176
+ return [[CryonirspTag.task(self.task_str), CryonirspTag.intermediate()]]
177
+
178
+ def run(self):
179
+ file_tag_dict = dict()
180
+ tag_list_list = self.tag_lists_to_save
181
+ if isinstance(tag_list_list[0], str):
182
+ tag_list_list = [tag_list_list]
183
+
184
+ for tags_to_save in tag_list_list:
185
+ path_list = self.read(tags=tags_to_save)
186
+ save_dir = self.scratch.workflow_base_path / Path(self.relative_save_file).stem
187
+ save_dir.mkdir(exist_ok=True)
188
+ for p in path_list:
189
+ copied_path = shutil.copy(str(p), save_dir)
190
+ tags = self.tags(p)
191
+ file_tag_dict[copied_path] = tags
192
+
193
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
194
+ tree = {"file_tag_dict": file_tag_dict}
195
+ af = asdf.AsdfFile(tree)
196
+ af.write_to(full_save_file)
197
+ logger.info(f"Saved {self.task_str} to {full_save_file}")
198
+
199
+
200
+ class LoadTaskTags(WorkflowTaskBase):
201
+ @property
202
+ def relative_save_file(self) -> str:
203
+ return "default_sav.asdf"
204
+
205
+ def run(self):
206
+ full_save_file = self.scratch.workflow_base_path / self.relative_save_file
207
+ with asdf.open(full_save_file) as af:
208
+ for f, t in af.tree["file_tag_dict"].items():
209
+ self.tag(path=f, tags=t)
210
+ logger.info(f"Loaded database entries from {full_save_file}")
211
+
212
+
213
+ class SaveGeometricCal(WorkflowTaskBase):
214
+ def run(self) -> None:
215
+ relative_save_file = "geometric_cal.asdf"
216
+ file_tag_dict = dict()
217
+ path_list = list(
218
+ self.read(tags=[CryonirspTag.task_geometric_angle(), CryonirspTag.intermediate()])
219
+ )
220
+ path_list += list(
221
+ self.read(tags=[CryonirspTag.task_geometric_offset(), CryonirspTag.intermediate()])
222
+ )
223
+ path_list += list(
224
+ self.read(
225
+ tags=[CryonirspTag.task_geometric_sepectral_shifts(), CryonirspTag.intermediate()]
226
+ )
227
+ )
228
+ path_list += list(
229
+ self.read(
230
+ tags=[
231
+ CryonirspTag.quality("TASK_TYPES"),
232
+ CryonirspTag.workflow_task("SPGeometricCalibration"),
233
+ ]
234
+ )
235
+ )
236
+ save_dir = self.scratch.workflow_base_path / Path(relative_save_file).stem
237
+ save_dir.mkdir(exist_ok=True)
238
+ for p in path_list:
239
+ copied_path = shutil.copy(str(p), save_dir)
240
+ tags = self.tags(p)
241
+ file_tag_dict[copied_path] = tags
242
+
243
+ full_save_file = self.scratch.workflow_base_path / relative_save_file
244
+ tree = {"file_tag_dict": file_tag_dict}
245
+ af = asdf.AsdfFile(tree)
246
+ af.write_to(full_save_file)
247
+ logger.info(f"Saved Geometric Calibration to {full_save_file}")
248
+
249
+
250
+ class LoadGeometricCal(LoadTaskTags):
251
+ @property
252
+ def relative_save_file(self) -> str:
253
+ return "geometric_cal.asdf"
254
+
255
+
256
+ class SaveBadPixelMap(SaveTaskTags):
257
+ @property
258
+ def task_str(self):
259
+ return CryonirspTaskName.bad_pixel_map.value
260
+
261
+ @property
262
+ def relative_save_file(self) -> str:
263
+ return "bad_pixel_map.asdf"
264
+
265
+
266
+ class LoadBadPixelMap(LoadTaskTags):
267
+ @property
268
+ def relative_save_file(self) -> str:
269
+ return "bad_pixel_map.asdf"
270
+
271
+
272
+ class SaveBeamBoundaryCal(SaveTaskTags):
273
+ @property
274
+ def task_str(self):
275
+ return CryonirspTaskName.beam_boundaries.value
276
+
277
+ @property
278
+ def relative_save_file(self) -> str:
279
+ return "beam_boundary_cal.asdf"
280
+
281
+
282
+ class LoadBeamBoundaryCal(LoadTaskTags):
283
+ @property
284
+ def relative_save_file(self) -> str:
285
+ return "beam_boundary_cal.asdf"
286
+
287
+
288
+ class SaveDarkCal(SaveTaskTags):
289
+ @property
290
+ def task_str(self) -> str:
291
+ return TaskName.dark.value
292
+
293
+ @property
294
+ def relative_save_file(self) -> str:
295
+ return "dark_cal.asdf"
296
+
297
+ @property
298
+ def tag_lists_to_save(self) -> list[list[str]]:
299
+ return super().tag_lists_to_save + [
300
+ [CryonirspTag.quality("TASK_TYPES"), CryonirspTag.workflow_task("DarkCalibration")]
301
+ ]
302
+
303
+
304
+ class LoadDarkCal(LoadTaskTags):
305
+ @property
306
+ def relative_save_file(self) -> str:
307
+ return "dark_cal.asdf"
308
+
309
+
310
+ class SaveLampCal(SaveTaskTags):
311
+ @property
312
+ def task_str(self) -> str:
313
+ return TaskName.lamp_gain.value
314
+
315
+ @property
316
+ def relative_save_file(self) -> str:
317
+ return "lamp_cal.asdf"
318
+
319
+ @property
320
+ def tag_lists_to_save(self) -> list[list[str]]:
321
+ return super().tag_lists_to_save + [
322
+ [CryonirspTag.quality("TASK_TYPES"), CryonirspTag.workflow_task("LampGainCalibration")],
323
+ ]
324
+
325
+
326
+ class LoadLampCal(LoadTaskTags):
327
+ @property
328
+ def relative_save_file(self) -> str:
329
+ return "lamp_cal.asdf"
330
+
331
+
332
+ class SaveSolarCal(SaveTaskTags):
333
+ @property
334
+ def task_str(self) -> str:
335
+ return TaskName.solar_gain.value
336
+
337
+ @property
338
+ def relative_save_file(self) -> str:
339
+ return "solar_cal.asdf"
340
+
341
+ @property
342
+ def tag_lists_to_save(self) -> list[list[str]]:
343
+ return super().tag_lists_to_save + [
344
+ [
345
+ CryonirspTag.quality("TASK_TYPES"),
346
+ CryonirspTag.workflow_task("CISolarGainCalibration"),
347
+ ],
348
+ [
349
+ CryonirspTag.quality("TASK_TYPES"),
350
+ CryonirspTag.workflow_task("SPSolarGainCalibration"),
351
+ ],
352
+ [
353
+ CryonirspTag.intermediate(),
354
+ CryonirspTag.task_characteristic_spectra(),
355
+ ],
356
+ ]
357
+
358
+
359
+ class SaveDispersionAxisCorrection(SaveTaskTags):
360
+ @property
361
+ def task_str(self) -> str:
362
+ return CryonirspTaskName.spectral_fit.value
363
+
364
+ @property
365
+ def relative_save_file(self) -> str:
366
+ return "sp_dispersion_axis_correction.asdf"
367
+
368
+ @property
369
+ def tag_lists_to_save(self) -> list[list[str]]:
370
+ return super().tag_lists_to_save + [
371
+ [
372
+ CryonirspTag.quality("TASK_TYPES"),
373
+ CryonirspTag.workflow_task("SPDispersionAxisCorrection"),
374
+ ],
375
+ ]
376
+
377
+
378
+ class LoadSolarCal(LoadTaskTags):
379
+ @property
380
+ def relative_save_file(self) -> str:
381
+ return "solar_cal.asdf"
382
+
383
+
384
+ class LoadDispersionAxisCorrection(LoadTaskTags):
385
+ @property
386
+ def relative_save_file(self) -> str:
387
+ return "sp_dispersion_axis_correction.asdf"
388
+
389
+
390
+ class SaveInstPolCal(SaveTaskTags):
391
+ @property
392
+ def task_str(self) -> str:
393
+ return TaskName.demodulation_matrices.value
394
+
395
+ @property
396
+ def relative_save_file(self) -> str:
397
+ return "inst_pol_cal.asdf"
398
+
399
+ @property
400
+ def tag_lists_to_save(self) -> list[list[str]]:
401
+ return super().tag_lists_to_save + [
402
+ [
403
+ CryonirspTag.quality("TASK_TYPES"),
404
+ CryonirspTag.workflow_task("CIInstrumentPolarizationCalibration"),
405
+ ],
406
+ [
407
+ CryonirspTag.quality("TASK_TYPES"),
408
+ CryonirspTag.workflow_task("SPInstrumentPolarizationCalibration"),
409
+ ],
410
+ [CryonirspTag.quality("POLCAL_CONSTANT_PAR_VALS")],
411
+ [CryonirspTag.quality("POLCAL_GLOBAL_PAR_VALS")],
412
+ [CryonirspTag.quality("POLCAL_LOCAL_PAR_VALS")],
413
+ [CryonirspTag.quality("POLCAL_FIT_RESIDUALS")],
414
+ [CryonirspTag.quality("POLCAL_EFFICIENCY")],
415
+ ]
416
+
417
+
418
+ class LoadInstPolCal(LoadTaskTags):
419
+ @property
420
+ def relative_save_file(self) -> str:
421
+ return "inst_pol_cal.asdf"
422
+
423
+
424
+ def translate_122_to_214_task(suffix: str):
425
+ class Translate122To214L0(WorkflowTaskBase):
426
+ def run(self) -> None:
427
+ raw_dir = Path(self.scratch.scratch_base_path) / f"CRYO-NIRSP{self.recipe_run_id:03n}"
428
+ if not os.path.exists(self.scratch.workflow_base_path):
429
+ os.makedirs(self.scratch.workflow_base_path)
430
+
431
+ if not raw_dir.exists():
432
+ raise FileNotFoundError(
433
+ f"Expected to find a raw CRYO-NIRSP{self.recipe_run_id:03n} folder in {self.scratch.scratch_base_path}"
434
+ )
435
+
436
+ for file in raw_dir.glob(f"*.{suffix}"):
437
+ translated_file_name = Path(self.scratch.workflow_base_path) / os.path.basename(
438
+ file
439
+ )
440
+ logger.info(f"Translating {file} -> {translated_file_name}")
441
+ hdl = fits.open(file)
442
+ # Handle both compressed and uncompressed files...
443
+ if len(hdl) > 1:
444
+ hdl_header = hdl[1].header
445
+ hdl_data = hdl[1].data
446
+ else:
447
+ hdl_header = hdl[0].header
448
+ hdl_data = hdl[0].data
449
+ header = spec122_validator.validate_and_translate_to_214_l0(
450
+ hdl_header, return_type=fits.HDUList
451
+ )[0].header
452
+
453
+ comp_hdu = fits.CompImageHDU(header=header, data=hdl_data)
454
+ comp_hdl = fits.HDUList([fits.PrimaryHDU(), comp_hdu])
455
+ comp_hdl.writeto(translated_file_name, overwrite=True)
456
+
457
+ hdl.close()
458
+ del hdl
459
+ comp_hdl.close()
460
+ del comp_hdl
461
+
462
+ return Translate122To214L0
463
+
464
+
465
+ def create_input_dataset_parameter_document(param_path: Path):
466
+ class CreateInputDatasetParameterDocument(WorkflowTaskBase):
467
+ def run(self) -> None:
468
+ doc_path = self.scratch.workflow_base_path / "input_dataset_parameters.json"
469
+ with open(doc_path, "w") as f:
470
+ f.write(json.dumps(self.input_dataset_document_simple_parameters_part))
471
+ self.tag(doc_path, CryonirspTag.input_dataset_parameters())
472
+ logger.info(f"Wrote input dataset doc to {doc_path}")
473
+
474
+ @property
475
+ def input_dataset_document_simple_parameters_part(self):
476
+ parameters_list = []
477
+ value_id = randint(1000, 2000)
478
+ param_class = cryonirsp_testing_parameters_factory(
479
+ param_path=param_path, create_files=False
480
+ )
481
+ params = asdict(
482
+ param_class(
483
+ cryonirsp_linearization_polyfit_coeffs_ci=WORKFLOW_LINEARIZATION_POLYFIT_COEFFS_CI,
484
+ cryonirsp_linearization_polyfit_coeffs_sp=WORKFLOW_LINEARIZATION_POLYFIT_COEFFS_SP,
485
+ )
486
+ )
487
+ for pn, pv in params.items():
488
+ values = [
489
+ {
490
+ "parameterValueId": value_id,
491
+ "parameterValue": json.dumps(pv),
492
+ "parameterValueStartDate": "1946-11-20",
493
+ }
494
+ ]
495
+ parameter = {"parameterName": pn, "parameterValues": values}
496
+ parameters_list.append(parameter)
497
+
498
+ return parameters_list
499
+
500
+ return CreateInputDatasetParameterDocument
501
+
502
+
503
+ def tag_inputs_task(suffix: str):
504
+ class TagInputs(WorkflowTaskBase):
505
+ def run(self) -> None:
506
+ logger.info(f"Looking in {os.path.abspath(self.scratch.workflow_base_path)}")
507
+ input_file_list = list(self.scratch.workflow_base_path.glob(f"*.{suffix}"))
508
+ if len(input_file_list) == 0:
509
+ raise FileNotFoundError(
510
+ f"Did not find any files matching '*.{suffix}' in {self.scratch.workflow_base_path}"
511
+ )
512
+ for file in input_file_list:
513
+ logger.info(f"Found {file}")
514
+ self.tag(path=file, tags=[CryonirspTag.input(), CryonirspTag.frame()])
515
+
516
+ return TagInputs
517
+
518
+
519
+ class DBAccess(CryonirspTaskBase):
520
+ """
521
+ No-Op task that allows use to access the redis database.
522
+
523
+ I.e.:
524
+
525
+ task = DBAccess(recipe_run_id)
526
+ value = task.constants.whatever
527
+ """
528
+
529
+ def __init__(self, recipe_run_id: int):
530
+ workflow_name = "redis_db_access_task"
531
+ workflow_version = "vfoo.bar"
532
+
533
+ super().__init__(
534
+ recipe_run_id=recipe_run_id,
535
+ workflow_name=workflow_name,
536
+ workflow_version=workflow_version,
537
+ )
538
+
539
+ def run(self) -> None:
540
+ pass
541
+
542
+ def __call__(self, *args, **kwargs):
543
+ raise RuntimeError(f"{self.__class__.__name__} not intended to be called.")
544
+
545
+
546
+ def transfer_trial_data_locally_task(
547
+ trial_dir: str | Path,
548
+ debug_switch: bool = True,
549
+ intermediate_switch: bool = True,
550
+ output_swtich: bool = True,
551
+ tag_lists: list | None = None,
552
+ ):
553
+ class LocalTrialData(TransferCryoTrialData):
554
+ @property
555
+ def destination_folder(self) -> Path:
556
+ return Path(trial_dir)
557
+
558
+ @property
559
+ def debug_frame_switch(self) -> bool:
560
+ return debug_switch
561
+
562
+ @property
563
+ def intermediate_frame_switch(self) -> bool:
564
+ return intermediate_switch
565
+
566
+ @property
567
+ def output_frame_switch(self) -> bool:
568
+ return output_swtich
569
+
570
+ @property
571
+ def specific_frame_tag_lists(self) -> list:
572
+ return tag_lists or []
573
+
574
+ def remove_folder_objects(self):
575
+ logger.info("Would have removed folder objects here")
576
+
577
+ def globus_transfer_scratch_to_object_store(
578
+ self,
579
+ transfer_items: list[GlobusTransferItem],
580
+ label: str = None,
581
+ sync_level: str = None,
582
+ verify_checksum: bool = True,
583
+ ) -> None:
584
+ if label:
585
+ logger.info(f"Transferring files with {label = }")
586
+
587
+ for frame in transfer_items:
588
+ if not frame.destination_path.parent.exists():
589
+ frame.destination_path.parent.mkdir(parents=True)
590
+ os.system(f"cp {frame.source_path} {frame.destination_path}")
591
+
592
+ return LocalTrialData