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,436 @@
1
+ from datetime import datetime
2
+ from pathlib import Path
3
+
4
+ import numpy as np
5
+ import pytest
6
+ from astropy.io import fits
7
+ from astropy.time import Time
8
+ from dkist_fits_specifications import __version__ as spec_version
9
+ from dkist_header_validator import spec214_validator
10
+ from dkist_processing_common.codecs.asdf import asdf_encoder
11
+ from dkist_processing_common.codecs.fits import fits_hdulist_encoder
12
+ from dkist_processing_common.tests.conftest import FakeGQLClient
13
+
14
+ from dkist_processing_cryonirsp.models.tags import CryonirspTag
15
+ from dkist_processing_cryonirsp.tasks.write_l1 import CIWriteL1Frame
16
+ from dkist_processing_cryonirsp.tasks.write_l1 import CryonirspWriteL1Frame
17
+ from dkist_processing_cryonirsp.tasks.write_l1 import SPWriteL1Frame
18
+ from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
19
+
20
+
21
+ @pytest.fixture(scope="function")
22
+ def write_l1_task(
23
+ recipe_run_id,
24
+ calibrated_ci_cryonirsp_headers,
25
+ calibrated_cryonirsp_headers,
26
+ init_cryonirsp_constants_db,
27
+ num_stokes_params,
28
+ num_map_scans,
29
+ num_meas,
30
+ arm_id,
31
+ ):
32
+ if num_stokes_params == 1:
33
+ num_modstates = 1
34
+ else:
35
+ num_modstates = 2
36
+ num_scan_steps = 2
37
+ if arm_id == "CI":
38
+ axis_1_type = "HPLN-TAN"
39
+ axis_2_type = "HPLT-TAN"
40
+ axis_3_type = "AWAV"
41
+ write_l1_task = CIWriteL1Frame
42
+ calibrated_headers = calibrated_ci_cryonirsp_headers
43
+ else:
44
+ axis_1_type = "AWAV"
45
+ axis_2_type = "HPLT-TAN"
46
+ axis_3_type = "HPLN-TAN"
47
+ write_l1_task = SPWriteL1Frame
48
+ calibrated_headers = calibrated_cryonirsp_headers
49
+
50
+ constants_db = CryonirspConstantsDb(
51
+ AVERAGE_CADENCE=10,
52
+ MINIMUM_CADENCE=10,
53
+ MAXIMUM_CADENCE=10,
54
+ VARIANCE_CADENCE=0,
55
+ NUM_MAP_SCANS=num_map_scans,
56
+ NUM_SCAN_STEPS=num_scan_steps,
57
+ # Needed so self.correct_for_polarization is set to the right value
58
+ NUM_MODSTATES=num_modstates,
59
+ ARM_ID=arm_id,
60
+ AXIS_1_TYPE=axis_1_type,
61
+ AXIS_2_TYPE=axis_2_type,
62
+ AXIS_3_TYPE=axis_3_type,
63
+ NUM_MEAS=num_meas,
64
+ )
65
+
66
+ init_cryonirsp_constants_db(recipe_run_id, constants_db)
67
+ with write_l1_task(
68
+ recipe_run_id=recipe_run_id,
69
+ workflow_name="workflow_name",
70
+ workflow_version="workflow_version",
71
+ ) as task:
72
+ try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
73
+ if num_stokes_params == 1:
74
+ stokes_params = ["I"]
75
+ else:
76
+ stokes_params = ["I", "Q", "U", "V"]
77
+ # Random data needed so skew and kurtosis don't barf
78
+ header = calibrated_headers
79
+ hdu = fits.PrimaryHDU(
80
+ data=np.random.random((header["NAXIS3"], header["NAXIS2"], header["NAXIS1"]))
81
+ * 100.0,
82
+ header=calibrated_headers,
83
+ )
84
+ hdul = fits.HDUList([hdu])
85
+ hdul[0].header["CTYPE1"] = axis_1_type
86
+ hdul[0].header["CTYPE2"] = axis_2_type
87
+ hdul[0].header["CTYPE3"] = axis_3_type
88
+ hdul[0].header["CNNMAPS"] = num_map_scans
89
+
90
+ # Set CNMODNST and CNSPINMD so the correct schema is loaded for validation
91
+ hdul[0].header["CNMODNST"] = num_modstates
92
+ if num_stokes_params > 1:
93
+ hdul[0].header["CNSPINMD"] = "Continuous"
94
+
95
+ # These headers are added to polarimetric frames in the science task
96
+ hdul[0].header["POL_NOIS"] = 0.1
97
+ hdul[0].header["POL_SENS"] = 0.2
98
+
99
+ else:
100
+ hdul[0].header["CNSPINMD"] = "None"
101
+
102
+ if arm_id == "SP":
103
+ write_dummy_sp_dispersion_intermediate(task, hdul[0].header)
104
+
105
+ for map_scan in range(1, num_map_scans + 1):
106
+ for scan_step in range(1, num_scan_steps + 1):
107
+ for meas_num in range(1, num_meas + 1):
108
+ # all stokes files have the same date-beg
109
+ hdul[0].header["DATE-BEG"] = datetime.now().isoformat("T")
110
+ for stokes_param in stokes_params:
111
+ hdul[0].header["CNCMEAS"] = meas_num
112
+ hdul[0].header["CNMAP"] = map_scan
113
+ task.write(
114
+ data=hdul,
115
+ tags=[
116
+ CryonirspTag.calibrated(),
117
+ CryonirspTag.frame(),
118
+ CryonirspTag.stokes(stokes_param),
119
+ CryonirspTag.meas_num(meas_num),
120
+ CryonirspTag.map_scan(map_scan),
121
+ CryonirspTag.scan_step(scan_step),
122
+ ],
123
+ encoder=fits_hdulist_encoder,
124
+ )
125
+ yield task, stokes_params, hdul[0].header
126
+ finally:
127
+ task._purge()
128
+
129
+
130
+ def write_dummy_sp_dispersion_intermediate(task: SPWriteL1Frame, header: fits.Header) -> None:
131
+ overwrite_keys = [
132
+ "CRVAL1",
133
+ "CDELT1",
134
+ "CRVAL1A",
135
+ "CDELT1A",
136
+ ]
137
+
138
+ # Guaranteed to be different than the OG header values
139
+ dummy_fit_solution = {k: header[k] * (np.random.random() + 1.1) for k in overwrite_keys}
140
+ dummy_fit_solution["PV1_0"] = np.random.random()
141
+ dummy_fit_solution["PV1_1"] = np.random.randint(4)
142
+ dummy_fit_solution["PV1_2"] = np.random.random()
143
+
144
+ task.write(
145
+ data=dummy_fit_solution,
146
+ tags=[CryonirspTag.intermediate(), CryonirspTag.task_spectral_fit()],
147
+ encoder=asdf_encoder,
148
+ )
149
+
150
+
151
+ @pytest.mark.parametrize(
152
+ "num_stokes_params",
153
+ [pytest.param(1, id="Stokes I"), pytest.param(4, id="Stokes IQUV")],
154
+ )
155
+ @pytest.mark.parametrize(
156
+ "num_meas",
157
+ [pytest.param(1, id="single meas"), pytest.param(2, id="multiple meas")],
158
+ )
159
+ @pytest.mark.parametrize(
160
+ "num_map_scans",
161
+ [pytest.param(1, id="single map"), pytest.param(2, id="multiple maps")],
162
+ )
163
+ @pytest.mark.parametrize(
164
+ "arm_id",
165
+ [pytest.param("CI", id="CI"), pytest.param("SP", id="SP")],
166
+ )
167
+ def test_write_l1_frame(write_l1_task, mocker, arm_id, num_stokes_params, num_map_scans, num_meas):
168
+ """
169
+ :Given: a write L1 task
170
+ :When: running the task
171
+ :Then: the correct header keys are written
172
+ """
173
+ mocker.patch(
174
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
175
+ )
176
+ task, stokes_params, original_headers = write_l1_task
177
+ task()
178
+
179
+ for stokes_param in stokes_params:
180
+ common_tags = [
181
+ CryonirspTag.frame(),
182
+ CryonirspTag.stokes(stokes_param),
183
+ ]
184
+ output_files = list(task.read(tags=common_tags + [CryonirspTag.output()]))
185
+ calibrated_files = list(task.read(tags=common_tags + [CryonirspTag.calibrated()]))
186
+ assert (
187
+ len(output_files)
188
+ == task.constants.num_map_scans
189
+ * task.constants.num_scan_steps
190
+ * task.constants.num_meas
191
+ )
192
+ preserve_date_tests(output_files, calibrated_files)
193
+ for output_file in output_files:
194
+ assert output_file.exists
195
+ assert output_file.name.startswith(f"CRYO-NIRSP_{arm_id}_")
196
+ assert spec214_validator.validate(output_file, extra=False)
197
+ hdu_list = fits.open(output_file)
198
+ header = hdu_list[1].header
199
+ assert len(hdu_list) == 2 # Primary, CompImage
200
+ assert type(hdu_list[0]) is fits.PrimaryHDU
201
+ assert type(hdu_list[1]) is fits.CompImageHDU
202
+ axis_num = 1
203
+ if arm_id == "CI":
204
+ longitude_axis_tests(axis_num, header)
205
+ ci_spatial_axis_correction_tests(axis_num, original_headers, header)
206
+ else:
207
+ spectral_axis_tests(axis_num, header)
208
+ sp_spatial_axis_correction_tests(axis_num, original_headers, header)
209
+ sp_spectral_axis_correction_tests(original_headers, header)
210
+ axis_num += 1
211
+ latitude_axis_tests(axis_num, arm_id, header)
212
+ axis_num += 1
213
+ if task.constants.num_meas > 1:
214
+ measurement_axis_tests(axis_num, header, task)
215
+ axis_num += 1
216
+ scan_step_axis_tests(axis_num, arm_id, header, task)
217
+ if task.constants.num_map_scans > 1:
218
+ axis_num += 1
219
+ map_scan_tests(axis_num, header, task)
220
+ if task.constants.correct_for_polarization:
221
+ axis_num += 1
222
+ stokes_axis_tests(axis_num, header)
223
+
224
+ # Other general tests
225
+ general_header_tests(axis_num, header, task, arm_id)
226
+
227
+ axis_flip_tests(original_headers, header, arm_id)
228
+
229
+
230
+ def sp_spatial_axis_correction_tests(
231
+ axis_num: int, original_header: fits.Header, header: fits.Header
232
+ ):
233
+ # test that headers were actually updated
234
+ if axis_num == 1:
235
+ assert header[f"PC{axis_num}_1"] == 1.0
236
+ assert header[f"PC{axis_num}_1A"] == 1.0
237
+ assert header[f"PC{axis_num}_2"] == 0.0
238
+ assert header[f"PC{axis_num}_2A"] == 0.0
239
+ assert header[f"PC{axis_num}_3"] == 0.0
240
+ assert header[f"PC{axis_num}_3A"] == 0.0
241
+ elif axis_num == 2 or 3:
242
+ assert header[f"PC{axis_num}_1"] == 0.0
243
+ assert header[f"PC{axis_num}_1A"] == 0.0
244
+ assert header[f"PC{axis_num}_2"] != original_header[f"PC{axis_num}_2"]
245
+ assert header[f"PC{axis_num}_2A"] != original_header[f"PC{axis_num}_2A"]
246
+ assert header[f"PC{axis_num}_3"] != original_header[f"PC{axis_num}_3"]
247
+ assert header[f"PC{axis_num}_3A"] != original_header[f"PC{axis_num}_3A"]
248
+ assert header[f"CRPIX{axis_num}"] != original_header[f"CRPIX{axis_num}"]
249
+ assert header[f"CRPIX{axis_num}A"] != original_header[f"CRPIX{axis_num}A"]
250
+ assert header[f"CDELT{axis_num}"] != original_header[f"CDELT{axis_num}"]
251
+ assert header[f"CDELT{axis_num}A"] != original_header[f"CDELT{axis_num}A"]
252
+
253
+ assert header["SLITORI"]
254
+
255
+
256
+ def ci_spatial_axis_correction_tests(
257
+ axis_num: int, original_header: fits.Header, header: fits.Header
258
+ ):
259
+ if axis_num == 1 or 2:
260
+ assert header[f"PC{axis_num}_1"] != original_header[f"PC{axis_num}_1"]
261
+ assert header[f"PC{axis_num}_1A"] != original_header[f"PC{axis_num}_1A"]
262
+ assert header[f"PC{axis_num}_2"] != original_header[f"PC{axis_num}_2"]
263
+ assert header[f"PC{axis_num}_2A"] != original_header[f"PC{axis_num}_2A"]
264
+ assert header[f"PC{axis_num}_3"] == 0
265
+ assert header[f"PC{axis_num}_3A"] == 0
266
+ assert header[f"CRPIX{axis_num}"] != original_header[f"CRPIX{axis_num}"]
267
+ assert header[f"CRPIX{axis_num}A"] != original_header[f"CRPIX{axis_num}A"]
268
+ assert header[f"CDELT{axis_num}"] != original_header[f"CDELT{axis_num}"]
269
+ assert header[f"CDELT{axis_num}A"] != original_header[f"CDELT{axis_num}A"]
270
+
271
+ if axis_num == 3:
272
+ assert header[f"PC{axis_num}_1"] == 0
273
+ assert header[f"PC{axis_num}_2"] == 0
274
+ assert header[f"PC{axis_num}_3"] == 1
275
+ assert header[f"PC{axis_num}_1A"] == 0
276
+ assert header[f"PC{axis_num}_2A"] == 0
277
+ assert header[f"PC{axis_num}_3A"] == 1
278
+
279
+ assert header["SLITORI"]
280
+ assert header["CNM1BOFF"] == 8.0
281
+ assert header["CNM1OFF"] == -2.75
282
+
283
+
284
+ def sp_spectral_axis_correction_tests(original_headers: fits.Header, header: fits.Header):
285
+ # CRPIX gets tested in `axis_flip_tests`
286
+ assert header["CRVAL1"] != original_headers["CRVAL1"]
287
+ assert header["CRVAL1A"] != original_headers["CRVAL1A"]
288
+ assert header["CDELT1"] != original_headers["CDELT1"]
289
+ assert header["CDELT1A"] != original_headers["CDELT1A"]
290
+ assert header["CTYPE1"] == "AWAV-GRA"
291
+ assert header["CUNIT1"] == "nm"
292
+ assert header["CTYPE1A"] == "AWAV-GRA"
293
+ assert header["CUNIT1A"] == "nm"
294
+ assert "PV1_0" in header
295
+ assert "PV1_2" in header
296
+ assert "PV1_1" in header
297
+
298
+
299
+ def longitude_axis_tests(axis_num: int, header: fits.Header):
300
+ assert header[f"DNAXIS{axis_num}"] == header[f"NAXIS{axis_num}"]
301
+ assert header[f"DTYPE{axis_num}"] == "SPATIAL"
302
+ assert header[f"DWNAME{axis_num}"] == "helioprojective longitude"
303
+ assert header[f"DUNIT{axis_num}"] == header[f"CUNIT{axis_num}"]
304
+ assert header[f"DPNAME{axis_num}"] == "detector y axis"
305
+
306
+
307
+ def spectral_axis_tests(axis_num: int, header: fits.Header):
308
+ assert header[f"DNAXIS{axis_num}"] == header[f"NAXIS{axis_num}"]
309
+ assert header[f"DTYPE{axis_num}"] == "SPECTRAL"
310
+ assert header[f"DPNAME{axis_num}"] == "dispersion axis"
311
+ assert header[f"DWNAME{axis_num}"] == "wavelength"
312
+ assert header[f"DUNIT{axis_num}"] == header[f"CUNIT{axis_num}"]
313
+
314
+
315
+ def latitude_axis_tests(axis_num: int, arm_id: str, header: fits.Header):
316
+ if arm_id == "CI":
317
+ latitude_dp_name = "detector x axis"
318
+ else:
319
+ latitude_dp_name = "spatial along slit"
320
+ assert header[f"DNAXIS{axis_num}"] == header[f"NAXIS{axis_num}"]
321
+ assert header[f"DTYPE{axis_num}"] == "SPATIAL"
322
+ assert header[f"DPNAME{axis_num}"] == latitude_dp_name
323
+ assert header[f"DWNAME{axis_num}"] == "helioprojective latitude"
324
+ assert header[f"DUNIT{axis_num}"] == header[f"CUNIT{axis_num}"]
325
+
326
+
327
+ def measurement_axis_tests(axis_num: int, header: fits.Header, task: CryonirspWriteL1Frame):
328
+ assert header[f"DNAXIS{axis_num}"] == task.constants.num_meas
329
+ assert header[f"DTYPE{axis_num}"] == "TEMPORAL"
330
+ assert header[f"DPNAME{axis_num}"] == "measurement number"
331
+ assert header[f"DWNAME{axis_num}"] == "time"
332
+ assert header[f"DUNIT{axis_num}"] == "s"
333
+ assert header[f"DINDEX{axis_num}"] == header["CNCMEAS"]
334
+
335
+
336
+ def scan_step_axis_tests(
337
+ axis_num: int, arm_id: str, header: fits.Header, task: CryonirspWriteL1Frame
338
+ ):
339
+ if arm_id == "CI":
340
+ scan_step_value = "TEMPORAL"
341
+ scan_step_dwname = "time"
342
+ scan_step_dunit = "s"
343
+ else:
344
+ scan_step_value = "SPATIAL"
345
+ scan_step_dwname = "helioprojective longitude"
346
+ scan_step_dunit = header[f"CUNIT3"]
347
+
348
+ assert header[f"DNAXIS{axis_num}"] == task.constants.num_scan_steps
349
+ assert header[f"DTYPE{axis_num}"] == scan_step_value
350
+ assert header[f"DPNAME{axis_num}"] == "scan step number"
351
+ assert header[f"DWNAME{axis_num}"] == scan_step_dwname
352
+ assert header[f"DUNIT{axis_num}"] == scan_step_dunit
353
+
354
+
355
+ def map_scan_tests(axis_num: int, header: fits.Header, task: CryonirspWriteL1Frame):
356
+ assert header["CNNMAPS"] == task.constants.num_map_scans
357
+ assert header[f"DNAXIS{axis_num}"] == task.constants.num_map_scans
358
+ assert header[f"DTYPE{axis_num}"] == "TEMPORAL"
359
+ assert header[f"DPNAME{axis_num}"] == "map scan number"
360
+ assert header[f"DWNAME{axis_num}"] == "time"
361
+ assert header[f"DUNIT{axis_num}"] == "s"
362
+ assert header[f"DINDEX{axis_num}"] == header["CNMAP"]
363
+
364
+
365
+ def stokes_axis_tests(axis_num: int, header: fits.Header):
366
+ assert header[f"DNAXIS{axis_num}"] == 4
367
+ assert header[f"DTYPE{axis_num}"] == "STOKES"
368
+ assert header[f"DPNAME{axis_num}"] == "polarization state"
369
+ assert header[f"DWNAME{axis_num}"] == "polarization state"
370
+ assert header[f"DUNIT{axis_num}"] == ""
371
+ assert header[f"DINDEX{axis_num}"] in range(1, 5)
372
+
373
+
374
+ def axis_flip_tests(original_header: fits.Header, header: fits.Header, arm_id: str):
375
+ if arm_id == "SP":
376
+ axis_length = original_header["NAXIS1"]
377
+ ref_pix = original_header["CRPIX1"]
378
+ assert header["CDELT1"] > 0
379
+ assert header["CRPIX1"] == axis_length - ref_pix
380
+ if arm_id == "CI":
381
+ pass
382
+
383
+
384
+ def preserve_date_tests(output_files: list[Path], calibrated_files: list[Path]) -> None:
385
+ # Make sure we didn't overwrite pre-computed DATE-BEG and DATE-END keys
386
+ cal_headers = [fits.getheader(f) for f in calibrated_files]
387
+ output_headers = [fits.getheader(f, ext=1) for f in output_files]
388
+
389
+ assert sorted([h["DATE-BEG"] for h in cal_headers]) == sorted(
390
+ [h["DATE-BEG"] for h in output_headers]
391
+ )
392
+ assert sorted([h["DATE-END"] for h in cal_headers]) == sorted(
393
+ [h["DATE-END"] for h in output_headers]
394
+ )
395
+
396
+
397
+ def general_header_tests(
398
+ axis_num: int,
399
+ header: fits.Header,
400
+ task: CryonirspWriteL1Frame,
401
+ arm_id: str,
402
+ ):
403
+ # Other general tests
404
+ assert header["DAAXES"] == 2
405
+ assert header["DNAXIS"] == axis_num
406
+ assert header["DEAXES"] == axis_num - 2
407
+ assert f"DNAXIS{axis_num + 1}" not in header
408
+
409
+ assert header["INFO_URL"] == task.docs_base_url
410
+ assert header["HEADVERS"] == spec_version
411
+ assert header["HEAD_URL"] == f"{task.docs_base_url}/projects/data-products/en/v{spec_version}"
412
+ calvers = task.version_from_module_name()
413
+ assert header["CALVERS"] == calvers
414
+ assert (
415
+ header["CAL_URL"]
416
+ == f"{task.docs_base_url}/projects/{task.constants.instrument.lower()}/en/v{calvers}/{task.workflow_name}.html"
417
+ )
418
+ date_avg = (
419
+ (Time(header["DATE-END"], precision=6) - Time(header["DATE-BEG"], precision=6)) / 2
420
+ + Time(header["DATE-BEG"], precision=6)
421
+ ).isot
422
+ assert header["DATE-AVG"] == date_avg
423
+ assert isinstance(header["HLSVERS"], str)
424
+ assert header["NSPECLNS"] == 1
425
+ assert header["WAVEBAND"] == "He I (1083.0 nm)"
426
+ assert header["SPECLN01"] == "He I (1083.0 nm)"
427
+ with pytest.raises(KeyError):
428
+ header["SPECLN02"]
429
+
430
+ # wavelength range tests
431
+ if arm_id == "SP":
432
+ assert round(header["WAVEMIN"], 2) == 1082
433
+ assert round(header["WAVEMAX"], 2) == 1084
434
+ if arm_id == "CI":
435
+ assert header["WAVEMIN"] == 1082.5
436
+ assert header["WAVEMAX"] == 1083.5
@@ -0,0 +1,2 @@
1
+ """Workflow package."""
2
+ from dkist_processing_cryonirsp.config import dkist_processing_cryonirsp_configurations
@@ -0,0 +1,77 @@
1
+ """Cryo CI raw data processing workflow."""
2
+ from dkist_processing_common.tasks import PublishCatalogAndQualityMessages
3
+ from dkist_processing_common.tasks import QualityL1Metrics
4
+ from dkist_processing_common.tasks import SubmitDatasetMetadata
5
+ from dkist_processing_common.tasks import Teardown
6
+ from dkist_processing_common.tasks import TransferL0Data
7
+ from dkist_processing_common.tasks import TransferL1Data
8
+ from dkist_processing_core import ResourceQueue
9
+ from dkist_processing_core import Workflow
10
+
11
+ from dkist_processing_cryonirsp.tasks import AssembleCryonirspMovie
12
+ from dkist_processing_cryonirsp.tasks import BadPixelMapCalibration
13
+ from dkist_processing_cryonirsp.tasks import CIBeamBoundariesCalibration
14
+ from dkist_processing_cryonirsp.tasks import (
15
+ CIInstrumentPolarizationCalibration,
16
+ )
17
+ from dkist_processing_cryonirsp.tasks import CIScienceCalibration
18
+ from dkist_processing_cryonirsp.tasks import CISolarGainCalibration
19
+ from dkist_processing_cryonirsp.tasks import CIWriteL1Frame
20
+ from dkist_processing_cryonirsp.tasks import CryonirspL0QualityMetrics
21
+ from dkist_processing_cryonirsp.tasks import CryonirspL1QualityMetrics
22
+ from dkist_processing_cryonirsp.tasks import DarkCalibration
23
+ from dkist_processing_cryonirsp.tasks import LampGainCalibration
24
+ from dkist_processing_cryonirsp.tasks import LinearityCorrection
25
+ from dkist_processing_cryonirsp.tasks import MakeCryonirspMovieFrames
26
+ from dkist_processing_cryonirsp.tasks import ParseL0CryonirspRampData
27
+ from dkist_processing_cryonirsp.tasks.l1_output_data import CIAssembleQualityData
28
+ from dkist_processing_cryonirsp.tasks.parse import ParseL0CryonirspCILinearizedData
29
+
30
+ l0_pipeline = Workflow(
31
+ category="cryonirsp",
32
+ input_data="l0",
33
+ output_data="l1",
34
+ detail="ci",
35
+ workflow_package=__package__,
36
+ )
37
+ l0_pipeline.add_node(task=TransferL0Data, upstreams=None)
38
+
39
+ # Science flow
40
+ l0_pipeline.add_node(task=ParseL0CryonirspRampData, upstreams=TransferL0Data)
41
+ l0_pipeline.add_node(
42
+ task=LinearityCorrection,
43
+ resource_queue=ResourceQueue.HIGH_MEMORY,
44
+ upstreams=ParseL0CryonirspRampData,
45
+ )
46
+ l0_pipeline.add_node(task=ParseL0CryonirspCILinearizedData, upstreams=LinearityCorrection)
47
+ l0_pipeline.add_node(task=BadPixelMapCalibration, upstreams=ParseL0CryonirspCILinearizedData)
48
+ l0_pipeline.add_node(task=CIBeamBoundariesCalibration, upstreams=BadPixelMapCalibration)
49
+ l0_pipeline.add_node(task=DarkCalibration, upstreams=CIBeamBoundariesCalibration)
50
+ l0_pipeline.add_node(task=LampGainCalibration, upstreams=DarkCalibration)
51
+ l0_pipeline.add_node(task=CISolarGainCalibration, upstreams=LampGainCalibration)
52
+ l0_pipeline.add_node(task=CIInstrumentPolarizationCalibration, upstreams=CISolarGainCalibration)
53
+ l0_pipeline.add_node(task=CIScienceCalibration, upstreams=CIInstrumentPolarizationCalibration)
54
+ l0_pipeline.add_node(task=CIWriteL1Frame, upstreams=CIScienceCalibration)
55
+
56
+ # Movie flow
57
+ l0_pipeline.add_node(task=MakeCryonirspMovieFrames, upstreams=CIScienceCalibration)
58
+ l0_pipeline.add_node(task=AssembleCryonirspMovie, upstreams=MakeCryonirspMovieFrames)
59
+
60
+ # Quality flow
61
+ l0_pipeline.add_node(task=CryonirspL0QualityMetrics, upstreams=ParseL0CryonirspCILinearizedData)
62
+ l0_pipeline.add_node(task=QualityL1Metrics, upstreams=CIScienceCalibration)
63
+ l0_pipeline.add_node(task=CryonirspL1QualityMetrics, upstreams=CIScienceCalibration)
64
+ l0_pipeline.add_node(
65
+ task=CIAssembleQualityData,
66
+ upstreams=[CryonirspL0QualityMetrics, QualityL1Metrics, CryonirspL1QualityMetrics],
67
+ )
68
+
69
+ # Output flow
70
+ l0_pipeline.add_node(task=SubmitDatasetMetadata, upstreams=[CIWriteL1Frame, CIAssembleQualityData])
71
+ l0_pipeline.add_node(task=TransferL1Data, upstreams=[CIWriteL1Frame, AssembleCryonirspMovie])
72
+ l0_pipeline.add_node(
73
+ task=PublishCatalogAndQualityMessages, upstreams=[SubmitDatasetMetadata, TransferL1Data]
74
+ )
75
+
76
+ # goodbye
77
+ l0_pipeline.add_node(task=Teardown, upstreams=PublishCatalogAndQualityMessages)
@@ -0,0 +1,84 @@
1
+ """Cryo SP raw data processing workflow."""
2
+ from dkist_processing_common.tasks import PublishCatalogAndQualityMessages
3
+ from dkist_processing_common.tasks import QualityL1Metrics
4
+ from dkist_processing_common.tasks import SubmitDatasetMetadata
5
+ from dkist_processing_common.tasks import Teardown
6
+ from dkist_processing_common.tasks import TransferL0Data
7
+ from dkist_processing_common.tasks import TransferL1Data
8
+ from dkist_processing_core import ResourceQueue
9
+ from dkist_processing_core import Workflow
10
+
11
+ from dkist_processing_cryonirsp.tasks import AssembleCryonirspMovie
12
+ from dkist_processing_cryonirsp.tasks import BadPixelMapCalibration
13
+ from dkist_processing_cryonirsp.tasks import CryonirspL0QualityMetrics
14
+ from dkist_processing_cryonirsp.tasks import CryonirspL1QualityMetrics
15
+ from dkist_processing_cryonirsp.tasks import DarkCalibration
16
+ from dkist_processing_cryonirsp.tasks import LampGainCalibration
17
+ from dkist_processing_cryonirsp.tasks import LinearityCorrection
18
+ from dkist_processing_cryonirsp.tasks import MakeCryonirspMovieFrames
19
+ from dkist_processing_cryonirsp.tasks import ParseL0CryonirspRampData
20
+ from dkist_processing_cryonirsp.tasks import SPBeamBoundariesCalibration
21
+ from dkist_processing_cryonirsp.tasks import SPDispersionAxisCorrection
22
+ from dkist_processing_cryonirsp.tasks import SPGeometricCalibration
23
+ from dkist_processing_cryonirsp.tasks import (
24
+ SPInstrumentPolarizationCalibration,
25
+ )
26
+ from dkist_processing_cryonirsp.tasks import SPScienceCalibration
27
+ from dkist_processing_cryonirsp.tasks import SPSolarGainCalibration
28
+ from dkist_processing_cryonirsp.tasks import SPWriteL1Frame
29
+ from dkist_processing_cryonirsp.tasks.l1_output_data import SPAssembleQualityData
30
+ from dkist_processing_cryonirsp.tasks.parse import ParseL0CryonirspSPLinearizedData
31
+
32
+ l0_pipeline = Workflow(
33
+ category="cryonirsp",
34
+ input_data="l0",
35
+ output_data="l1",
36
+ detail="sp",
37
+ workflow_package=__package__,
38
+ )
39
+ l0_pipeline.add_node(task=TransferL0Data, upstreams=None)
40
+
41
+ # Science flow
42
+ l0_pipeline.add_node(task=ParseL0CryonirspRampData, upstreams=TransferL0Data)
43
+ l0_pipeline.add_node(
44
+ task=LinearityCorrection,
45
+ resource_queue=ResourceQueue.HIGH_MEMORY,
46
+ upstreams=ParseL0CryonirspRampData,
47
+ )
48
+ l0_pipeline.add_node(task=ParseL0CryonirspSPLinearizedData, upstreams=LinearityCorrection)
49
+ l0_pipeline.add_node(task=BadPixelMapCalibration, upstreams=ParseL0CryonirspSPLinearizedData)
50
+ l0_pipeline.add_node(task=SPBeamBoundariesCalibration, upstreams=BadPixelMapCalibration)
51
+ l0_pipeline.add_node(task=DarkCalibration, upstreams=SPBeamBoundariesCalibration)
52
+ l0_pipeline.add_node(task=LampGainCalibration, upstreams=DarkCalibration)
53
+ l0_pipeline.add_node(task=SPGeometricCalibration, upstreams=LampGainCalibration)
54
+ l0_pipeline.add_node(task=SPSolarGainCalibration, upstreams=SPGeometricCalibration)
55
+ l0_pipeline.add_node(task=SPDispersionAxisCorrection, upstreams=SPSolarGainCalibration)
56
+ l0_pipeline.add_node(task=SPInstrumentPolarizationCalibration, upstreams=SPSolarGainCalibration)
57
+ l0_pipeline.add_node(
58
+ task=SPScienceCalibration,
59
+ upstreams=[SPInstrumentPolarizationCalibration, SPDispersionAxisCorrection],
60
+ )
61
+ l0_pipeline.add_node(task=SPWriteL1Frame, upstreams=SPScienceCalibration)
62
+
63
+ # Movie flow
64
+ l0_pipeline.add_node(task=MakeCryonirspMovieFrames, upstreams=SPScienceCalibration)
65
+ l0_pipeline.add_node(task=AssembleCryonirspMovie, upstreams=MakeCryonirspMovieFrames)
66
+
67
+ # Quality flow
68
+ l0_pipeline.add_node(task=CryonirspL0QualityMetrics, upstreams=ParseL0CryonirspSPLinearizedData)
69
+ l0_pipeline.add_node(task=QualityL1Metrics, upstreams=SPScienceCalibration)
70
+ l0_pipeline.add_node(task=CryonirspL1QualityMetrics, upstreams=SPScienceCalibration)
71
+ l0_pipeline.add_node(
72
+ task=SPAssembleQualityData,
73
+ upstreams=[CryonirspL0QualityMetrics, QualityL1Metrics, CryonirspL1QualityMetrics],
74
+ )
75
+
76
+ # Output flow
77
+ l0_pipeline.add_node(task=SubmitDatasetMetadata, upstreams=[SPWriteL1Frame, SPAssembleQualityData])
78
+ l0_pipeline.add_node(task=TransferL1Data, upstreams=[SPWriteL1Frame, AssembleCryonirspMovie])
79
+ l0_pipeline.add_node(
80
+ task=PublishCatalogAndQualityMessages, upstreams=[SubmitDatasetMetadata, TransferL1Data]
81
+ )
82
+
83
+ # goodbye
84
+ l0_pipeline.add_node(task=Teardown, upstreams=PublishCatalogAndQualityMessages)