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,319 @@
1
+ import json
2
+
3
+ import numpy as np
4
+ import pytest
5
+ from dkist_processing_common._util.scratch import WorkflowFileSystem
6
+ from dkist_processing_common.codecs.fits import fits_hdulist_encoder
7
+ from dkist_processing_common.tests.conftest import FakeGQLClient
8
+ from dkist_processing_math import transform
9
+
10
+ from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
11
+ from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
12
+ from dkist_processing_cryonirsp.models.tags import CryonirspTag
13
+ from dkist_processing_cryonirsp.tasks.sp_geometric import SPGeometricCalibration
14
+ from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
15
+ from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
16
+ from dkist_processing_cryonirsp.tests.conftest import generate_214_l0_fits_frame
17
+ from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidSPSolarGainFrames
18
+
19
+
20
+ @pytest.fixture(scope="function")
21
+ def geometric_calibration_task_that_completes(
22
+ tmp_path, recipe_run_id, assign_input_dataset_doc_to_task, init_cryonirsp_constants_db
23
+ ):
24
+ # This fixture makes data that look enough like real data that all of the feature detection stuff at least runs
25
+ # through (mostly this is an issue for the angle calculation). It would be great to contrive data that
26
+ # produce a geometric calibration with real numbers that can be checked, but for now we'll rely on the grogu
27
+ # tests for that. In other words, this fixture just tests if the machinery of the task completes and some object
28
+ # (ANY object) is written correctly.
29
+ number_of_modstates = 1
30
+ number_of_beams = 2
31
+ exposure_time = 20.0 # From CryonirspHeadersValidSolarGainFrames fixture
32
+ exposure_conditions = ExposureConditions(
33
+ exposure_time, AllowableOpticalDensityFilterNames.OPEN.value
34
+ )
35
+ data_shape_int = 100, 98
36
+ data_shape_raw = 100, 196
37
+ dataset_shape = 1, *data_shape_raw
38
+ constants_db = CryonirspConstantsDb(
39
+ NUM_MODSTATES=number_of_modstates,
40
+ SOLAR_GAIN_EXPOSURE_CONDITIONS_LIST=(exposure_conditions,),
41
+ ARM_ID="SP",
42
+ )
43
+ init_cryonirsp_constants_db(recipe_run_id, constants_db)
44
+ with SPGeometricCalibration(
45
+ recipe_run_id=recipe_run_id,
46
+ workflow_name="sp_geometric_calibration",
47
+ workflow_version="VX.Y",
48
+ ) as task:
49
+ try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
50
+ task.scratch = WorkflowFileSystem(
51
+ scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
52
+ )
53
+ param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
54
+ assign_input_dataset_doc_to_task(task, param_class())
55
+
56
+ task.angles = [0.0, 0.0]
57
+ task.offsets = np.zeros((number_of_beams, 2))
58
+ task.shifts = np.zeros(data_shape_int[0])
59
+
60
+ # Create the intermediate frames needed
61
+ # Create fake bad pixel map
62
+ task.intermediate_frame_write_arrays(
63
+ arrays=np.zeros(data_shape_raw),
64
+ task_tag=CryonirspTag.task_bad_pixel_map(),
65
+ )
66
+ for beam in range(1, number_of_beams + 1):
67
+ dark_cal = np.ones(data_shape_int) * 3.0
68
+ task.intermediate_frame_write_arrays(
69
+ arrays=dark_cal,
70
+ beam=beam,
71
+ task_tag=CryonirspTag.task_dark(),
72
+ exposure_conditions=exposure_conditions,
73
+ )
74
+
75
+ # Need a lamp for each beam
76
+ lamp_gain = np.ones(data_shape_int)
77
+ task.intermediate_frame_write_arrays(
78
+ arrays=lamp_gain,
79
+ beam=beam,
80
+ task_tag=CryonirspTag.task_lamp_gain(),
81
+ exposure_conditions=exposure_conditions,
82
+ )
83
+
84
+ # And a beam border intermediate array
85
+ border = data_shape_raw[1] // 2
86
+ task.intermediate_frame_write_arrays(
87
+ arrays=np.array(
88
+ [
89
+ 0,
90
+ data_shape_int[0],
91
+ ((beam - 1) * border),
92
+ (border + (beam - 1) * border),
93
+ ]
94
+ ),
95
+ task_tag=CryonirspTag.task_beam_boundaries(),
96
+ beam=beam,
97
+ )
98
+
99
+ # Create the raw data, which is based on two beams per frame
100
+ beam1 = 1
101
+ beam2 = 2
102
+ ds = CryonirspHeadersValidSPSolarGainFrames(
103
+ dataset_shape=dataset_shape,
104
+ array_shape=dataset_shape,
105
+ time_delta=10,
106
+ )
107
+ header = ds.header()
108
+ # Create each beam of the solar gain image, rotate and translate them, and then combine them into one
109
+ true_solar_1 = np.ones(data_shape_int)
110
+ true_solar_1[data_shape_int[0] // 2, :] += 5
111
+ true_solar_1[:, data_shape_int[1] // 2] += 5
112
+ true_solar_2 = np.copy(true_solar_1)
113
+ # Now add the beam number to each beam in the array
114
+ true_solar_1 += beam1
115
+ true_solar_2 += beam2
116
+ translated_solar_1 = next(
117
+ transform.translate_arrays(arrays=true_solar_1, translation=task.offsets[0])
118
+ )
119
+ translated_solar_2 = next(
120
+ transform.translate_arrays(arrays=true_solar_2, translation=task.offsets[1])
121
+ )
122
+ distorted_solar_1 = next(
123
+ transform.rotate_arrays_about_point(arrays=translated_solar_1, angle=task.angles[0])
124
+ )
125
+ distorted_solar_2 = next(
126
+ transform.rotate_arrays_about_point(arrays=translated_solar_2, angle=task.angles[1])
127
+ )
128
+
129
+ raw_solar_1 = distorted_solar_1 + dark_cal
130
+ raw_solar_2 = distorted_solar_2 + dark_cal
131
+ raw_solar = np.concatenate((raw_solar_1, raw_solar_2), axis=1)
132
+ solar_hdul = generate_214_l0_fits_frame(data=raw_solar, s122_header=header)
133
+ task.write(
134
+ data=solar_hdul,
135
+ tags=[
136
+ CryonirspTag.linearized(),
137
+ CryonirspTag.task_solar_gain(),
138
+ CryonirspTag.frame(),
139
+ CryonirspTag.beam(beam),
140
+ CryonirspTag.exposure_conditions(exposure_conditions),
141
+ ],
142
+ encoder=fits_hdulist_encoder,
143
+ )
144
+
145
+ yield task, data_shape_int
146
+ finally:
147
+ task._purge()
148
+
149
+
150
+ @pytest.fixture(scope="function")
151
+ def geometric_calibration_task_with_simple_raw_data(
152
+ tmp_path, recipe_run_id, assign_input_dataset_doc_to_task, init_cryonirsp_constants_db
153
+ ):
154
+ number_of_modstates = 1
155
+ number_of_beams = 2
156
+ exposure_time = 20.0 # From CryonirspHeadersValidSolarGainFrames fixture
157
+ exposure_conditions = ExposureConditions(
158
+ exposure_time, AllowableOpticalDensityFilterNames.OPEN.value
159
+ )
160
+ data_shape_int = 100, 98
161
+ data_shape_raw = 100, 196
162
+ constants_db = CryonirspConstantsDb(
163
+ NUM_MODSTATES=number_of_modstates,
164
+ SOLAR_GAIN_EXPOSURE_CONDITIONS_LIST=(exposure_conditions,),
165
+ ARM_ID="SP",
166
+ )
167
+ init_cryonirsp_constants_db(recipe_run_id, constants_db)
168
+ with SPGeometricCalibration(
169
+ recipe_run_id=recipe_run_id,
170
+ workflow_name="sp_geometric_calibration",
171
+ workflow_version="VX.Y",
172
+ ) as task:
173
+ try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
174
+ task.scratch = WorkflowFileSystem(
175
+ scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
176
+ )
177
+ param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
178
+ assign_input_dataset_doc_to_task(task, param_class())
179
+
180
+ # Create the intermediate frames needed
181
+ # Create a fake bad pixel map
182
+ task.intermediate_frame_write_arrays(
183
+ arrays=np.zeros(data_shape_raw),
184
+ task_tag=CryonirspTag.task_bad_pixel_map(),
185
+ )
186
+ for beam in range(1, number_of_beams + 1):
187
+ dark_cal = np.ones(data_shape_int) * 3.0
188
+ task.intermediate_frame_write_arrays(
189
+ arrays=dark_cal,
190
+ beam=beam,
191
+ task_tag=CryonirspTag.task_dark(),
192
+ exposure_conditions=exposure_conditions,
193
+ )
194
+
195
+ # Need a lamp for each beam
196
+ lamp_gain = np.ones(data_shape_int)
197
+ task.intermediate_frame_write_arrays(
198
+ arrays=lamp_gain,
199
+ beam=beam,
200
+ task_tag=CryonirspTag.task_lamp_gain(),
201
+ exposure_conditions=exposure_conditions,
202
+ )
203
+
204
+ # And a beam border intermediate array
205
+ border = data_shape_raw[1] // 2
206
+ task.intermediate_frame_write_arrays(
207
+ arrays=np.array(
208
+ [
209
+ 0,
210
+ data_shape_int[0],
211
+ ((beam - 1) * border),
212
+ (border + (beam - 1) * border),
213
+ ]
214
+ ),
215
+ task_tag=CryonirspTag.task_beam_boundaries(),
216
+ beam=beam,
217
+ )
218
+
219
+ # Let's write a dark with the wrong exposure time, just to make sure it doesn't get used
220
+ task.intermediate_frame_write_arrays(
221
+ arrays=np.ones(data_shape_int) * 1e6,
222
+ beam=beam,
223
+ task_tag=CryonirspTag.task_dark(),
224
+ exposure_conditions=ExposureConditions(
225
+ exposure_time**2, AllowableOpticalDensityFilterNames.OPEN.value
226
+ ),
227
+ )
228
+
229
+ # Create the raw data, which is based on two beams per frame
230
+ beam1 = 1
231
+ beam2 = 2
232
+ dark_cal_two_beams = np.concatenate((dark_cal, dark_cal), axis=1)
233
+ ds = CryonirspHeadersValidSPSolarGainFrames(
234
+ dataset_shape=(1,) + data_shape_raw,
235
+ array_shape=(1,) + data_shape_raw,
236
+ time_delta=10,
237
+ )
238
+ header = ds.header()
239
+ true_solar = np.ones(data_shape_raw)
240
+ # Now add the beam number to each beam in the array
241
+ true_solar[:, : data_shape_int[1]] += beam1
242
+ true_solar[:, data_shape_int[1] :] += beam2
243
+ raw_solar = true_solar + dark_cal_two_beams
244
+ solar_hdul = generate_214_l0_fits_frame(data=raw_solar, s122_header=header)
245
+ task.write(
246
+ data=solar_hdul,
247
+ tags=[
248
+ CryonirspTag.linearized(),
249
+ CryonirspTag.task_solar_gain(),
250
+ CryonirspTag.frame(),
251
+ CryonirspTag.exposure_conditions(exposure_conditions),
252
+ ],
253
+ encoder=fits_hdulist_encoder,
254
+ )
255
+
256
+ yield task, data_shape_int, true_solar
257
+ finally:
258
+ task._purge()
259
+
260
+
261
+ def test_geometric_task(geometric_calibration_task_that_completes, mocker):
262
+ """
263
+ Given: A set of raw solar gain images and necessary intermediate calibrations
264
+ When: Running the geometric task
265
+ Then: The damn thing runs and makes outputs that at least are the right type
266
+ """
267
+ # See the note in the fixture above: this test does NOT test for accuracy of the calibration
268
+ mocker.patch(
269
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
270
+ )
271
+ task, beam_shape = geometric_calibration_task_that_completes
272
+ task()
273
+ for beam in range(1, task.constants.num_beams + 1):
274
+ assert type(task.intermediate_frame_load_angle(beam=beam)) is float
275
+ assert task.intermediate_frame_load_spec_shift(beam=beam).shape[0] == beam_shape[0]
276
+ assert task.intermediate_frame_load_state_offset(beam=beam).shape == (2,)
277
+
278
+ quality_files = task.read(tags=[CryonirspTag.quality("TASK_TYPES")])
279
+ for file in quality_files:
280
+ with file.open() as f:
281
+ data = json.load(f)
282
+ assert isinstance(data, dict)
283
+ assert data["total_frames"] == task.scratch.count_all(
284
+ tags=[
285
+ CryonirspTag.linearized(),
286
+ CryonirspTag.frame(),
287
+ CryonirspTag.task_solar_gain(),
288
+ ]
289
+ )
290
+
291
+
292
+ def test_basic_corrections(geometric_calibration_task_with_simple_raw_data):
293
+ """
294
+ Given: A set of raw solar gain images and necessary intermediate calibrations
295
+ When: Doing basic dark and lamp gain corrections
296
+ Then: The corrections are applied correctly
297
+ """
298
+ task, beam_shape, true_solar = geometric_calibration_task_with_simple_raw_data
299
+ flipped_true_solar = np.flip(true_solar, axis=1)
300
+ task.do_basic_corrections()
301
+
302
+ # Positive test on the flipped true solar image
303
+ for beam in range(1, task.constants.num_beams + 1):
304
+ if beam == 1:
305
+ expected = flipped_true_solar[:, 0 : beam_shape[1]]
306
+ else:
307
+ expected = flipped_true_solar[:, beam_shape[1] :]
308
+ array = task.basic_dark_bp_corrected_data(beam=beam)
309
+ np.testing.assert_equal(expected, array)
310
+
311
+ # Negative test on the original true solar image
312
+ for beam in range(1, task.constants.num_beams + 1):
313
+ if beam == 1:
314
+ expected = true_solar[:, 0 : beam_shape[1]]
315
+ else:
316
+ expected = true_solar[:, beam_shape[1] :]
317
+ array = task.basic_dark_bp_corrected_data(beam=beam)
318
+ with pytest.raises(AssertionError):
319
+ np.testing.assert_equal(expected, array)
@@ -0,0 +1,121 @@
1
+ from datetime import datetime
2
+
3
+ import numpy as np
4
+ import pytest
5
+ from astropy.io import fits
6
+ from dkist_header_validator import spec122_validator
7
+ from dkist_processing_common._util.scratch import WorkflowFileSystem
8
+ from dkist_processing_common.codecs.fits import fits_hdulist_encoder
9
+ from dkist_processing_common.tests.conftest import FakeGQLClient
10
+
11
+ from dkist_processing_cryonirsp.models.tags import CryonirspTag
12
+ from dkist_processing_cryonirsp.tasks.make_movie_frames import SPMakeCryonirspMovieFrames
13
+ from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
14
+ from dkist_processing_cryonirsp.tests.conftest import generate_fits_frame
15
+ from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidObserveFrames
16
+
17
+
18
+ @pytest.fixture(
19
+ scope="function",
20
+ params=[
21
+ pytest.param((True, 2), id="polarimetric"),
22
+ pytest.param((False, 2), id="intensity-only"),
23
+ pytest.param((False, 1), id="single_scan_step"),
24
+ ],
25
+ )
26
+ def sp_movie_frames_task(tmp_path, recipe_run_id, init_cryonirsp_constants_db, request):
27
+ is_polarimetric, scan_steps = request.param
28
+ map_scans = 3
29
+ array_shape = (3, 4)
30
+ if is_polarimetric:
31
+ num_mod = 8
32
+ spin_mode = "Continuous"
33
+ else:
34
+ num_mod = 1
35
+ spin_mode = "None"
36
+ constants_db = CryonirspConstantsDb(
37
+ NUM_MODSTATES=num_mod,
38
+ MODULATOR_SPIN_MODE=spin_mode,
39
+ NUM_SCAN_STEPS=scan_steps,
40
+ NUM_MAP_SCANS=map_scans,
41
+ TIME_OBS_LIST=(datetime.now().isoformat("T"),),
42
+ )
43
+ init_cryonirsp_constants_db(recipe_run_id, constants_db)
44
+ with SPMakeCryonirspMovieFrames(
45
+ recipe_run_id=recipe_run_id, workflow_name="make_movie_frames", workflow_version="VX.Y"
46
+ ) as task:
47
+ try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
48
+ meas_num = 1 # Use only the first measurement if there are multiple measurements.
49
+ task.scratch = WorkflowFileSystem(
50
+ scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
51
+ )
52
+ start_time = datetime.now()
53
+ for stokes_state in ["I", "Q", "U", "V"]:
54
+ for map_scan in range(1, map_scans + 1):
55
+ for scan_step in range(0, scan_steps + 1):
56
+ ds = CryonirspHeadersValidObserveFrames(
57
+ dataset_shape=(2, *array_shape),
58
+ array_shape=(1, *array_shape),
59
+ time_delta=10,
60
+ num_map_scans=map_scans,
61
+ map_scan=map_scan,
62
+ num_scan_steps=scan_steps,
63
+ scan_step=scan_step,
64
+ num_modstates=1,
65
+ modstate=1,
66
+ start_time=start_time,
67
+ num_meas=1,
68
+ meas_num=1,
69
+ arm_id="SP",
70
+ )
71
+ header_generator = (
72
+ spec122_validator.validate_and_translate_to_214_l0(
73
+ d.header(), return_type=fits.HDUList
74
+ )[0].header
75
+ for d in ds
76
+ )
77
+ hdul = generate_fits_frame(
78
+ header_generator=header_generator,
79
+ shape=(1, *array_shape),
80
+ )
81
+ task.write(
82
+ data=hdul,
83
+ tags=[
84
+ CryonirspTag.calibrated(),
85
+ CryonirspTag.frame(),
86
+ CryonirspTag.map_scan(map_scan),
87
+ CryonirspTag.scan_step(scan_step),
88
+ CryonirspTag.stokes(stokes_state),
89
+ CryonirspTag.meas_num(meas_num),
90
+ ],
91
+ encoder=fits_hdulist_encoder,
92
+ )
93
+ yield task, map_scans, scan_steps, array_shape, is_polarimetric
94
+ finally:
95
+ task._purge()
96
+
97
+
98
+ def test_sp_make_movie_frames(sp_movie_frames_task, mocker):
99
+ """
100
+ Given: A SPMakeCryonirspMovieFrames task
101
+ When: Calling the task instance
102
+ Then: a fits file is made for each scan containing the movie frame for that scan
103
+ """
104
+ mocker.patch(
105
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
106
+ )
107
+ task, map_scans, scan_steps, array_shape, is_polarimetric = sp_movie_frames_task
108
+ if scan_steps > 1:
109
+ expected_shape = (scan_steps, array_shape[0])
110
+ else:
111
+ expected_shape = array_shape
112
+ if is_polarimetric:
113
+ expected_shape = tuple(np.array(expected_shape) * 2)
114
+ task()
115
+ assert len(list(task.read(tags=[CryonirspTag.movie_frame()]))) == map_scans
116
+ for filepath in task.read(tags=[CryonirspTag.movie_frame()]):
117
+ assert filepath.exists()
118
+ hdul = fits.open(filepath)
119
+ assert hdul[0].header["INSTRUME"] == "CRYO-NIRSP"
120
+ # Multiply by 2 because a single map is (axis_length, steps) but there are 4 stokes in a 2x2 array
121
+ assert hdul[0].data.shape == expected_shape