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.
- changelog/.gitempty +0 -0
- dkist_processing_cryonirsp/__init__.py +11 -0
- dkist_processing_cryonirsp/config.py +12 -0
- dkist_processing_cryonirsp/models/__init__.py +1 -0
- dkist_processing_cryonirsp/models/constants.py +248 -0
- dkist_processing_cryonirsp/models/exposure_conditions.py +26 -0
- dkist_processing_cryonirsp/models/parameters.py +296 -0
- dkist_processing_cryonirsp/models/tags.py +168 -0
- dkist_processing_cryonirsp/models/task_name.py +14 -0
- dkist_processing_cryonirsp/parsers/__init__.py +1 -0
- dkist_processing_cryonirsp/parsers/cryonirsp_l0_fits_access.py +111 -0
- dkist_processing_cryonirsp/parsers/cryonirsp_l1_fits_access.py +30 -0
- dkist_processing_cryonirsp/parsers/exposure_conditions.py +163 -0
- dkist_processing_cryonirsp/parsers/map_repeats.py +40 -0
- dkist_processing_cryonirsp/parsers/measurements.py +55 -0
- dkist_processing_cryonirsp/parsers/modstates.py +31 -0
- dkist_processing_cryonirsp/parsers/optical_density_filters.py +40 -0
- dkist_processing_cryonirsp/parsers/polarimetric_check.py +120 -0
- dkist_processing_cryonirsp/parsers/scan_step.py +412 -0
- dkist_processing_cryonirsp/parsers/time.py +80 -0
- dkist_processing_cryonirsp/parsers/wavelength.py +26 -0
- dkist_processing_cryonirsp/tasks/__init__.py +19 -0
- dkist_processing_cryonirsp/tasks/assemble_movie.py +202 -0
- dkist_processing_cryonirsp/tasks/bad_pixel_map.py +96 -0
- dkist_processing_cryonirsp/tasks/beam_boundaries_base.py +279 -0
- dkist_processing_cryonirsp/tasks/ci_beam_boundaries.py +55 -0
- dkist_processing_cryonirsp/tasks/ci_science.py +169 -0
- dkist_processing_cryonirsp/tasks/cryonirsp_base.py +67 -0
- dkist_processing_cryonirsp/tasks/dark.py +98 -0
- dkist_processing_cryonirsp/tasks/gain.py +251 -0
- dkist_processing_cryonirsp/tasks/instrument_polarization.py +447 -0
- dkist_processing_cryonirsp/tasks/l1_output_data.py +44 -0
- dkist_processing_cryonirsp/tasks/linearity_correction.py +582 -0
- dkist_processing_cryonirsp/tasks/make_movie_frames.py +302 -0
- dkist_processing_cryonirsp/tasks/mixin/__init__.py +1 -0
- dkist_processing_cryonirsp/tasks/mixin/beam_access.py +52 -0
- dkist_processing_cryonirsp/tasks/mixin/corrections.py +177 -0
- dkist_processing_cryonirsp/tasks/mixin/intermediate_frame.py +193 -0
- dkist_processing_cryonirsp/tasks/mixin/linearized_frame.py +309 -0
- dkist_processing_cryonirsp/tasks/mixin/shift_measurements.py +297 -0
- dkist_processing_cryonirsp/tasks/parse.py +281 -0
- dkist_processing_cryonirsp/tasks/quality_metrics.py +271 -0
- dkist_processing_cryonirsp/tasks/science_base.py +511 -0
- dkist_processing_cryonirsp/tasks/sp_beam_boundaries.py +270 -0
- dkist_processing_cryonirsp/tasks/sp_dispersion_axis_correction.py +484 -0
- dkist_processing_cryonirsp/tasks/sp_geometric.py +585 -0
- dkist_processing_cryonirsp/tasks/sp_science.py +299 -0
- dkist_processing_cryonirsp/tasks/sp_solar_gain.py +475 -0
- dkist_processing_cryonirsp/tasks/trial_output_data.py +61 -0
- dkist_processing_cryonirsp/tasks/write_l1.py +1033 -0
- dkist_processing_cryonirsp/tests/__init__.py +1 -0
- dkist_processing_cryonirsp/tests/conftest.py +456 -0
- dkist_processing_cryonirsp/tests/header_models.py +592 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/__init__.py +0 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/l0_cals_only.py +541 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/l0_to_l1.py +615 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/linearize_only.py +96 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/local_trial_helpers.py +592 -0
- dkist_processing_cryonirsp/tests/test_assemble_movie.py +144 -0
- dkist_processing_cryonirsp/tests/test_assemble_qualilty.py +517 -0
- dkist_processing_cryonirsp/tests/test_bad_pixel_maps.py +115 -0
- dkist_processing_cryonirsp/tests/test_ci_beam_boundaries.py +106 -0
- dkist_processing_cryonirsp/tests/test_ci_science.py +355 -0
- dkist_processing_cryonirsp/tests/test_corrections.py +126 -0
- dkist_processing_cryonirsp/tests/test_cryo_base.py +202 -0
- dkist_processing_cryonirsp/tests/test_cryo_constants.py +76 -0
- dkist_processing_cryonirsp/tests/test_dark.py +287 -0
- dkist_processing_cryonirsp/tests/test_gain.py +278 -0
- dkist_processing_cryonirsp/tests/test_instrument_polarization.py +531 -0
- dkist_processing_cryonirsp/tests/test_linearity_correction.py +245 -0
- dkist_processing_cryonirsp/tests/test_make_movie_frames.py +111 -0
- dkist_processing_cryonirsp/tests/test_parameters.py +266 -0
- dkist_processing_cryonirsp/tests/test_parse.py +1439 -0
- dkist_processing_cryonirsp/tests/test_quality.py +203 -0
- dkist_processing_cryonirsp/tests/test_sp_beam_boundaries.py +112 -0
- dkist_processing_cryonirsp/tests/test_sp_dispersion_axis_correction.py +155 -0
- dkist_processing_cryonirsp/tests/test_sp_geometric.py +319 -0
- dkist_processing_cryonirsp/tests/test_sp_make_movie_frames.py +121 -0
- dkist_processing_cryonirsp/tests/test_sp_science.py +483 -0
- dkist_processing_cryonirsp/tests/test_sp_solar.py +198 -0
- dkist_processing_cryonirsp/tests/test_trial_create_quality_report.py +79 -0
- dkist_processing_cryonirsp/tests/test_trial_output_data.py +251 -0
- dkist_processing_cryonirsp/tests/test_workflows.py +9 -0
- dkist_processing_cryonirsp/tests/test_write_l1.py +436 -0
- dkist_processing_cryonirsp/workflows/__init__.py +2 -0
- dkist_processing_cryonirsp/workflows/ci_l0_processing.py +77 -0
- dkist_processing_cryonirsp/workflows/sp_l0_processing.py +84 -0
- dkist_processing_cryonirsp/workflows/trial_workflows.py +190 -0
- dkist_processing_cryonirsp-1.3.4.dist-info/METADATA +194 -0
- dkist_processing_cryonirsp-1.3.4.dist-info/RECORD +111 -0
- dkist_processing_cryonirsp-1.3.4.dist-info/WHEEL +5 -0
- dkist_processing_cryonirsp-1.3.4.dist-info/top_level.txt +4 -0
- docs/Makefile +134 -0
- docs/bad_pixel_calibration.rst +47 -0
- docs/beam_angle_calculation.rst +53 -0
- docs/beam_boundary_computation.rst +88 -0
- docs/changelog.rst +7 -0
- docs/ci_science_calibration.rst +33 -0
- docs/conf.py +52 -0
- docs/index.rst +21 -0
- docs/l0_to_l1_cryonirsp_ci-full-trial.rst +10 -0
- docs/l0_to_l1_cryonirsp_ci.rst +10 -0
- docs/l0_to_l1_cryonirsp_sp-full-trial.rst +10 -0
- docs/l0_to_l1_cryonirsp_sp.rst +10 -0
- docs/linearization.rst +43 -0
- docs/make.bat +170 -0
- docs/requirements.txt +1 -0
- docs/requirements_table.rst +8 -0
- docs/scientific_changelog.rst +10 -0
- docs/sp_science_calibration.rst +59 -0
- licenses/LICENSE.rst +11 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import random
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pytest
|
|
7
|
+
from astropy.io import fits
|
|
8
|
+
from astropy.time import Time
|
|
9
|
+
from astropy.time import TimeDelta
|
|
10
|
+
from dkist_header_validator import spec122_validator
|
|
11
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
12
|
+
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
13
|
+
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
14
|
+
|
|
15
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
|
|
16
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
17
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspStemName
|
|
18
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
19
|
+
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
|
|
20
|
+
from dkist_processing_cryonirsp.tasks.sp_science import CalibrationCollection
|
|
21
|
+
from dkist_processing_cryonirsp.tasks.sp_science import SPScienceCalibration
|
|
22
|
+
from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
|
|
23
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
24
|
+
from dkist_processing_cryonirsp.tests.conftest import generate_fits_frame
|
|
25
|
+
from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidObserveFrames
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture(scope="function", params=["Full Stokes", "Stokes-I"])
|
|
29
|
+
def sp_science_calibration_task(
|
|
30
|
+
tmp_path,
|
|
31
|
+
recipe_run_id,
|
|
32
|
+
assign_input_dataset_doc_to_task,
|
|
33
|
+
init_cryonirsp_constants_db,
|
|
34
|
+
request,
|
|
35
|
+
):
|
|
36
|
+
num_map_scans = 2
|
|
37
|
+
num_beams = 2
|
|
38
|
+
num_scan_steps = 2
|
|
39
|
+
exposure_time = 0.02 # From CryonirspHeadersValidObserveFrames fixture
|
|
40
|
+
exposure_conditions = ExposureConditions(
|
|
41
|
+
exposure_time, AllowableOpticalDensityFilterNames.OPEN.value
|
|
42
|
+
)
|
|
43
|
+
if request.param == "Full Stokes":
|
|
44
|
+
num_modstates = 2
|
|
45
|
+
else:
|
|
46
|
+
num_modstates = 1
|
|
47
|
+
array_shape = (1, 30, 60)
|
|
48
|
+
intermediate_shape = (30, 30)
|
|
49
|
+
dataset_shape = (num_beams * num_map_scans * num_scan_steps * num_modstates,) + array_shape[1:]
|
|
50
|
+
|
|
51
|
+
constants_db = CryonirspConstantsDb(
|
|
52
|
+
NUM_MODSTATES=num_modstates,
|
|
53
|
+
NUM_MAP_SCANS=num_map_scans,
|
|
54
|
+
NUM_SCAN_STEPS=num_scan_steps,
|
|
55
|
+
NUM_BEAMS=num_beams,
|
|
56
|
+
OBSERVE_EXPOSURE_CONDITIONS_LIST=(exposure_conditions,),
|
|
57
|
+
MODULATOR_SPIN_MODE="Continuous" if request.param == "Full Stokes" else "Off",
|
|
58
|
+
)
|
|
59
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
60
|
+
with SPScienceCalibration(
|
|
61
|
+
recipe_run_id=recipe_run_id, workflow_name="sp_science_calibration", workflow_version="VX.Y"
|
|
62
|
+
) as task:
|
|
63
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
64
|
+
all_zeros = np.zeros(intermediate_shape)
|
|
65
|
+
all_ones = np.ones(intermediate_shape)
|
|
66
|
+
task.scratch = WorkflowFileSystem(
|
|
67
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
68
|
+
)
|
|
69
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
70
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
71
|
+
# Create fake bad pixel map
|
|
72
|
+
task.intermediate_frame_write_arrays(
|
|
73
|
+
arrays=np.zeros((30, 60)),
|
|
74
|
+
task_tag=CryonirspTag.task_bad_pixel_map(),
|
|
75
|
+
)
|
|
76
|
+
# Create fake demodulation matrices
|
|
77
|
+
demod_matrices = np.zeros((1, 1, 4, num_modstates))
|
|
78
|
+
for modstate in range(num_modstates):
|
|
79
|
+
demod_matrices[0, 0, :, modstate] = [1, 2, 3, 4]
|
|
80
|
+
for beam in range(num_beams):
|
|
81
|
+
demod_hdul = fits.HDUList([fits.PrimaryHDU(data=demod_matrices)])
|
|
82
|
+
task.write(
|
|
83
|
+
data=demod_hdul,
|
|
84
|
+
tags=[
|
|
85
|
+
CryonirspTag.intermediate(),
|
|
86
|
+
CryonirspTag.frame(),
|
|
87
|
+
CryonirspTag.task_demodulation_matrices(),
|
|
88
|
+
CryonirspTag.beam(beam + 1),
|
|
89
|
+
],
|
|
90
|
+
encoder=fits_hdulist_encoder,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Create fake geometric objects
|
|
94
|
+
angle = np.array([0.0])
|
|
95
|
+
offset = np.array([-10.2, 5.1])
|
|
96
|
+
spec_shift = np.zeros(intermediate_shape[0])
|
|
97
|
+
for beam in range(1, num_beams + 1):
|
|
98
|
+
task.intermediate_frame_write_arrays(
|
|
99
|
+
arrays=angle, beam=beam, task_tag=CryonirspTag.task_geometric_angle()
|
|
100
|
+
)
|
|
101
|
+
task.intermediate_frame_write_arrays(
|
|
102
|
+
arrays=spec_shift,
|
|
103
|
+
beam=beam,
|
|
104
|
+
task_tag=CryonirspTag.task_geometric_sepectral_shifts(),
|
|
105
|
+
)
|
|
106
|
+
for modstate in range(1, num_modstates + 1):
|
|
107
|
+
task.intermediate_frame_write_arrays(
|
|
108
|
+
arrays=offset
|
|
109
|
+
* (beam - 1), # Because we need the fiducial array to have (0, 0) offset
|
|
110
|
+
beam=beam,
|
|
111
|
+
modstate=modstate,
|
|
112
|
+
task_tag=CryonirspTag.task_geometric_offset(),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Create fake dark intermediate arrays
|
|
116
|
+
for beam in range(1, num_beams + 1):
|
|
117
|
+
task.intermediate_frame_write_arrays(
|
|
118
|
+
all_zeros,
|
|
119
|
+
beam=beam,
|
|
120
|
+
task_tag=CryonirspTag.task_dark(),
|
|
121
|
+
exposure_conditions=exposure_conditions,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# And a beam border intermediate array
|
|
125
|
+
for beam in range(1, num_beams + 1):
|
|
126
|
+
task.intermediate_frame_write_arrays(
|
|
127
|
+
arrays=np.array([0, 30, ((beam - 1) * 30), (30 + (beam - 1) * 30)]),
|
|
128
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
129
|
+
beam=beam,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Create fake lamp and solar gain intermediate arrays
|
|
133
|
+
for beam in range(1, num_beams + 1):
|
|
134
|
+
for modstate in range(1, num_modstates + 1):
|
|
135
|
+
gain_hdul = fits.HDUList([fits.PrimaryHDU(data=all_ones)])
|
|
136
|
+
task.write(
|
|
137
|
+
data=gain_hdul,
|
|
138
|
+
tags=[
|
|
139
|
+
CryonirspTag.intermediate(),
|
|
140
|
+
CryonirspTag.frame(),
|
|
141
|
+
CryonirspTag.task_lamp_gain(),
|
|
142
|
+
CryonirspTag.beam(beam),
|
|
143
|
+
CryonirspTag.modstate(modstate),
|
|
144
|
+
],
|
|
145
|
+
encoder=fits_hdulist_encoder,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
task.write(
|
|
149
|
+
data=gain_hdul,
|
|
150
|
+
tags=[
|
|
151
|
+
CryonirspTag.intermediate(),
|
|
152
|
+
CryonirspTag.frame(),
|
|
153
|
+
CryonirspTag.task_solar_gain(),
|
|
154
|
+
CryonirspTag.beam(beam),
|
|
155
|
+
CryonirspTag.modstate(modstate),
|
|
156
|
+
],
|
|
157
|
+
encoder=fits_hdulist_encoder,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Create fake observe arrays
|
|
161
|
+
start_time = datetime.now()
|
|
162
|
+
for map_scan in range(1, num_map_scans + 1):
|
|
163
|
+
for scan_step in range(1, num_scan_steps + 1):
|
|
164
|
+
for modstate in range(1, num_modstates + 1):
|
|
165
|
+
ds = CryonirspHeadersValidObserveFrames(
|
|
166
|
+
dataset_shape=dataset_shape,
|
|
167
|
+
array_shape=array_shape,
|
|
168
|
+
time_delta=10,
|
|
169
|
+
scan_step=scan_step,
|
|
170
|
+
num_scan_steps=num_scan_steps,
|
|
171
|
+
num_map_scans=num_map_scans,
|
|
172
|
+
map_scan=map_scan,
|
|
173
|
+
num_modstates=num_modstates,
|
|
174
|
+
modstate=modstate,
|
|
175
|
+
start_time=start_time,
|
|
176
|
+
num_meas=1,
|
|
177
|
+
meas_num=1,
|
|
178
|
+
arm_id="SP",
|
|
179
|
+
)
|
|
180
|
+
header_generator = (
|
|
181
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
182
|
+
d.header(), return_type=fits.HDUList
|
|
183
|
+
)[0].header
|
|
184
|
+
for d in ds
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
hdul = generate_fits_frame(
|
|
188
|
+
header_generator=header_generator, shape=array_shape
|
|
189
|
+
)
|
|
190
|
+
header = hdul[0].header
|
|
191
|
+
task.write(
|
|
192
|
+
data=hdul,
|
|
193
|
+
tags=[
|
|
194
|
+
CryonirspTag.task_observe(),
|
|
195
|
+
CryonirspTag.meas_num(1),
|
|
196
|
+
CryonirspTag.scan_step(scan_step),
|
|
197
|
+
CryonirspTag.map_scan(map_scan),
|
|
198
|
+
CryonirspTag.modstate(modstate),
|
|
199
|
+
CryonirspTag.linearized(),
|
|
200
|
+
CryonirspTag.frame(),
|
|
201
|
+
CryonirspTag.exposure_conditions(exposure_conditions),
|
|
202
|
+
],
|
|
203
|
+
encoder=fits_hdulist_encoder,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
yield task, request.param, offset, header, intermediate_shape
|
|
207
|
+
finally:
|
|
208
|
+
task._purge()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@pytest.fixture(scope="function")
|
|
212
|
+
def sp_science_calibration_task_no_data(
|
|
213
|
+
tmp_path, recipe_run_id, init_cryonirsp_constants_db, is_polarimetric
|
|
214
|
+
):
|
|
215
|
+
constants_db = CryonirspConstantsDb(
|
|
216
|
+
NUM_MODSTATES=2 if is_polarimetric else 1,
|
|
217
|
+
MODULATOR_SPIN_MODE="Continuous" if is_polarimetric else "Off",
|
|
218
|
+
)
|
|
219
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
220
|
+
with SPScienceCalibration(
|
|
221
|
+
recipe_run_id=recipe_run_id, workflow_name="sp_science_calibration", workflow_version="VX.Y"
|
|
222
|
+
) as task:
|
|
223
|
+
try:
|
|
224
|
+
yield task
|
|
225
|
+
except:
|
|
226
|
+
raise
|
|
227
|
+
finally:
|
|
228
|
+
task._purge()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@pytest.fixture(scope="session")
|
|
232
|
+
def sp_headers_with_dates() -> tuple[list[fits.Header], str, int, int]:
|
|
233
|
+
num_headers = 5
|
|
234
|
+
start_time = "1969-12-06T18:00:00"
|
|
235
|
+
exp_time = 12
|
|
236
|
+
time_delta = 10
|
|
237
|
+
ds = CryonirspHeadersValidObserveFrames(
|
|
238
|
+
dataset_shape=(num_headers, 4, 4),
|
|
239
|
+
array_shape=(1, 4, 4),
|
|
240
|
+
time_delta=time_delta,
|
|
241
|
+
num_map_scans=1,
|
|
242
|
+
map_scan=1,
|
|
243
|
+
num_scan_steps=1,
|
|
244
|
+
scan_step=1,
|
|
245
|
+
num_modstates=num_headers,
|
|
246
|
+
modstate=1,
|
|
247
|
+
start_time=datetime.fromisoformat(start_time),
|
|
248
|
+
num_meas=1,
|
|
249
|
+
meas_num=1,
|
|
250
|
+
arm_id="SP",
|
|
251
|
+
)
|
|
252
|
+
headers = [
|
|
253
|
+
spec122_validator.validate_and_translate_to_214_l0(d.header(), return_type=fits.HDUList)[
|
|
254
|
+
0
|
|
255
|
+
].header
|
|
256
|
+
for d in ds
|
|
257
|
+
]
|
|
258
|
+
random.shuffle(headers) # Shuffle to make sure they're not already in time order
|
|
259
|
+
for h in headers:
|
|
260
|
+
h["XPOSURE"] = exp_time # Exposure time, in ms
|
|
261
|
+
|
|
262
|
+
return headers, start_time, exp_time, time_delta
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@pytest.fixture(scope="session")
|
|
266
|
+
def sp_compressed_headers_with_dates(
|
|
267
|
+
sp_headers_with_dates,
|
|
268
|
+
) -> tuple[list[fits.Header], str, int, int]:
|
|
269
|
+
headers, start_time, exp_time, time_delta = sp_headers_with_dates
|
|
270
|
+
comp_headers = [fits.hdu.compressed.CompImageHeader(h, h) for h in headers]
|
|
271
|
+
return comp_headers, start_time, exp_time, time_delta
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@pytest.fixture(scope="function")
|
|
275
|
+
def sp_calibration_collection_with_geo_shifts(shifts) -> CalibrationCollection:
|
|
276
|
+
num_beams, num_mod, _ = shifts.shape
|
|
277
|
+
geo_shifts = {
|
|
278
|
+
str(b + 1): {f"m{m + 1}": shifts[b, m, :] for m in range(num_mod)} for b in range(num_beams)
|
|
279
|
+
}
|
|
280
|
+
return CalibrationCollection(
|
|
281
|
+
dark=dict(),
|
|
282
|
+
angle=dict(),
|
|
283
|
+
state_offset=geo_shifts,
|
|
284
|
+
spec_shift=dict(),
|
|
285
|
+
demod_matrices=None,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@pytest.fixture
|
|
290
|
+
def calibrated_array_and_header_dicts(
|
|
291
|
+
sp_headers_with_dates, is_polarimetric
|
|
292
|
+
) -> tuple[dict[str, np.ndarray], dict[str, fits.Header], tuple[int, int]]:
|
|
293
|
+
headers = sp_headers_with_dates[0]
|
|
294
|
+
header = headers[0]
|
|
295
|
+
|
|
296
|
+
num_stokes_params = 4 if is_polarimetric else 1
|
|
297
|
+
array_shape = (10, 10)
|
|
298
|
+
shape = (*array_shape, num_stokes_params)
|
|
299
|
+
|
|
300
|
+
beam1 = np.ones(shape) + np.arange(num_stokes_params)[None, None, :]
|
|
301
|
+
beam2 = np.ones(shape) + np.arange(num_stokes_params)[None, None, :] + 10
|
|
302
|
+
|
|
303
|
+
array_dict = {CryonirspTag.beam(1): beam1, CryonirspTag.beam(2): beam2}
|
|
304
|
+
header_dict = {CryonirspTag.beam(1): header, CryonirspTag.beam(2): header}
|
|
305
|
+
|
|
306
|
+
return array_dict, header_dict, array_shape
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def test_sp_science_calibration_task(sp_science_calibration_task, mocker):
|
|
310
|
+
"""
|
|
311
|
+
Given: A SPScienceCalibration task
|
|
312
|
+
When: Calling the task instance
|
|
313
|
+
Then: There are the expected number of science frames with the correct tags applied and the headers have been correctly updated
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
mocker.patch(
|
|
317
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# When
|
|
321
|
+
task, polarization_mode, offset, og_header, og_single_beam_shape = sp_science_calibration_task
|
|
322
|
+
task()
|
|
323
|
+
|
|
324
|
+
# 1 from re-dummification
|
|
325
|
+
expected_final_shape = (
|
|
326
|
+
1,
|
|
327
|
+
og_single_beam_shape[0],
|
|
328
|
+
og_single_beam_shape[1],
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Then
|
|
332
|
+
tags = [
|
|
333
|
+
CryonirspTag.calibrated(),
|
|
334
|
+
CryonirspTag.frame(),
|
|
335
|
+
]
|
|
336
|
+
files = list(task.read(tags=tags))
|
|
337
|
+
if polarization_mode == "Full Stokes":
|
|
338
|
+
# 2 raster steps * 2 map scans * 4 stokes params = 16 frames
|
|
339
|
+
assert len(files) == 16
|
|
340
|
+
elif polarization_mode == "Stokes-I":
|
|
341
|
+
# 2 raster steps * 2 map scans * 1 stokes param = 4 frames
|
|
342
|
+
assert len(files) == 4
|
|
343
|
+
for file in files:
|
|
344
|
+
hdul = fits.open(file)
|
|
345
|
+
assert len(hdul) == 1
|
|
346
|
+
hdu = hdul[0]
|
|
347
|
+
assert type(hdul[0]) is fits.PrimaryHDU
|
|
348
|
+
assert hdu.data.shape == expected_final_shape
|
|
349
|
+
assert "DATE-BEG" in hdu.header.keys()
|
|
350
|
+
assert "DATE-END" in hdu.header.keys()
|
|
351
|
+
if polarization_mode == "Full Stokes":
|
|
352
|
+
assert "POL_NOIS" in hdu.header.keys()
|
|
353
|
+
assert "POL_SENS" in hdu.header.keys()
|
|
354
|
+
|
|
355
|
+
# Check that scan step keys were updated
|
|
356
|
+
scan_step = [
|
|
357
|
+
int(t.split("_")[-1]) for t in task.tags(file) if CryonirspStemName.scan_step.value in t
|
|
358
|
+
][0]
|
|
359
|
+
|
|
360
|
+
assert hdu.header["CNNUMSCN"] == 2
|
|
361
|
+
assert hdu.header["CNCURSCN"] == scan_step
|
|
362
|
+
|
|
363
|
+
# Check that WCS keys were updated
|
|
364
|
+
if offset[0] > 0:
|
|
365
|
+
assert hdu.header["CRPIX2"] == og_header["CRPIX2"]
|
|
366
|
+
if offset[1] > 0:
|
|
367
|
+
assert hdu.header["CRPIX1"] == og_header["CRPIX1"]
|
|
368
|
+
|
|
369
|
+
quality_files = task.read(tags=[CryonirspTag.quality("TASK_TYPES")])
|
|
370
|
+
for file in quality_files:
|
|
371
|
+
with file.open() as f:
|
|
372
|
+
data = json.load(f)
|
|
373
|
+
assert isinstance(data, dict)
|
|
374
|
+
assert data["total_frames"] == task.scratch.count_all(
|
|
375
|
+
tags=[CryonirspTag.linearized(), CryonirspTag.frame(), CryonirspTag.task_observe()]
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def test_compute_sp_date_keys(sp_headers_with_dates, recipe_run_id, init_cryonirsp_constants_db):
|
|
380
|
+
"""
|
|
381
|
+
Given: A set of SP headers with different DATE-OBS values
|
|
382
|
+
When: Computing the time over which the headers were taken
|
|
383
|
+
Then: A header with correct DATE-BEG, DATE-END, and DATE-AVG keys is returned
|
|
384
|
+
"""
|
|
385
|
+
headers, start_time, exp_time, time_delta = sp_headers_with_dates
|
|
386
|
+
constants_db = CryonirspConstantsDb()
|
|
387
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
388
|
+
with SPScienceCalibration(
|
|
389
|
+
recipe_run_id=recipe_run_id, workflow_name="science_calibration", workflow_version="VX.Y"
|
|
390
|
+
) as task:
|
|
391
|
+
final_header = task.compute_date_keys(headers)
|
|
392
|
+
final_header_from_single = task.compute_date_keys(headers[0])
|
|
393
|
+
|
|
394
|
+
date_end = (
|
|
395
|
+
Time(start_time)
|
|
396
|
+
+ (len(headers) - 1) * TimeDelta(time_delta, format="sec")
|
|
397
|
+
+ TimeDelta(exp_time / 1000.0, format="sec")
|
|
398
|
+
).isot
|
|
399
|
+
|
|
400
|
+
assert final_header["DATE-BEG"] == start_time
|
|
401
|
+
assert final_header["DATE-END"] == date_end
|
|
402
|
+
|
|
403
|
+
date_end_from_single = (
|
|
404
|
+
Time(headers[0]["DATE-BEG"])
|
|
405
|
+
# + TimeDelta(time_delta, format="sec")
|
|
406
|
+
+ TimeDelta(exp_time / 1000.0, format="sec")
|
|
407
|
+
).isot
|
|
408
|
+
|
|
409
|
+
assert final_header_from_single["DATE-BEG"] == headers[0]["DATE-BEG"]
|
|
410
|
+
assert final_header_from_single["DATE-END"] == date_end_from_single
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def test_compute_sp_date_keys_compressed_headers(
|
|
414
|
+
sp_compressed_headers_with_dates, recipe_run_id, init_cryonirsp_constants_db
|
|
415
|
+
):
|
|
416
|
+
"""
|
|
417
|
+
Given: A set of SP compressed headers with different DATE-OBS values
|
|
418
|
+
When: Computing the time over which the headers were taken
|
|
419
|
+
Then: A header with correct DATE-BEG, DATE-END, and DATE-AVG keys is returned
|
|
420
|
+
"""
|
|
421
|
+
headers, start_time, exp_time, time_delta = sp_compressed_headers_with_dates
|
|
422
|
+
constants_db = CryonirspConstantsDb()
|
|
423
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
424
|
+
with SPScienceCalibration(
|
|
425
|
+
recipe_run_id=recipe_run_id, workflow_name="science_calibration", workflow_version="VX.Y"
|
|
426
|
+
) as task:
|
|
427
|
+
final_header = task.compute_date_keys(headers)
|
|
428
|
+
final_header_from_single = task.compute_date_keys(headers[0])
|
|
429
|
+
|
|
430
|
+
date_end = (
|
|
431
|
+
Time(start_time)
|
|
432
|
+
+ (len(headers) - 1) * TimeDelta(time_delta, format="sec")
|
|
433
|
+
+ TimeDelta(exp_time / 1000.0, format="sec")
|
|
434
|
+
).isot
|
|
435
|
+
|
|
436
|
+
assert final_header["DATE-BEG"] == start_time
|
|
437
|
+
assert final_header["DATE-END"] == date_end
|
|
438
|
+
|
|
439
|
+
date_end_from_single = (
|
|
440
|
+
Time(headers[0]["DATE-BEG"]) + TimeDelta(exp_time / 1000.0, format="sec")
|
|
441
|
+
).isot
|
|
442
|
+
|
|
443
|
+
assert final_header_from_single["DATE-BEG"] == headers[0]["DATE-BEG"]
|
|
444
|
+
assert final_header_from_single["DATE-END"] == date_end_from_single
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@pytest.mark.parametrize(
|
|
448
|
+
"is_polarimetric", [pytest.param(True, id="Full Stokes"), pytest.param(False, id="Stokes-I")]
|
|
449
|
+
)
|
|
450
|
+
def test_combine_beams_into_fits_access(
|
|
451
|
+
sp_science_calibration_task_no_data, calibrated_array_and_header_dicts, is_polarimetric
|
|
452
|
+
):
|
|
453
|
+
"""
|
|
454
|
+
Given: A SPScienceCalibrationTask with a set of calibrated beam arrays and headers
|
|
455
|
+
When: Combining the beams
|
|
456
|
+
Then: The correct result is returned
|
|
457
|
+
"""
|
|
458
|
+
array_dict, header_dict, array_shape = calibrated_array_and_header_dicts
|
|
459
|
+
task = sp_science_calibration_task_no_data
|
|
460
|
+
|
|
461
|
+
combined_result = task.combine_beams_into_fits_access(
|
|
462
|
+
array_dict=array_dict, header_dict=header_dict
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
assert isinstance(combined_result, CryonirspL0FitsAccess)
|
|
466
|
+
|
|
467
|
+
data = combined_result.data
|
|
468
|
+
expected_num_stokes = 4 if is_polarimetric else 1
|
|
469
|
+
|
|
470
|
+
# See `calibrated_array_and_header_dicts` for where these numbers come from
|
|
471
|
+
b1 = np.arange(1, expected_num_stokes + 1)
|
|
472
|
+
b2 = np.arange(1, expected_num_stokes + 1) + 10
|
|
473
|
+
avg_I = (b1[0] + b2[0]) / 2.0
|
|
474
|
+
if is_polarimetric:
|
|
475
|
+
expected_I = np.ones(array_shape) * avg_I
|
|
476
|
+
expected_Q = np.ones(array_shape) * (b1[1] / b1[0] + b2[1] / b2[0]) / 2.0 * avg_I
|
|
477
|
+
expected_U = np.ones(array_shape) * (b1[2] / b1[0] + b2[2] / b2[0]) / 2.0 * avg_I
|
|
478
|
+
expected_V = np.ones(array_shape) * (b1[3] / b1[0] + b2[3] / b2[0]) / 2.0 * avg_I
|
|
479
|
+
expected = np.dstack([expected_I, expected_Q, expected_U, expected_V])
|
|
480
|
+
else:
|
|
481
|
+
expected = np.ones((*array_shape, expected_num_stokes)) * avg_I
|
|
482
|
+
|
|
483
|
+
np.testing.assert_array_equal(data, expected)
|
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
|
|
9
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
|
|
10
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
11
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
12
|
+
from dkist_processing_cryonirsp.tasks.sp_solar_gain import SPSolarGainCalibration
|
|
13
|
+
from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
|
|
14
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
15
|
+
from dkist_processing_cryonirsp.tests.conftest import generate_214_l0_fits_frame
|
|
16
|
+
from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidSPSolarGainFrames
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture(scope="function")
|
|
20
|
+
def solar_gain_calibration_task_that_completes(
|
|
21
|
+
tmp_path,
|
|
22
|
+
recipe_run_id,
|
|
23
|
+
assign_input_dataset_doc_to_task,
|
|
24
|
+
init_cryonirsp_constants_db,
|
|
25
|
+
fringe_correction,
|
|
26
|
+
):
|
|
27
|
+
number_of_modstates = 1
|
|
28
|
+
# Be careful here!!! We have some files that are beam specific and others that contain both beams!!!
|
|
29
|
+
number_of_beams = 2
|
|
30
|
+
exposure_time = 20.0 # From CryonirspHeadersValidSolarGainFrames fixture
|
|
31
|
+
exposure_conditions = ExposureConditions(
|
|
32
|
+
exposure_time, AllowableOpticalDensityFilterNames.OPEN.value
|
|
33
|
+
)
|
|
34
|
+
intermediate_shape = (10, 10)
|
|
35
|
+
dataset_shape = (1, 10, 20)
|
|
36
|
+
array_shape = (1, 10, 20)
|
|
37
|
+
constants_db = CryonirspConstantsDb(
|
|
38
|
+
NUM_MODSTATES=number_of_modstates,
|
|
39
|
+
SOLAR_GAIN_EXPOSURE_CONDITIONS_LIST=(exposure_conditions,),
|
|
40
|
+
ARM_ID="SP",
|
|
41
|
+
)
|
|
42
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
43
|
+
with SPSolarGainCalibration(
|
|
44
|
+
recipe_run_id=recipe_run_id,
|
|
45
|
+
workflow_name="sp_solar_gain_calibration",
|
|
46
|
+
workflow_version="VX.Y", # check workflow name?
|
|
47
|
+
) as task:
|
|
48
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
49
|
+
task.scratch = WorkflowFileSystem(
|
|
50
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
51
|
+
)
|
|
52
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
53
|
+
assign_input_dataset_doc_to_task(
|
|
54
|
+
task, param_class(cryonirsp_fringe_correction_on=fringe_correction)
|
|
55
|
+
)
|
|
56
|
+
# Create fake bad pixel map
|
|
57
|
+
task.intermediate_frame_write_arrays(
|
|
58
|
+
arrays=np.zeros((10, 20)),
|
|
59
|
+
task_tag=CryonirspTag.task_bad_pixel_map(),
|
|
60
|
+
)
|
|
61
|
+
for beam in range(1, number_of_beams + 1):
|
|
62
|
+
# Create fake beam border intermediate arrays
|
|
63
|
+
task.intermediate_frame_write_arrays(
|
|
64
|
+
arrays=np.array([0, 10, ((beam - 1) * 10), 10 + ((beam - 1) * 10)]),
|
|
65
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
66
|
+
beam=beam,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# DarkCal object
|
|
70
|
+
dark_cal = np.ones(intermediate_shape) * 3.0
|
|
71
|
+
task.intermediate_frame_write_arrays(
|
|
72
|
+
arrays=dark_cal,
|
|
73
|
+
beam=beam,
|
|
74
|
+
task_tag=CryonirspTag.task_dark(),
|
|
75
|
+
exposure_conditions=exposure_conditions,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Geo angles and spec_shifts
|
|
79
|
+
task.intermediate_frame_write_arrays(
|
|
80
|
+
arrays=np.zeros(1),
|
|
81
|
+
beam=beam,
|
|
82
|
+
task_tag=CryonirspTag.task_geometric_angle(),
|
|
83
|
+
)
|
|
84
|
+
task.intermediate_frame_write_arrays(
|
|
85
|
+
arrays=np.zeros(intermediate_shape[0]),
|
|
86
|
+
beam=beam,
|
|
87
|
+
task_tag=CryonirspTag.task_geometric_sepectral_shifts(),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
for modstate in range(1, number_of_modstates + 1):
|
|
91
|
+
# LampCal object
|
|
92
|
+
lamp_cal_ramp = np.arange(1, intermediate_shape[1] + 1) / 5
|
|
93
|
+
lamp_cal_ramp = np.flip(lamp_cal_ramp)
|
|
94
|
+
lamp_cal = np.ones(intermediate_shape) * lamp_cal_ramp[None, :]
|
|
95
|
+
lamp_cal /= np.nanmean(lamp_cal)
|
|
96
|
+
task.intermediate_frame_write_arrays(
|
|
97
|
+
arrays=lamp_cal,
|
|
98
|
+
beam=beam,
|
|
99
|
+
modstate=modstate,
|
|
100
|
+
task_tag=CryonirspTag.task_lamp_gain(),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Geo offsets
|
|
104
|
+
task.intermediate_frame_write_arrays(
|
|
105
|
+
arrays=np.zeros(2),
|
|
106
|
+
beam=beam,
|
|
107
|
+
modstate=modstate,
|
|
108
|
+
task_tag=CryonirspTag.task_geometric_offset(),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Raw gain input images contain both beams, so are not beam specific!!!
|
|
112
|
+
ds = CryonirspHeadersValidSPSolarGainFrames(
|
|
113
|
+
dataset_shape=dataset_shape,
|
|
114
|
+
array_shape=array_shape,
|
|
115
|
+
time_delta=10,
|
|
116
|
+
)
|
|
117
|
+
header = ds.header()
|
|
118
|
+
true_gain = np.ones(intermediate_shape)
|
|
119
|
+
true_solar_signal = (
|
|
120
|
+
np.arange(1, intermediate_shape[1] + 1) / 5
|
|
121
|
+
) # creates a trend from 0.2 to 2
|
|
122
|
+
true_solar_single_beam = true_gain * true_solar_signal[None, :]
|
|
123
|
+
true_solar_gain = np.concatenate(
|
|
124
|
+
(true_solar_single_beam, true_solar_single_beam), axis=1
|
|
125
|
+
)
|
|
126
|
+
raw_dark = np.concatenate((dark_cal, dark_cal), axis=1)
|
|
127
|
+
raw_solar = true_solar_gain + raw_dark
|
|
128
|
+
solar_hdul = generate_214_l0_fits_frame(data=raw_solar, s122_header=header)
|
|
129
|
+
task.write(
|
|
130
|
+
data=solar_hdul,
|
|
131
|
+
tags=[
|
|
132
|
+
CryonirspTag.linearized(),
|
|
133
|
+
CryonirspTag.task_solar_gain(),
|
|
134
|
+
CryonirspTag.modstate(modstate),
|
|
135
|
+
CryonirspTag.frame(),
|
|
136
|
+
CryonirspTag.exposure_conditions(exposure_conditions),
|
|
137
|
+
],
|
|
138
|
+
encoder=fits_hdulist_encoder,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
yield task, true_solar_single_beam
|
|
142
|
+
finally:
|
|
143
|
+
task._purge()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@pytest.fixture(scope="function")
|
|
147
|
+
def solar_gain_calibration_task_with_no_data(tmp_path, recipe_run_id, init_cryonirsp_constants_db):
|
|
148
|
+
number_of_modstates = 1
|
|
149
|
+
constants_db = CryonirspConstantsDb(NUM_MODSTATES=number_of_modstates, ARM_ID="SP")
|
|
150
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
151
|
+
with SPSolarGainCalibration(
|
|
152
|
+
recipe_run_id=recipe_run_id, workflow_name="geometric_calibration", workflow_version="VX.Y"
|
|
153
|
+
) as task:
|
|
154
|
+
task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
|
|
155
|
+
|
|
156
|
+
yield task
|
|
157
|
+
task._purge()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@pytest.mark.parametrize("fringe_correction", [False, True])
|
|
161
|
+
def test_solar_gain_task(solar_gain_calibration_task_that_completes, mocker, fringe_correction):
|
|
162
|
+
"""
|
|
163
|
+
Given: A set of raw solar gain images and necessary intermediate calibrations
|
|
164
|
+
When: Running the solargain task
|
|
165
|
+
Then: The task completes and the outputs are correct
|
|
166
|
+
"""
|
|
167
|
+
mocker.patch(
|
|
168
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
task, true_solar_single_beam = solar_gain_calibration_task_that_completes
|
|
172
|
+
task()
|
|
173
|
+
for beam in range(1, task.constants.num_beams + 1):
|
|
174
|
+
for modstate in range(1, task.constants.num_modstates + 1):
|
|
175
|
+
solar_gain = task.intermediate_frame_load_solar_gain_array(beam=beam)
|
|
176
|
+
# If fringe correction is on, then just be happy we got a file...
|
|
177
|
+
if task.parameters.fringe_correction_on:
|
|
178
|
+
continue
|
|
179
|
+
# The processed image is flipped, so we must flip the original to test
|
|
180
|
+
expected = np.flip(true_solar_single_beam, axis=1)
|
|
181
|
+
np.testing.assert_allclose(expected, solar_gain)
|
|
182
|
+
# Test for the existence of the spectral corrected solar array
|
|
183
|
+
spectral_corrected_array = task.intermediate_frame_load_intermediate_arrays(
|
|
184
|
+
tags=[CryonirspTag.beam(beam), CryonirspTag.task("SPECTRAL_CORRECTED_SOLAR_ARRAY")]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
quality_files = task.read(tags=[CryonirspTag.quality("TASK_TYPES")])
|
|
188
|
+
for file in quality_files:
|
|
189
|
+
with file.open() as f:
|
|
190
|
+
data = json.load(f)
|
|
191
|
+
assert isinstance(data, dict)
|
|
192
|
+
assert data["total_frames"] == task.scratch.count_all(
|
|
193
|
+
tags=[
|
|
194
|
+
CryonirspTag.linearized(),
|
|
195
|
+
CryonirspTag.frame(),
|
|
196
|
+
CryonirspTag.task_solar_gain(),
|
|
197
|
+
]
|
|
198
|
+
)
|