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,278 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pytest
|
|
6
|
+
from astropy.io import fits
|
|
7
|
+
from dkist_header_validator import spec122_validator
|
|
8
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
9
|
+
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
10
|
+
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
11
|
+
|
|
12
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
|
|
13
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
14
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
15
|
+
from dkist_processing_cryonirsp.tasks.gain import CISolarGainCalibration
|
|
16
|
+
from dkist_processing_cryonirsp.tasks.gain import LampGainCalibration
|
|
17
|
+
from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
|
|
18
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
19
|
+
from dkist_processing_cryonirsp.tests.conftest import generate_fits_frame
|
|
20
|
+
from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidLampGainFrames
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def ci_solar_gain_calibration_task(
|
|
25
|
+
tmp_path,
|
|
26
|
+
recipe_run_id,
|
|
27
|
+
assign_input_dataset_doc_to_task,
|
|
28
|
+
init_cryonirsp_constants_db,
|
|
29
|
+
):
|
|
30
|
+
dataset_shape = (1, 10, 10)
|
|
31
|
+
array_shape = (1, 10, 10)
|
|
32
|
+
intermediate_shape = (10, 10)
|
|
33
|
+
constants_db = CryonirspConstantsDb(NUM_MODSTATES=1, ARM_ID="CI")
|
|
34
|
+
exposure_conditions = constants_db.SOLAR_GAIN_EXPOSURE_CONDITIONS_LIST[0]
|
|
35
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
36
|
+
with CISolarGainCalibration(
|
|
37
|
+
recipe_run_id=recipe_run_id,
|
|
38
|
+
workflow_name="ci_solar_gain_calibration",
|
|
39
|
+
workflow_version="VX.Y",
|
|
40
|
+
) as task:
|
|
41
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
42
|
+
task.scratch = WorkflowFileSystem(
|
|
43
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
44
|
+
)
|
|
45
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
46
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
47
|
+
# Need a beam boundary file
|
|
48
|
+
task.intermediate_frame_write_arrays(
|
|
49
|
+
arrays=np.array([0, intermediate_shape[0], 0, intermediate_shape[1]]),
|
|
50
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
51
|
+
beam=1,
|
|
52
|
+
)
|
|
53
|
+
# Create fake bad pixel map
|
|
54
|
+
task.intermediate_frame_write_arrays(
|
|
55
|
+
arrays=np.zeros(array_shape[1:]), task_tag=CryonirspTag.task_bad_pixel_map()
|
|
56
|
+
)
|
|
57
|
+
dark_signal = 3.0
|
|
58
|
+
start_time = datetime.now()
|
|
59
|
+
# Make intermediate dark frame
|
|
60
|
+
dark_cal = np.ones(intermediate_shape) * dark_signal
|
|
61
|
+
task.intermediate_frame_write_arrays(
|
|
62
|
+
arrays=dark_cal,
|
|
63
|
+
beam=1,
|
|
64
|
+
task_tag=CryonirspTag.task_dark(),
|
|
65
|
+
exposure_conditions=exposure_conditions,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
solar_signal = 6.28
|
|
69
|
+
ds = CryonirspHeadersValidLampGainFrames(
|
|
70
|
+
dataset_shape=dataset_shape,
|
|
71
|
+
array_shape=array_shape,
|
|
72
|
+
time_delta=10,
|
|
73
|
+
start_time=start_time,
|
|
74
|
+
)
|
|
75
|
+
header_generator = (
|
|
76
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
77
|
+
d.header(), return_type=fits.HDUList
|
|
78
|
+
)[0].header
|
|
79
|
+
for d in ds
|
|
80
|
+
)
|
|
81
|
+
hdul = generate_fits_frame(header_generator=header_generator, shape=array_shape)
|
|
82
|
+
|
|
83
|
+
hdul[0].data.fill(solar_signal + dark_signal)
|
|
84
|
+
tags = [
|
|
85
|
+
CryonirspTag.linearized(),
|
|
86
|
+
CryonirspTag.task_solar_gain(),
|
|
87
|
+
CryonirspTag.frame(),
|
|
88
|
+
CryonirspTag.exposure_conditions(exposure_conditions),
|
|
89
|
+
]
|
|
90
|
+
task.write(
|
|
91
|
+
data=hdul,
|
|
92
|
+
tags=tags,
|
|
93
|
+
encoder=fits_hdulist_encoder,
|
|
94
|
+
)
|
|
95
|
+
yield task, solar_signal
|
|
96
|
+
finally:
|
|
97
|
+
task._purge()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.fixture(scope="function")
|
|
101
|
+
def lamp_calibration_task(
|
|
102
|
+
tmp_path,
|
|
103
|
+
recipe_run_id,
|
|
104
|
+
assign_input_dataset_doc_to_task,
|
|
105
|
+
init_cryonirsp_constants_db,
|
|
106
|
+
number_of_beams,
|
|
107
|
+
):
|
|
108
|
+
exposure_conditions = ExposureConditions(100.0, AllowableOpticalDensityFilterNames.OPEN.value)
|
|
109
|
+
dataset_shape = (1, 10, 20)
|
|
110
|
+
array_shape = (1, 10, 20)
|
|
111
|
+
intermediate_shape = (10, 10)
|
|
112
|
+
constants_db = CryonirspConstantsDb(
|
|
113
|
+
NUM_MODSTATES=1,
|
|
114
|
+
ARM_ID="SP" if number_of_beams > 1 else "CI",
|
|
115
|
+
)
|
|
116
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
117
|
+
with LampGainCalibration(
|
|
118
|
+
recipe_run_id=recipe_run_id, workflow_name="sp_gain_calibration", workflow_version="VX.Y"
|
|
119
|
+
) as task:
|
|
120
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
121
|
+
task.scratch = WorkflowFileSystem(
|
|
122
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
123
|
+
)
|
|
124
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
125
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
126
|
+
# Need a beam boundary file
|
|
127
|
+
task.intermediate_frame_write_arrays(
|
|
128
|
+
arrays=np.array([0, intermediate_shape[0], 0, intermediate_shape[1]]),
|
|
129
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
130
|
+
beam=1,
|
|
131
|
+
)
|
|
132
|
+
# Create fake bad pixel map
|
|
133
|
+
task.intermediate_frame_write_arrays(
|
|
134
|
+
arrays=np.zeros(array_shape[1:]), task_tag=CryonirspTag.task_bad_pixel_map()
|
|
135
|
+
)
|
|
136
|
+
dark_signal = 3.0
|
|
137
|
+
start_time = datetime.now()
|
|
138
|
+
# Make intermediate dark frame
|
|
139
|
+
dark_cal = np.ones(intermediate_shape) * dark_signal
|
|
140
|
+
|
|
141
|
+
# Need a dark for each beam
|
|
142
|
+
for b in range(number_of_beams):
|
|
143
|
+
task.intermediate_frame_write_arrays(
|
|
144
|
+
arrays=dark_cal,
|
|
145
|
+
beam=b + 1,
|
|
146
|
+
task_tag=CryonirspTag.task_dark(),
|
|
147
|
+
exposure_conditions=exposure_conditions,
|
|
148
|
+
)
|
|
149
|
+
# Create fake beam border intermediate arrays
|
|
150
|
+
task.intermediate_frame_write_arrays(
|
|
151
|
+
arrays=np.array([0, 10, (b * 10), 10 + (b * 10)]),
|
|
152
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
153
|
+
beam=b + 1,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# does this need to be in the beam loop as well?
|
|
157
|
+
ds = CryonirspHeadersValidLampGainFrames(
|
|
158
|
+
dataset_shape=dataset_shape,
|
|
159
|
+
array_shape=array_shape,
|
|
160
|
+
time_delta=10,
|
|
161
|
+
start_time=start_time,
|
|
162
|
+
)
|
|
163
|
+
header_generator = (
|
|
164
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
165
|
+
d.header(), return_type=fits.HDUList
|
|
166
|
+
)[0].header
|
|
167
|
+
for d in ds
|
|
168
|
+
)
|
|
169
|
+
hdul = generate_fits_frame(
|
|
170
|
+
header_generator=header_generator, shape=array_shape
|
|
171
|
+
) # Tweak data so that beam sides are slightly different
|
|
172
|
+
# Use data != 1 to check normalization in test
|
|
173
|
+
hdul[0].data.fill(1.1)
|
|
174
|
+
tags = [
|
|
175
|
+
CryonirspTag.beam(b + 1),
|
|
176
|
+
CryonirspTag.linearized(),
|
|
177
|
+
CryonirspTag.task_lamp_gain(),
|
|
178
|
+
CryonirspTag.frame(),
|
|
179
|
+
CryonirspTag.exposure_conditions(exposure_conditions),
|
|
180
|
+
]
|
|
181
|
+
task.write(
|
|
182
|
+
data=hdul,
|
|
183
|
+
tags=tags,
|
|
184
|
+
encoder=fits_hdulist_encoder,
|
|
185
|
+
)
|
|
186
|
+
yield task
|
|
187
|
+
finally:
|
|
188
|
+
task._purge()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def test_ci_solar_gain_calibration_task(ci_solar_gain_calibration_task, mocker):
|
|
192
|
+
"""
|
|
193
|
+
Given: A CISolarGainCalibration task
|
|
194
|
+
When: Calling the task instance
|
|
195
|
+
Then: The correct number of output solar gain frames exists, are tagged correctly, and are not normalized
|
|
196
|
+
"""
|
|
197
|
+
mocker.patch(
|
|
198
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
199
|
+
)
|
|
200
|
+
# When
|
|
201
|
+
task, solar_signal = ci_solar_gain_calibration_task
|
|
202
|
+
task()
|
|
203
|
+
# Then
|
|
204
|
+
tags = [
|
|
205
|
+
CryonirspTag.task_solar_gain(),
|
|
206
|
+
CryonirspTag.intermediate(),
|
|
207
|
+
CryonirspTag.frame(),
|
|
208
|
+
CryonirspTag.beam(1),
|
|
209
|
+
]
|
|
210
|
+
files = list(task.read(tags=tags))
|
|
211
|
+
num_files = len(files)
|
|
212
|
+
assert num_files == 1 # Because only 1 beam in CI
|
|
213
|
+
|
|
214
|
+
hdu = fits.open(files[0])[0]
|
|
215
|
+
expected_results = np.ones((10, 10)) * solar_signal
|
|
216
|
+
np.testing.assert_allclose(hdu.data, expected_results)
|
|
217
|
+
|
|
218
|
+
tags = [
|
|
219
|
+
CryonirspTag.task_lamp_gain(),
|
|
220
|
+
CryonirspTag.intermediate(),
|
|
221
|
+
]
|
|
222
|
+
for filepath in task.read(tags=tags):
|
|
223
|
+
assert filepath.exists()
|
|
224
|
+
|
|
225
|
+
quality_files = task.read(tags=[CryonirspTag.quality("TASK_TYPES")])
|
|
226
|
+
for file in quality_files:
|
|
227
|
+
with file.open() as f:
|
|
228
|
+
data = json.load(f)
|
|
229
|
+
assert isinstance(data, dict)
|
|
230
|
+
assert data["total_frames"] == num_files
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@pytest.mark.parametrize("number_of_beams", [pytest.param(1, id="CI"), pytest.param(2, id="SP")])
|
|
234
|
+
def test_lamp_calibration_task(lamp_calibration_task, number_of_beams, mocker):
|
|
235
|
+
"""
|
|
236
|
+
Given: A LampGainCalibration task
|
|
237
|
+
When: Calling the task instance
|
|
238
|
+
Then: The correct number of output lamp gain frames exists, are tagged correctly, and are normalized
|
|
239
|
+
"""
|
|
240
|
+
mocker.patch(
|
|
241
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
242
|
+
)
|
|
243
|
+
# When
|
|
244
|
+
task = lamp_calibration_task
|
|
245
|
+
task()
|
|
246
|
+
# Then
|
|
247
|
+
tags = [
|
|
248
|
+
CryonirspTag.task_lamp_gain(),
|
|
249
|
+
CryonirspTag.intermediate(),
|
|
250
|
+
]
|
|
251
|
+
num_files = task.scratch.count_all(tags)
|
|
252
|
+
assert num_files == number_of_beams
|
|
253
|
+
|
|
254
|
+
for j in range(number_of_beams):
|
|
255
|
+
tags = [
|
|
256
|
+
CryonirspTag.task_lamp_gain(),
|
|
257
|
+
CryonirspTag.intermediate(),
|
|
258
|
+
CryonirspTag.beam(j + 1),
|
|
259
|
+
]
|
|
260
|
+
files = list(task.read(tags=tags))
|
|
261
|
+
assert len(files) == 1
|
|
262
|
+
hdu = fits.open(files[0])[0]
|
|
263
|
+
expected_results = np.ones((10, 10)) # Because lamps are normalized
|
|
264
|
+
np.testing.assert_allclose(hdu.data, expected_results)
|
|
265
|
+
|
|
266
|
+
tags = [
|
|
267
|
+
CryonirspTag.task_lamp_gain(),
|
|
268
|
+
CryonirspTag.intermediate(),
|
|
269
|
+
]
|
|
270
|
+
for filepath in task.read(tags=tags):
|
|
271
|
+
assert filepath.exists()
|
|
272
|
+
|
|
273
|
+
quality_files = task.read(tags=[CryonirspTag.quality("TASK_TYPES")])
|
|
274
|
+
for file in quality_files:
|
|
275
|
+
with file.open() as f:
|
|
276
|
+
data = json.load(f)
|
|
277
|
+
assert isinstance(data, dict)
|
|
278
|
+
assert data["total_frames"] == num_files
|