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,126 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
6
|
+
from dkist_processing_common.tasks import WorkflowTaskBase
|
|
7
|
+
from dkist_processing_common.tasks.mixin.input_dataset import InputDatasetMixin
|
|
8
|
+
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
9
|
+
|
|
10
|
+
from dkist_processing_cryonirsp.models.constants import CryonirspConstants
|
|
11
|
+
from dkist_processing_cryonirsp.tasks.mixin.corrections import CorrectionsMixin
|
|
12
|
+
from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
|
|
13
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
14
|
+
|
|
15
|
+
base_bad_pixel_map = np.zeros(shape=(10, 10))
|
|
16
|
+
|
|
17
|
+
normal_bad_pixel_map = base_bad_pixel_map.copy()
|
|
18
|
+
normal_bad_pixel_map[1, 6] = 1
|
|
19
|
+
|
|
20
|
+
column_error_bad_pixel_map = base_bad_pixel_map.copy()
|
|
21
|
+
column_error_bad_pixel_map[:, 6] = 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BadPixelMapTask(WorkflowTaskBase, CorrectionsMixin, InputDatasetMixin):
|
|
25
|
+
constants: CryonirspConstants
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def constants_model_class(self):
|
|
29
|
+
"""Get CryoNIRSP pipeline constants."""
|
|
30
|
+
return CryonirspConstants
|
|
31
|
+
|
|
32
|
+
def run(self):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture(params=["CI", "SP"])
|
|
37
|
+
def bad_pixel_mask_task(
|
|
38
|
+
tmp_path,
|
|
39
|
+
recipe_run_id,
|
|
40
|
+
assign_input_dataset_doc_to_task,
|
|
41
|
+
mocker,
|
|
42
|
+
request,
|
|
43
|
+
init_cryonirsp_constants_db,
|
|
44
|
+
):
|
|
45
|
+
mocker.patch(
|
|
46
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
47
|
+
)
|
|
48
|
+
constants_db = CryonirspConstantsDb(
|
|
49
|
+
ARM_ID=request.param,
|
|
50
|
+
)
|
|
51
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
52
|
+
with BadPixelMapTask(
|
|
53
|
+
recipe_run_id=recipe_run_id,
|
|
54
|
+
workflow_name="bad_pixel_mask",
|
|
55
|
+
workflow_version="VX.Y",
|
|
56
|
+
) as task:
|
|
57
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
58
|
+
task.scratch = WorkflowFileSystem(
|
|
59
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
60
|
+
)
|
|
61
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
62
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
63
|
+
yield task
|
|
64
|
+
finally:
|
|
65
|
+
task._purge()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def contrive_bad_px_neighborhood(
|
|
69
|
+
array: np.ndarray, bad_px_location: tuple[int, int], kernel_size: int, desired_median: float
|
|
70
|
+
) -> np.array:
|
|
71
|
+
"""
|
|
72
|
+
Adjust array values so the neighborhood around a bad pixel results in a known value being used to replace that pixel.
|
|
73
|
+
"""
|
|
74
|
+
# Sticking with the y, x convention used by `corrections_correct_bad_pixels`.
|
|
75
|
+
y, x = bad_px_location
|
|
76
|
+
half_kernel_size = kernel_size // 2
|
|
77
|
+
num_y, num_x = array.shape
|
|
78
|
+
|
|
79
|
+
y_slice = slice(max(y - half_kernel_size, 0), min(y + half_kernel_size + 1, num_y))
|
|
80
|
+
x_slice = slice(max(x - half_kernel_size, 0), min(x + half_kernel_size + 1, num_x))
|
|
81
|
+
|
|
82
|
+
array[y_slice, x_slice] = desired_median
|
|
83
|
+
|
|
84
|
+
return array
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.parametrize(
|
|
88
|
+
"bad_pixel_map, algorithm_type",
|
|
89
|
+
[
|
|
90
|
+
pytest.param(normal_bad_pixel_map, "normal", id="normal algorithm"),
|
|
91
|
+
pytest.param(column_error_bad_pixel_map, "fast", id="fast algorithm"),
|
|
92
|
+
],
|
|
93
|
+
)
|
|
94
|
+
def test_corrections_correct_bad_pixels(bad_pixel_map, algorithm_type, bad_pixel_mask_task):
|
|
95
|
+
t = bad_pixel_mask_task
|
|
96
|
+
bad_pixel_x = 1
|
|
97
|
+
bad_pixel_y = 6
|
|
98
|
+
|
|
99
|
+
# Create a data array. Adding 10 ensures that 0 will be a valid sentinel value of bad-ness
|
|
100
|
+
rng = np.random.default_rng()
|
|
101
|
+
array_to_fix = rng.random((10, 10), dtype=float) * 100 + 10.0
|
|
102
|
+
|
|
103
|
+
# Assign a single bad pixel to check against
|
|
104
|
+
if algorithm_type == "normal":
|
|
105
|
+
expected_corrected_value = rng.random() * 100 + 10
|
|
106
|
+
array_to_fix = contrive_bad_px_neighborhood(
|
|
107
|
+
array=array_to_fix,
|
|
108
|
+
bad_px_location=(1, 6),
|
|
109
|
+
kernel_size=t.parameters.corrections_bad_pixel_median_filter_size,
|
|
110
|
+
desired_median=expected_corrected_value,
|
|
111
|
+
)
|
|
112
|
+
array_to_fix[bad_pixel_x, bad_pixel_y] = 0
|
|
113
|
+
|
|
114
|
+
corrected_array = t.corrections_correct_bad_pixels(
|
|
115
|
+
array_to_fix=array_to_fix, bad_pixel_map=bad_pixel_map
|
|
116
|
+
)
|
|
117
|
+
if algorithm_type == "fast":
|
|
118
|
+
for val in corrected_array[:, bad_pixel_y]:
|
|
119
|
+
assert val == np.nanmedian(array_to_fix)
|
|
120
|
+
assert corrected_array[bad_pixel_x, bad_pixel_y] == np.nanmedian(array_to_fix)
|
|
121
|
+
|
|
122
|
+
if algorithm_type == "normal":
|
|
123
|
+
x, y = np.meshgrid(np.arange(10), np.arange(10))
|
|
124
|
+
idx = np.where((x != bad_pixel_x) | (y != bad_pixel_y))
|
|
125
|
+
np.testing.assert_array_equal(corrected_array[idx], array_to_fix[idx])
|
|
126
|
+
assert corrected_array[bad_pixel_x, bad_pixel_y] == expected_corrected_value
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
from astropy.io import fits
|
|
4
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
5
|
+
from dkist_processing_common.codecs.fits import fits_hdu_decoder
|
|
6
|
+
|
|
7
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
8
|
+
from dkist_processing_cryonirsp.models.task_name import CryonirspTaskName
|
|
9
|
+
from dkist_processing_cryonirsp.tasks.cryonirsp_base import CryonirspTaskBase
|
|
10
|
+
from dkist_processing_cryonirsp.tasks.mixin.intermediate_frame import (
|
|
11
|
+
IntermediateFrameMixin,
|
|
12
|
+
)
|
|
13
|
+
from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
|
|
14
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
15
|
+
|
|
16
|
+
NUM_BEAMS = 2
|
|
17
|
+
NUM_MODSTATES = 8
|
|
18
|
+
NUM_CS_STEPS = 6
|
|
19
|
+
NUM_SCAN_STEPS = 10
|
|
20
|
+
WAVE = 1082.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture(scope="function")
|
|
24
|
+
def cryo_science_task(
|
|
25
|
+
tmp_path, recipe_run_id, assign_input_dataset_doc_to_task, init_cryonirsp_constants_db
|
|
26
|
+
):
|
|
27
|
+
class Task(CryonirspTaskBase, IntermediateFrameMixin):
|
|
28
|
+
def run(self):
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
constants_db = CryonirspConstantsDb(
|
|
32
|
+
NUM_MODSTATES=NUM_MODSTATES,
|
|
33
|
+
NUM_CS_STEPS=NUM_CS_STEPS,
|
|
34
|
+
NUM_SCAN_STEPS=NUM_SCAN_STEPS,
|
|
35
|
+
WAVELENGTH=WAVE,
|
|
36
|
+
)
|
|
37
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
38
|
+
with Task(
|
|
39
|
+
recipe_run_id=recipe_run_id,
|
|
40
|
+
workflow_name="parse_cryonirsp_input_data",
|
|
41
|
+
workflow_version="VX.Y",
|
|
42
|
+
) as task:
|
|
43
|
+
try:
|
|
44
|
+
task.scratch = WorkflowFileSystem(
|
|
45
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
46
|
+
)
|
|
47
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
48
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
49
|
+
yield task
|
|
50
|
+
finally:
|
|
51
|
+
task._purge()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_write_intermediate_arrays(cryo_science_task):
|
|
55
|
+
"""
|
|
56
|
+
Given: A CryonirspTaskBase task
|
|
57
|
+
When: Using the helper to write a single intermediate array
|
|
58
|
+
Then: The array is written and tagged correctly
|
|
59
|
+
"""
|
|
60
|
+
data = np.random.random((10, 10))
|
|
61
|
+
head = fits.Header()
|
|
62
|
+
head["TEST"] = "foo"
|
|
63
|
+
cryo_science_task.intermediate_frame_write_arrays(
|
|
64
|
+
arrays=data, headers=head, beam=1, map_scan=2, scan_step=3, task="BAR"
|
|
65
|
+
)
|
|
66
|
+
loaded_list = list(
|
|
67
|
+
cryo_science_task.read(
|
|
68
|
+
tags=[
|
|
69
|
+
CryonirspTag.intermediate(),
|
|
70
|
+
CryonirspTag.frame(),
|
|
71
|
+
CryonirspTag.beam(1),
|
|
72
|
+
CryonirspTag.map_scan(2),
|
|
73
|
+
CryonirspTag.scan_step(3),
|
|
74
|
+
CryonirspTag.task("BAR"),
|
|
75
|
+
],
|
|
76
|
+
decoder=fits_hdu_decoder,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
assert len(loaded_list) == 1
|
|
80
|
+
hdu = loaded_list[0]
|
|
81
|
+
np.testing.assert_equal(hdu.data, data)
|
|
82
|
+
assert hdu.header["TEST"] == "foo"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_write_intermediate_arrays_task_tag(cryo_science_task):
|
|
86
|
+
"""
|
|
87
|
+
Given: A CryonirspTaskBase task
|
|
88
|
+
When: Using the helper to write a single intermediate array with a formatted task tag input arg
|
|
89
|
+
Then: The array is written and tagged correctly
|
|
90
|
+
"""
|
|
91
|
+
data = np.random.random((10, 10))
|
|
92
|
+
head = fits.Header()
|
|
93
|
+
head["TEST"] = "foo"
|
|
94
|
+
|
|
95
|
+
# bad_pixel_map chosen for no particular reason
|
|
96
|
+
cryo_science_task.intermediate_frame_write_arrays(
|
|
97
|
+
arrays=data, headers=head, task_tag=CryonirspTag.task_bad_pixel_map()
|
|
98
|
+
)
|
|
99
|
+
loaded_list = list(
|
|
100
|
+
cryo_science_task.read(
|
|
101
|
+
tags=[CryonirspTag.task_bad_pixel_map()],
|
|
102
|
+
decoder=fits_hdu_decoder,
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
assert len(loaded_list) == 1
|
|
106
|
+
hdu = loaded_list[0]
|
|
107
|
+
np.testing.assert_equal(hdu.data, data)
|
|
108
|
+
assert hdu.header["TEST"] == "foo"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_write_intermediate_arrays_task_collisions(cryo_science_task):
|
|
112
|
+
"""
|
|
113
|
+
Given: A CryonirspTaskBase task
|
|
114
|
+
When: Using the helper but providing invalid `task` or `task_tag` inputs
|
|
115
|
+
Then: An error is raised
|
|
116
|
+
"""
|
|
117
|
+
data = np.random.random((10, 10))
|
|
118
|
+
|
|
119
|
+
# Test both given
|
|
120
|
+
with pytest.raises(ValueError, match="Cannot specify"):
|
|
121
|
+
cryo_science_task.intermediate_frame_write_arrays(
|
|
122
|
+
arrays=data, task_tag=CryonirspTag.task_bad_pixel_map(), task="DARK"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Test neither given
|
|
126
|
+
with pytest.raises(ValueError, match="Must specify"):
|
|
127
|
+
cryo_science_task.intermediate_frame_write_arrays(arrays=data)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_write_intermediate_arrays_none_header(cryo_science_task):
|
|
131
|
+
"""
|
|
132
|
+
Given: A CryonirspTaskBase task
|
|
133
|
+
When: Using the helper to write a single intermediate array with no header
|
|
134
|
+
Then: The array is written and tagged correctly
|
|
135
|
+
"""
|
|
136
|
+
data = np.random.random((10, 10))
|
|
137
|
+
cryo_science_task.intermediate_frame_write_arrays(
|
|
138
|
+
arrays=data, headers=None, beam=1, map_scan=2, scan_step=3, task="BAR"
|
|
139
|
+
)
|
|
140
|
+
loaded_list = list(
|
|
141
|
+
cryo_science_task.read(
|
|
142
|
+
tags=[
|
|
143
|
+
CryonirspTag.intermediate(),
|
|
144
|
+
CryonirspTag.frame(),
|
|
145
|
+
CryonirspTag.beam(1),
|
|
146
|
+
CryonirspTag.map_scan(2),
|
|
147
|
+
CryonirspTag.scan_step(3),
|
|
148
|
+
CryonirspTag.task("BAR"),
|
|
149
|
+
],
|
|
150
|
+
decoder=fits_hdu_decoder,
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
assert len(loaded_list) == 1
|
|
154
|
+
hdu = loaded_list[0]
|
|
155
|
+
np.testing.assert_equal(hdu.data, data)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@pytest.fixture
|
|
159
|
+
def cryo_science_task_with_tagged_intermediates(
|
|
160
|
+
recipe_run_id, tmpdir_factory, init_cryonirsp_constants_db
|
|
161
|
+
):
|
|
162
|
+
class Task(CryonirspTaskBase, IntermediateFrameMixin):
|
|
163
|
+
def run(self):
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
init_cryonirsp_constants_db(recipe_run_id, CryonirspConstantsDb())
|
|
167
|
+
with Task(
|
|
168
|
+
recipe_run_id=recipe_run_id,
|
|
169
|
+
workflow_name="parse_cryo_input_data",
|
|
170
|
+
workflow_version="VX.Y",
|
|
171
|
+
) as task:
|
|
172
|
+
try:
|
|
173
|
+
task.scratch = WorkflowFileSystem(scratch_base_path=tmpdir_factory.mktemp("data"))
|
|
174
|
+
tag_names = [["beam"], ["exposure_time", "task"], ["modstate"]]
|
|
175
|
+
tag_vals = [[1], [10.23, "dark"], [3]]
|
|
176
|
+
tag_fcns = [[getattr(CryonirspTag, n) for n in nl] for nl in tag_names]
|
|
177
|
+
tag_list = [[f(v) for f, v in zip(fl, vl)] for fl, vl in zip(tag_fcns, tag_vals)]
|
|
178
|
+
for i, tags in enumerate(tag_list):
|
|
179
|
+
hdul = fits.HDUList([fits.PrimaryHDU(data=np.ones((2, 2)) * i)])
|
|
180
|
+
fname = task.scratch.workflow_base_path / f"file{i}.fits"
|
|
181
|
+
hdul.writeto(fname)
|
|
182
|
+
task.tag(fname, tags + [CryonirspTag.intermediate(), CryonirspTag.frame()])
|
|
183
|
+
|
|
184
|
+
yield task, tag_names, tag_vals
|
|
185
|
+
finally:
|
|
186
|
+
task._purge()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_load_intermediate_arrays(cryo_science_task_with_tagged_intermediates):
|
|
190
|
+
"""
|
|
191
|
+
Given: A task with tagged intermediate frames
|
|
192
|
+
When: Using intermediate frame loaders to grab the intermediate frames
|
|
193
|
+
Then: The correct arrays are returned
|
|
194
|
+
"""
|
|
195
|
+
task, tag_names, tag_vals = cryo_science_task_with_tagged_intermediates
|
|
196
|
+
tag_list_list = [
|
|
197
|
+
[getattr(CryonirspTag, n)(v) for n, v in zip(nl, vl)] for nl, vl in zip(tag_names, tag_vals)
|
|
198
|
+
]
|
|
199
|
+
for i, tags in enumerate(tag_list_list):
|
|
200
|
+
arrays = list(task.intermediate_frame_load_intermediate_arrays(tags=tags))
|
|
201
|
+
assert len(arrays) == 1
|
|
202
|
+
np.testing.assert_equal(arrays[0], np.ones((2, 2)) * i)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from dataclasses import asdict
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
|
|
7
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
8
|
+
from dkist_processing_cryonirsp.tasks.cryonirsp_base import CryonirspTaskBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture(scope="function")
|
|
12
|
+
def testing_constants(polarimetric):
|
|
13
|
+
@dataclass
|
|
14
|
+
class testing_constants:
|
|
15
|
+
obs_ip_start_time: str = "1999-12-31T23:59:59"
|
|
16
|
+
num_modstates: int = 10 if polarimetric else 1
|
|
17
|
+
num_beams: int = 2
|
|
18
|
+
num_cs_steps: int = 18
|
|
19
|
+
num_scan_steps: int = 1000
|
|
20
|
+
wavelength: float = 1082.0
|
|
21
|
+
lamp_gain_exposure_conditions_list: tuple[ExposureConditions, ...] = (
|
|
22
|
+
ExposureConditions(100.0, AllowableOpticalDensityFilterNames.OPEN.value),
|
|
23
|
+
)
|
|
24
|
+
solar_gain_exposure_conditions_list: tuple[ExposureConditions, ...] = (
|
|
25
|
+
ExposureConditions(1.0, AllowableOpticalDensityFilterNames.OPEN.value),
|
|
26
|
+
)
|
|
27
|
+
observe_exposure_conditions_list: tuple[ExposureConditions, ...] = (
|
|
28
|
+
ExposureConditions(0.01, AllowableOpticalDensityFilterNames.OPEN.value),
|
|
29
|
+
)
|
|
30
|
+
modulator_spin_mode: str = "Continuous" if polarimetric else "Off"
|
|
31
|
+
# We don't need all the common ones, but let's put one just to check
|
|
32
|
+
instrument: str = "CHECK_OUT_THIS_INSTRUMENT"
|
|
33
|
+
arm_id: str = "SP"
|
|
34
|
+
|
|
35
|
+
return testing_constants()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture(scope="function")
|
|
39
|
+
def expected_constant_dict(testing_constants) -> dict:
|
|
40
|
+
lower_dict = asdict(testing_constants)
|
|
41
|
+
return {k.upper(): v for k, v in lower_dict.items()}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.fixture(scope="function")
|
|
45
|
+
def cryo_science_task_with_constants(
|
|
46
|
+
recipe_run_id, expected_constant_dict, init_cryonirsp_constants_db
|
|
47
|
+
):
|
|
48
|
+
class Task(CryonirspTaskBase):
|
|
49
|
+
def run(self):
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
init_cryonirsp_constants_db(recipe_run_id, expected_constant_dict)
|
|
53
|
+
task = Task(
|
|
54
|
+
recipe_run_id=recipe_run_id,
|
|
55
|
+
workflow_name="parse_cryo_input_data",
|
|
56
|
+
workflow_version="VX.Y",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
yield task
|
|
60
|
+
|
|
61
|
+
task._purge()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.parametrize(
|
|
65
|
+
"polarimetric",
|
|
66
|
+
[pytest.param(True, id="Polarimetric"), pytest.param(False, id="Spectrographic")],
|
|
67
|
+
)
|
|
68
|
+
def test_cryo_constants(cryo_science_task_with_constants, expected_constant_dict, polarimetric):
|
|
69
|
+
task = cryo_science_task_with_constants
|
|
70
|
+
for k, v in expected_constant_dict.items():
|
|
71
|
+
if k.lower() == "modulator_spin_mode":
|
|
72
|
+
continue
|
|
73
|
+
if type(v) is tuple:
|
|
74
|
+
v = list(v)
|
|
75
|
+
assert getattr(task.constants, k.lower()) == v
|
|
76
|
+
assert task.constants.correct_for_polarization == polarimetric
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import json
|
|
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.exposure_conditions import AllowableOpticalDensityFilterNames
|
|
12
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
13
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
14
|
+
from dkist_processing_cryonirsp.tasks.dark import DarkCalibration
|
|
15
|
+
from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
|
|
16
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
17
|
+
from dkist_processing_cryonirsp.tests.conftest import generate_fits_frame
|
|
18
|
+
from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidDarkFrames
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture(scope="function")
|
|
22
|
+
def sp_dark_calibration_task(
|
|
23
|
+
tmp_path, assign_input_dataset_doc_to_task, init_cryonirsp_constants_db, recipe_run_id
|
|
24
|
+
):
|
|
25
|
+
# Make sure we test cases where either the exp time or filter are the same, but the other value is
|
|
26
|
+
# different
|
|
27
|
+
exposure_conditions = (
|
|
28
|
+
ExposureConditions(100.0, AllowableOpticalDensityFilterNames.OPEN.value),
|
|
29
|
+
ExposureConditions(1.0, AllowableOpticalDensityFilterNames.G278.value),
|
|
30
|
+
ExposureConditions(0.01, AllowableOpticalDensityFilterNames.NONE.value),
|
|
31
|
+
ExposureConditions(100.0, AllowableOpticalDensityFilterNames.G278.value),
|
|
32
|
+
)
|
|
33
|
+
unused_exposure_condition = ExposureConditions(
|
|
34
|
+
200.0, AllowableOpticalDensityFilterNames.NONE.value
|
|
35
|
+
)
|
|
36
|
+
constants_db = CryonirspConstantsDb(
|
|
37
|
+
NON_DARK_AND_NON_POLCAL_TASK_EXPOSURE_CONDITIONS_LIST=exposure_conditions,
|
|
38
|
+
ARM_ID="SP",
|
|
39
|
+
)
|
|
40
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
41
|
+
with DarkCalibration(
|
|
42
|
+
recipe_run_id=recipe_run_id, workflow_name="dark_calibration", workflow_version="VX.Y"
|
|
43
|
+
) as task:
|
|
44
|
+
illuminated_beam_shape = (6, 4)
|
|
45
|
+
num_beams = 2
|
|
46
|
+
num_exp_cond = len(exposure_conditions) + 1 # +1 for the unused condition
|
|
47
|
+
num_frames_per_condition = 3
|
|
48
|
+
array_shape = (1, 10, 20)
|
|
49
|
+
dataset_shape = (num_exp_cond * num_frames_per_condition, 20, 10)
|
|
50
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
51
|
+
task.scratch = WorkflowFileSystem(
|
|
52
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
53
|
+
)
|
|
54
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
55
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
56
|
+
|
|
57
|
+
# Create fake beam border intermediate arrays
|
|
58
|
+
for beam in range(1, num_beams + 1):
|
|
59
|
+
spectral_starting_pixel = 5 + ((beam - 1) * 10)
|
|
60
|
+
beam_boundaries = np.array(
|
|
61
|
+
[
|
|
62
|
+
3,
|
|
63
|
+
3 + illuminated_beam_shape[0],
|
|
64
|
+
spectral_starting_pixel,
|
|
65
|
+
spectral_starting_pixel + illuminated_beam_shape[1],
|
|
66
|
+
]
|
|
67
|
+
)
|
|
68
|
+
task.intermediate_frame_write_arrays(
|
|
69
|
+
arrays=beam_boundaries,
|
|
70
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
71
|
+
beam=beam,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
ds = CryonirspHeadersValidDarkFrames(
|
|
75
|
+
dataset_shape=dataset_shape,
|
|
76
|
+
array_shape=array_shape,
|
|
77
|
+
time_delta=10,
|
|
78
|
+
exposure_time=1.0,
|
|
79
|
+
)
|
|
80
|
+
header_generator = (
|
|
81
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
82
|
+
d.header(), return_type=fits.HDUList
|
|
83
|
+
)[0].header
|
|
84
|
+
for d in ds
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
for condition in exposure_conditions + (unused_exposure_condition,):
|
|
88
|
+
for _ in range(num_frames_per_condition):
|
|
89
|
+
hdul = generate_fits_frame(header_generator=header_generator, shape=array_shape)
|
|
90
|
+
hdul[0].data.fill(condition.exposure_time)
|
|
91
|
+
task.write(
|
|
92
|
+
data=hdul,
|
|
93
|
+
tags=[
|
|
94
|
+
CryonirspTag.linearized(),
|
|
95
|
+
CryonirspTag.frame(),
|
|
96
|
+
CryonirspTag.task_dark(),
|
|
97
|
+
CryonirspTag.exposure_conditions(condition),
|
|
98
|
+
],
|
|
99
|
+
encoder=fits_hdulist_encoder,
|
|
100
|
+
)
|
|
101
|
+
yield task, num_beams, exposure_conditions, unused_exposure_condition, illuminated_beam_shape
|
|
102
|
+
finally:
|
|
103
|
+
task._purge()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.fixture(scope="function")
|
|
107
|
+
def ci_dark_calibration_task(
|
|
108
|
+
tmp_path, assign_input_dataset_doc_to_task, init_cryonirsp_constants_db, recipe_run_id
|
|
109
|
+
):
|
|
110
|
+
# Make sure we test cases where either the exp time or filter are the same, but the other value is
|
|
111
|
+
# different
|
|
112
|
+
exposure_conditions = (
|
|
113
|
+
ExposureConditions(100.0, AllowableOpticalDensityFilterNames.OPEN.value),
|
|
114
|
+
ExposureConditions(1.0, AllowableOpticalDensityFilterNames.G278.value),
|
|
115
|
+
ExposureConditions(0.01, AllowableOpticalDensityFilterNames.NONE.value),
|
|
116
|
+
ExposureConditions(100.0, AllowableOpticalDensityFilterNames.G278.value),
|
|
117
|
+
)
|
|
118
|
+
unused_exposure_condition = ExposureConditions(
|
|
119
|
+
200.0, AllowableOpticalDensityFilterNames.NONE.value
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
constants_db = CryonirspConstantsDb(
|
|
123
|
+
NON_DARK_AND_NON_POLCAL_TASK_EXPOSURE_CONDITIONS_LIST=exposure_conditions,
|
|
124
|
+
ARM_ID="CI",
|
|
125
|
+
)
|
|
126
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
127
|
+
with DarkCalibration(
|
|
128
|
+
recipe_run_id=recipe_run_id, workflow_name="dark_calibration", workflow_version="VX.Y"
|
|
129
|
+
) as task:
|
|
130
|
+
num_exp_cond = len(exposure_conditions) + 1 # +1 for the unused condition
|
|
131
|
+
num_frames_per_condition = 3
|
|
132
|
+
array_shape = (1, 10, 10)
|
|
133
|
+
dataset_shape = (num_exp_cond * num_frames_per_condition, 20, 10)
|
|
134
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
135
|
+
task.scratch = WorkflowFileSystem(
|
|
136
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
137
|
+
)
|
|
138
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
139
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
140
|
+
|
|
141
|
+
# Need a beam boundary file
|
|
142
|
+
task.intermediate_frame_write_arrays(
|
|
143
|
+
arrays=np.array([0, 10, 0, 10]),
|
|
144
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
145
|
+
beam=1,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
ds = CryonirspHeadersValidDarkFrames(
|
|
149
|
+
dataset_shape=dataset_shape,
|
|
150
|
+
array_shape=array_shape,
|
|
151
|
+
time_delta=10,
|
|
152
|
+
exposure_time=1.0,
|
|
153
|
+
)
|
|
154
|
+
header_generator = (
|
|
155
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
156
|
+
d.header(), return_type=fits.HDUList
|
|
157
|
+
)[0].header
|
|
158
|
+
for d in ds
|
|
159
|
+
)
|
|
160
|
+
for condition in exposure_conditions + (unused_exposure_condition,):
|
|
161
|
+
for _ in range(num_frames_per_condition):
|
|
162
|
+
hdul = generate_fits_frame(header_generator=header_generator, shape=array_shape)
|
|
163
|
+
hdul[0].data.fill(condition.exposure_time)
|
|
164
|
+
task.write(
|
|
165
|
+
data=hdul,
|
|
166
|
+
tags=[
|
|
167
|
+
CryonirspTag.linearized(),
|
|
168
|
+
CryonirspTag.frame(),
|
|
169
|
+
CryonirspTag.task_dark(),
|
|
170
|
+
CryonirspTag.exposure_conditions(condition),
|
|
171
|
+
],
|
|
172
|
+
encoder=fits_hdulist_encoder,
|
|
173
|
+
)
|
|
174
|
+
yield task, exposure_conditions, unused_exposure_condition
|
|
175
|
+
finally:
|
|
176
|
+
task._purge()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def test_sp_dark_calibration_task(sp_dark_calibration_task, mocker):
|
|
180
|
+
"""
|
|
181
|
+
Given: A DarkCalibration task with multiple task exposure times
|
|
182
|
+
When: Calling the task instance
|
|
183
|
+
Then: Only one average intermediate dark frame exists for each exposure time and unused times are not made
|
|
184
|
+
"""
|
|
185
|
+
mocker.patch(
|
|
186
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
187
|
+
)
|
|
188
|
+
# When
|
|
189
|
+
(
|
|
190
|
+
task,
|
|
191
|
+
num_beams,
|
|
192
|
+
exp_conditions,
|
|
193
|
+
unused_condition,
|
|
194
|
+
illuminated_beam_shape,
|
|
195
|
+
) = sp_dark_calibration_task
|
|
196
|
+
task()
|
|
197
|
+
# Then
|
|
198
|
+
for condition in exp_conditions:
|
|
199
|
+
for b in range(num_beams):
|
|
200
|
+
files = list(
|
|
201
|
+
task.read(
|
|
202
|
+
tags=[
|
|
203
|
+
CryonirspTag.task_dark(),
|
|
204
|
+
CryonirspTag.intermediate(),
|
|
205
|
+
CryonirspTag.frame(),
|
|
206
|
+
CryonirspTag.beam(b + 1),
|
|
207
|
+
CryonirspTag.exposure_conditions(condition),
|
|
208
|
+
]
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
assert len(files) == 1
|
|
212
|
+
expected = np.ones(illuminated_beam_shape) * condition.exposure_time
|
|
213
|
+
hdul = fits.open(files[0])
|
|
214
|
+
np.testing.assert_equal(expected, hdul[0].data)
|
|
215
|
+
hdul.close()
|
|
216
|
+
|
|
217
|
+
unused_time_read = task.read(
|
|
218
|
+
tags=[
|
|
219
|
+
CryonirspTag.task_dark(),
|
|
220
|
+
CryonirspTag.intermediate(),
|
|
221
|
+
CryonirspTag.frame(),
|
|
222
|
+
CryonirspTag.exposure_conditions(unused_condition),
|
|
223
|
+
]
|
|
224
|
+
)
|
|
225
|
+
assert len(list(unused_time_read)) == 0
|
|
226
|
+
|
|
227
|
+
quality_files = task.read(tags=[CryonirspTag.quality("TASK_TYPES")])
|
|
228
|
+
for file in quality_files:
|
|
229
|
+
with file.open() as f:
|
|
230
|
+
data = json.load(f)
|
|
231
|
+
assert isinstance(data, dict)
|
|
232
|
+
assert data["total_frames"] == task.scratch.count_all(
|
|
233
|
+
tags=[CryonirspTag.linearized(), CryonirspTag.frame(), CryonirspTag.task_dark()]
|
|
234
|
+
)
|
|
235
|
+
assert data["frames_not_used"] == 3
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def test_ci_dark_calibration_task(ci_dark_calibration_task, mocker):
|
|
239
|
+
"""
|
|
240
|
+
Given: A DarkCalibration task with multiple task exposure times
|
|
241
|
+
When: Calling the task instance
|
|
242
|
+
Then: Only one average intermediate dark frame exists for each exposure time and unused times are not made
|
|
243
|
+
"""
|
|
244
|
+
mocker.patch(
|
|
245
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
246
|
+
)
|
|
247
|
+
# When
|
|
248
|
+
task, exp_conditions, unused_condition = ci_dark_calibration_task
|
|
249
|
+
task()
|
|
250
|
+
# Then
|
|
251
|
+
for condition in exp_conditions:
|
|
252
|
+
files = list(
|
|
253
|
+
task.read(
|
|
254
|
+
tags=[
|
|
255
|
+
CryonirspTag.task_dark(),
|
|
256
|
+
CryonirspTag.beam(1),
|
|
257
|
+
CryonirspTag.intermediate(),
|
|
258
|
+
CryonirspTag.frame(),
|
|
259
|
+
CryonirspTag.exposure_conditions(condition),
|
|
260
|
+
]
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
assert len(files) == 1
|
|
264
|
+
expected = np.ones((10, 10)) * condition.exposure_time
|
|
265
|
+
hdul = fits.open(files[0])
|
|
266
|
+
np.testing.assert_equal(expected, hdul[0].data)
|
|
267
|
+
hdul.close()
|
|
268
|
+
|
|
269
|
+
unused_time_read = task.read(
|
|
270
|
+
tags=[
|
|
271
|
+
CryonirspTag.task_dark(),
|
|
272
|
+
CryonirspTag.intermediate(),
|
|
273
|
+
CryonirspTag.frame(),
|
|
274
|
+
CryonirspTag.exposure_conditions(unused_condition),
|
|
275
|
+
]
|
|
276
|
+
)
|
|
277
|
+
assert len(list(unused_time_read)) == 0
|
|
278
|
+
|
|
279
|
+
quality_files = task.read(tags=[CryonirspTag.quality("TASK_TYPES")])
|
|
280
|
+
for file in quality_files:
|
|
281
|
+
with file.open() as f:
|
|
282
|
+
data = json.load(f)
|
|
283
|
+
assert isinstance(data, dict)
|
|
284
|
+
assert data["total_frames"] == task.scratch.count_all(
|
|
285
|
+
tags=[CryonirspTag.linearized(), CryonirspTag.frame(), CryonirspTag.task_dark()]
|
|
286
|
+
)
|
|
287
|
+
assert data["frames_not_used"] == 3
|