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,144 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
4
|
+
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
5
|
+
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
6
|
+
|
|
7
|
+
from dkist_processing_cryonirsp.models.constants import CryonirspBudName
|
|
8
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
9
|
+
from dkist_processing_cryonirsp.tasks.assemble_movie import AssembleCryonirspMovie
|
|
10
|
+
from dkist_processing_cryonirsp.tasks.assemble_movie import SPAssembleCryonirspMovie
|
|
11
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
12
|
+
from dkist_processing_cryonirsp.tests.conftest import generate_214_l1_fits_frame
|
|
13
|
+
from dkist_processing_cryonirsp.tests.header_models import Cryonirsp122ObserveFrames
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture(
|
|
17
|
+
scope="function", params=[pytest.param(True, id="shrink"), pytest.param(False, id="noshrink")]
|
|
18
|
+
)
|
|
19
|
+
def assemble_movie_task_with_tagged_movie_frames(
|
|
20
|
+
request,
|
|
21
|
+
tmp_path,
|
|
22
|
+
recipe_run_id,
|
|
23
|
+
init_cryonirsp_constants_db,
|
|
24
|
+
):
|
|
25
|
+
num_map_scans = 10
|
|
26
|
+
num_scan_steps = 1
|
|
27
|
+
if request.param:
|
|
28
|
+
frame_shape = (1080 * 2, 1920 * 2) # Intentionally "backward" from normal
|
|
29
|
+
expected_shape = (1080, 1920)[::-1]
|
|
30
|
+
else:
|
|
31
|
+
frame_shape = (100, 235) # Weird aspect ratio
|
|
32
|
+
expected_shape = (100, 235)[::-1]
|
|
33
|
+
init_cryonirsp_constants_db(recipe_run_id, CryonirspConstantsDb(NUM_MAP_SCANS=num_map_scans))
|
|
34
|
+
with AssembleCryonirspMovie(
|
|
35
|
+
recipe_run_id=recipe_run_id,
|
|
36
|
+
workflow_name="ci_cryo_make_movie_frames",
|
|
37
|
+
workflow_version="VX.Y",
|
|
38
|
+
) as task:
|
|
39
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
40
|
+
task.scratch = WorkflowFileSystem(
|
|
41
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
42
|
+
)
|
|
43
|
+
task.testing_num_map_scans = num_map_scans
|
|
44
|
+
task.num_steps = num_scan_steps
|
|
45
|
+
task.num_exp_per_step = 1
|
|
46
|
+
ds = Cryonirsp122ObserveFrames(
|
|
47
|
+
array_shape=(1, *frame_shape),
|
|
48
|
+
num_steps=task.num_steps,
|
|
49
|
+
num_exp_per_step=task.num_exp_per_step,
|
|
50
|
+
num_map_scans=task.testing_num_map_scans,
|
|
51
|
+
)
|
|
52
|
+
header_generator = (d.header() for d in ds)
|
|
53
|
+
data = np.random.random((1, *frame_shape))
|
|
54
|
+
for d, header in enumerate(header_generator):
|
|
55
|
+
for scan_step in range(num_scan_steps + 1):
|
|
56
|
+
hdl = generate_214_l1_fits_frame(s122_header=header, data=data)
|
|
57
|
+
task.write(
|
|
58
|
+
data=hdl,
|
|
59
|
+
tags=[
|
|
60
|
+
CryonirspTag.movie_frame(),
|
|
61
|
+
CryonirspTag.map_scan(d + 1),
|
|
62
|
+
CryonirspTag.scan_step(scan_step),
|
|
63
|
+
],
|
|
64
|
+
encoder=fits_hdulist_encoder,
|
|
65
|
+
)
|
|
66
|
+
yield task, frame_shape, expected_shape
|
|
67
|
+
finally:
|
|
68
|
+
task._purge()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_assemble_movie(assemble_movie_task_with_tagged_movie_frames, mocker):
|
|
72
|
+
mocker.patch(
|
|
73
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
74
|
+
)
|
|
75
|
+
task, _, _ = assemble_movie_task_with_tagged_movie_frames
|
|
76
|
+
task()
|
|
77
|
+
movie_file = list(task.read(tags=[CryonirspTag.movie()]))
|
|
78
|
+
assert len(movie_file) == 1
|
|
79
|
+
assert movie_file[0].exists()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_compute_frame_shape(assemble_movie_task_with_tagged_movie_frames):
|
|
83
|
+
"""
|
|
84
|
+
Given: An AssembleCryonirspMovieFrames task and OUTPUT frames
|
|
85
|
+
When: Computing the size of the final movie
|
|
86
|
+
Then: The correct size is computed: the size of an L1 output frame
|
|
87
|
+
"""
|
|
88
|
+
task, frame_shape, expected_shape = assemble_movie_task_with_tagged_movie_frames
|
|
89
|
+
|
|
90
|
+
# Task starts as polarimetric from fixture constants
|
|
91
|
+
assert task.compute_frame_shape() == tuple(expected_shape)
|
|
92
|
+
|
|
93
|
+
# Update constants to be non-polarimetric
|
|
94
|
+
del task.constants._db_dict[CryonirspBudName.num_modstates.value]
|
|
95
|
+
task.constants._db_dict[CryonirspBudName.num_modstates.value] = 1
|
|
96
|
+
assert task.compute_frame_shape() == tuple(expected_shape)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.fixture(scope="function")
|
|
100
|
+
def assemble_sp_task_with_tagged_movie_frames(tmp_path, recipe_run_id, init_cryonirsp_constants_db):
|
|
101
|
+
num_map_scans = 10
|
|
102
|
+
init_cryonirsp_constants_db(recipe_run_id, CryonirspConstantsDb(NUM_MAP_SCANS=num_map_scans))
|
|
103
|
+
with SPAssembleCryonirspMovie(
|
|
104
|
+
recipe_run_id=recipe_run_id,
|
|
105
|
+
workflow_name="sp_cryo_make_movie_frames",
|
|
106
|
+
workflow_version="VX.Y",
|
|
107
|
+
) as task:
|
|
108
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
109
|
+
task.scratch = WorkflowFileSystem(
|
|
110
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
111
|
+
)
|
|
112
|
+
task.testing_num_map_scans = num_map_scans
|
|
113
|
+
task.num_steps = 1 # do we need num_steps and num_exp_per_step for SP?
|
|
114
|
+
task.num_exp_per_step = 1
|
|
115
|
+
ds = Cryonirsp122ObserveFrames(
|
|
116
|
+
array_shape=(1, 100, 100),
|
|
117
|
+
num_steps=task.num_steps,
|
|
118
|
+
num_exp_per_step=task.num_exp_per_step,
|
|
119
|
+
num_map_scans=task.testing_num_map_scans,
|
|
120
|
+
)
|
|
121
|
+
header_generator = (d.header() for d in ds)
|
|
122
|
+
for d, header in enumerate(header_generator):
|
|
123
|
+
hdl = generate_214_l1_fits_frame(s122_header=header)
|
|
124
|
+
task.write(
|
|
125
|
+
data=hdl,
|
|
126
|
+
tags=[
|
|
127
|
+
CryonirspTag.movie_frame(),
|
|
128
|
+
CryonirspTag.map_scan(d + 1),
|
|
129
|
+
],
|
|
130
|
+
encoder=fits_hdulist_encoder,
|
|
131
|
+
)
|
|
132
|
+
yield task
|
|
133
|
+
finally:
|
|
134
|
+
task._purge()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_assemble_sp_movie(assemble_sp_task_with_tagged_movie_frames, mocker):
|
|
138
|
+
mocker.patch(
|
|
139
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
140
|
+
)
|
|
141
|
+
assemble_sp_task_with_tagged_movie_frames()
|
|
142
|
+
movie_file = list(assemble_sp_task_with_tagged_movie_frames.read(tags=[CryonirspTag.movie()]))
|
|
143
|
+
assert len(movie_file) == 1
|
|
144
|
+
assert movie_file[0].exists()
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import re
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from itertools import chain
|
|
5
|
+
from typing import Callable
|
|
6
|
+
from unittest.mock import MagicMock
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
12
|
+
from dkist_processing_common.codecs.json import json_decoder
|
|
13
|
+
from dkist_processing_common.tasks import AssembleQualityData
|
|
14
|
+
from dkist_quality.report import ReportMetric
|
|
15
|
+
from pandas import DataFrame
|
|
16
|
+
|
|
17
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
18
|
+
from dkist_processing_cryonirsp.tasks.l1_output_data import CIAssembleQualityData
|
|
19
|
+
from dkist_processing_cryonirsp.tasks.l1_output_data import SPAssembleQualityData
|
|
20
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture(scope="function", params=["CI", "SP"])
|
|
24
|
+
def cryo_assemble_quality_data_task(
|
|
25
|
+
tmp_path, recipe_run_id, init_cryonirsp_constants_db, request
|
|
26
|
+
) -> AssembleQualityData:
|
|
27
|
+
arm_id = request.param
|
|
28
|
+
init_cryonirsp_constants_db(
|
|
29
|
+
recipe_run_id=recipe_run_id, constants_obj=CryonirspConstantsDb(ARM_ID=arm_id)
|
|
30
|
+
)
|
|
31
|
+
if arm_id == "CI":
|
|
32
|
+
with CIAssembleQualityData(
|
|
33
|
+
recipe_run_id=recipe_run_id,
|
|
34
|
+
workflow_name="cryonirsp_submit_quality",
|
|
35
|
+
workflow_version="VX.Y",
|
|
36
|
+
) as task:
|
|
37
|
+
yield task, arm_id
|
|
38
|
+
task._purge()
|
|
39
|
+
elif arm_id == "SP":
|
|
40
|
+
with SPAssembleQualityData(
|
|
41
|
+
recipe_run_id=recipe_run_id,
|
|
42
|
+
workflow_name="cryonirsp_submit_quality",
|
|
43
|
+
workflow_version="VX.Y",
|
|
44
|
+
) as task:
|
|
45
|
+
yield task, arm_id
|
|
46
|
+
task._purge()
|
|
47
|
+
else:
|
|
48
|
+
raise RuntimeError(f"Invalid cryo-nirsp arm_id: {arm_id!r}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def dummy_quality_data() -> list[dict]:
|
|
53
|
+
return [{"dummy_key": "dummy_value"}]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.fixture
|
|
57
|
+
def quality_assemble_data_mock(mocker, dummy_quality_data) -> MagicMock:
|
|
58
|
+
yield mocker.patch(
|
|
59
|
+
"dkist_processing_common.tasks.mixin.quality.QualityMixin.quality_assemble_data",
|
|
60
|
+
return_value=dummy_quality_data,
|
|
61
|
+
autospec=True,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Metric:
|
|
67
|
+
value: dict | list
|
|
68
|
+
tags: list[str]
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def value_bytes(self) -> bytes:
|
|
72
|
+
return json.dumps(self.value).encode()
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def file_name(self) -> str:
|
|
76
|
+
# always include the metric in the filename
|
|
77
|
+
metric = re.sub("[ _]", "-", self.tags[0])
|
|
78
|
+
# if a second tag is present, include it in the filename
|
|
79
|
+
second_tag = re.sub("[ _]", "-", self.tags[1]) if len(self.tags) > 1 else None
|
|
80
|
+
if second_tag:
|
|
81
|
+
return f"{metric}_{second_tag}_{uuid4().hex[:6]}.dat"
|
|
82
|
+
return f"{metric}_{uuid4().hex[:6]}.dat"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.fixture()
|
|
86
|
+
def dataframe_json() -> str:
|
|
87
|
+
"""Random dataframe for raincloud_plot"""
|
|
88
|
+
nummod = 3
|
|
89
|
+
numstep = 10
|
|
90
|
+
numpoints = 100
|
|
91
|
+
points = np.random.randn(numpoints * numstep * nummod)
|
|
92
|
+
mods = np.hstack([np.arange(nummod) + 1 for i in range(numstep * numpoints)])
|
|
93
|
+
steps = np.hstack([np.arange(numstep) + 1 for i in range(nummod * numpoints)])
|
|
94
|
+
data = np.vstack((points, mods, steps)).T
|
|
95
|
+
return DataFrame(data=data, columns=["Flux residual", "Modstate", "CS Step"]).to_json()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.fixture()
|
|
99
|
+
def quality_metrics(dataframe_json) -> list[Metric]:
|
|
100
|
+
"""
|
|
101
|
+
Quality metric data
|
|
102
|
+
"""
|
|
103
|
+
metrics = [
|
|
104
|
+
Metric(
|
|
105
|
+
{
|
|
106
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
107
|
+
"y_values": [1, 2],
|
|
108
|
+
"series_name": "",
|
|
109
|
+
},
|
|
110
|
+
["QUALITY_FRAME_AVERAGE", "QUALITY_TASK_DARK"],
|
|
111
|
+
),
|
|
112
|
+
Metric(
|
|
113
|
+
{
|
|
114
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
115
|
+
"y_values": [3, 4],
|
|
116
|
+
"series_name": "",
|
|
117
|
+
},
|
|
118
|
+
["QUALITY_FRAME_AVERAGE", "QUALITY_TASK_GAIN"],
|
|
119
|
+
),
|
|
120
|
+
Metric(
|
|
121
|
+
{
|
|
122
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
123
|
+
"y_values": [5, 6],
|
|
124
|
+
"series_name": "",
|
|
125
|
+
},
|
|
126
|
+
["QUALITY_FRAME_RMS", "QUALITY_TASK_DARK"],
|
|
127
|
+
),
|
|
128
|
+
Metric(
|
|
129
|
+
{
|
|
130
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
131
|
+
"y_values": [7, 8],
|
|
132
|
+
"series_name": "",
|
|
133
|
+
},
|
|
134
|
+
["QUALITY_FRAME_RMS", "QUALITY_TASK_GAIN"],
|
|
135
|
+
),
|
|
136
|
+
Metric(
|
|
137
|
+
{"task_type": "gain", "frame_averages": [6, 7, 8, 9, 10]}, ["QUALITY_DATASET_AVERAGE"]
|
|
138
|
+
),
|
|
139
|
+
Metric(
|
|
140
|
+
{"task_type": "dark", "frame_averages": [1, 2, 3, 4, 5]}, ["QUALITY_DATASET_AVERAGE"]
|
|
141
|
+
),
|
|
142
|
+
Metric(
|
|
143
|
+
{"task_type": "dark", "frame_averages": [6, 7, 8, 9, 10]}, ["QUALITY_DATASET_AVERAGE"]
|
|
144
|
+
),
|
|
145
|
+
Metric({"task_type": "dark", "frame_rms": [1, 2, 3, 4, 5]}, ["QUALITY_DATASET_RMS"]),
|
|
146
|
+
Metric({"task_type": "dark", "frame_rms": [6, 7, 8, 8, 10]}, ["QUALITY_DATASET_RMS"]),
|
|
147
|
+
Metric({"task_type": "gain", "frame_rms": [2, 4, 6, 8, 10]}, ["QUALITY_DATASET_RMS"]),
|
|
148
|
+
Metric(
|
|
149
|
+
{
|
|
150
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
151
|
+
"y_values": [1, 2],
|
|
152
|
+
"series_name": "",
|
|
153
|
+
},
|
|
154
|
+
["QUALITY_FRIED_PARAMETER"],
|
|
155
|
+
),
|
|
156
|
+
Metric(
|
|
157
|
+
{
|
|
158
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
159
|
+
"y_values": [3, 4],
|
|
160
|
+
"series_name": "",
|
|
161
|
+
},
|
|
162
|
+
["QUALITY_LIGHT_LEVEL"],
|
|
163
|
+
),
|
|
164
|
+
Metric(["Good", "Good", "Good", "Good", "Good", "Ill"], ["QUALITY_HEALTH_STATUS"]),
|
|
165
|
+
Metric([1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0], ["QUALITY_AO_STATUS"]),
|
|
166
|
+
Metric(
|
|
167
|
+
{
|
|
168
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
169
|
+
"y_values": [5, 6],
|
|
170
|
+
"series_name": "",
|
|
171
|
+
},
|
|
172
|
+
["QUALITY_NOISE"],
|
|
173
|
+
),
|
|
174
|
+
Metric(
|
|
175
|
+
{
|
|
176
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
177
|
+
"y_values": [1, 2],
|
|
178
|
+
"series_name": "I",
|
|
179
|
+
},
|
|
180
|
+
["QUALITY_SENSITIVITY", "STOKES_I"],
|
|
181
|
+
),
|
|
182
|
+
Metric(
|
|
183
|
+
{
|
|
184
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
185
|
+
"y_values": [3, 4],
|
|
186
|
+
"series_name": "Q",
|
|
187
|
+
},
|
|
188
|
+
["QUALITY_SENSITIVITY", "STOKES_Q"],
|
|
189
|
+
),
|
|
190
|
+
Metric(
|
|
191
|
+
{
|
|
192
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
193
|
+
"y_values": [5, 6],
|
|
194
|
+
"series_name": "U",
|
|
195
|
+
},
|
|
196
|
+
["QUALITY_SENSITIVITY", "STOKES_U"],
|
|
197
|
+
),
|
|
198
|
+
Metric(
|
|
199
|
+
{
|
|
200
|
+
"x_values": ["2021-01-01T01:01:01", "2021-01-01T02:01:01"],
|
|
201
|
+
"y_values": [7, 8],
|
|
202
|
+
"series_name": "V",
|
|
203
|
+
},
|
|
204
|
+
["QUALITY_SENSITIVITY", "STOKES_V"],
|
|
205
|
+
),
|
|
206
|
+
Metric(
|
|
207
|
+
{"task_type": "dark", "total_frames": 100, "frames_not_used": 7}, ["QUALITY_TASK_TYPES"]
|
|
208
|
+
),
|
|
209
|
+
Metric(
|
|
210
|
+
{"task_type": "gain", "total_frames": 100, "frames_not_used": 0}, ["QUALITY_TASK_TYPES"]
|
|
211
|
+
),
|
|
212
|
+
Metric(
|
|
213
|
+
{
|
|
214
|
+
"param_names": ["foo"],
|
|
215
|
+
"param_vary": [True],
|
|
216
|
+
"param_init_vals": [1],
|
|
217
|
+
"param_fit_vals": [2],
|
|
218
|
+
"param_diffs": [1],
|
|
219
|
+
"param_ratios": [1],
|
|
220
|
+
"warnings": ["A warning"],
|
|
221
|
+
},
|
|
222
|
+
["QUALITY_POLCAL_GLOBAL_PAR_VALS", "QUALITY_TASK_CI BEAM 1"],
|
|
223
|
+
),
|
|
224
|
+
Metric(
|
|
225
|
+
{
|
|
226
|
+
"label": "Beam foo",
|
|
227
|
+
"modmat_list": np.random.randn(8, 4, 100).tolist(),
|
|
228
|
+
"free_param_dict": {
|
|
229
|
+
"I_sys_CS00_step00": {"fit_values": [1, 2, 3.0], "init_value": 0.3},
|
|
230
|
+
"I_sys_CS00_step01": {"fit_values": [10, 20, 30.0], "init_value": 0.33},
|
|
231
|
+
"param_X": {"fit_values": [5, 6, 7.0], "init_value": 99},
|
|
232
|
+
},
|
|
233
|
+
"bin_strs": ["bin1", "bin2"],
|
|
234
|
+
"total_bins": 100,
|
|
235
|
+
"num_varied_I_sys": 2,
|
|
236
|
+
},
|
|
237
|
+
["QUALITY_POLCAL_LOCAL_PAR_VALS", "QUALITY_TASK_CI BEAM 1"],
|
|
238
|
+
),
|
|
239
|
+
Metric(
|
|
240
|
+
{
|
|
241
|
+
"bin_strs": ["bin1", "bin2"],
|
|
242
|
+
"total_bins": 100,
|
|
243
|
+
"red_chi_list": [1, 2, 3],
|
|
244
|
+
"residual_json": dataframe_json,
|
|
245
|
+
},
|
|
246
|
+
["QUALITY_POLCAL_FIT_RESIDUALS", "QUALITY_TASK_CI BEAM 1"],
|
|
247
|
+
),
|
|
248
|
+
Metric(
|
|
249
|
+
{
|
|
250
|
+
"bin_strs": ["bin1", "bin2"],
|
|
251
|
+
"total_bins": 100,
|
|
252
|
+
"efficiency_list": ((np.random.randn(4, 100) - 0.5) * 0.3).tolist(),
|
|
253
|
+
"warnings": ["A warning"],
|
|
254
|
+
},
|
|
255
|
+
["QUALITY_POLCAL_EFFICIENCY", "QUALITY_TASK_CI BEAM 1"],
|
|
256
|
+
),
|
|
257
|
+
Metric({"name": "metric 1", "warnings": ["warning 1"]}, ["QUALITY_RANGE"]),
|
|
258
|
+
Metric({"name": "metric 2", "warnings": ["warning 2"]}, ["QUALITY_RANGE"]),
|
|
259
|
+
Metric({"name": "metric 3", "warnings": ["warning 3"]}, ["QUALITY_RANGE"]),
|
|
260
|
+
Metric({"name": "hist 1", "value": 7, "warnings": None}, ["QUALITY_HISTORICAL"]),
|
|
261
|
+
Metric({"name": "hist 2", "value": "abc", "warnings": None}, ["QUALITY_HISTORICAL"]),
|
|
262
|
+
Metric(
|
|
263
|
+
{"name": "hist 3", "value": 9.35, "warnings": "warning for historical metric 3"},
|
|
264
|
+
["QUALITY_HISTORICAL"],
|
|
265
|
+
),
|
|
266
|
+
]
|
|
267
|
+
return metrics
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@pytest.fixture()
|
|
271
|
+
def plot_data_expected() -> Callable[[str], bool]:
|
|
272
|
+
"""
|
|
273
|
+
Tightly coupled with quality_metrics fixture and resultant report metric name
|
|
274
|
+
"""
|
|
275
|
+
# names where plot_data is expected to be populated
|
|
276
|
+
names = {
|
|
277
|
+
"Average Across Frame - DARK",
|
|
278
|
+
"Average Across Frame - GAIN",
|
|
279
|
+
"Root Mean Square (RMS) Across Frame - DARK",
|
|
280
|
+
"Root Mean Square (RMS) Across Frame - GAIN",
|
|
281
|
+
"Fried Parameter",
|
|
282
|
+
"Light Level",
|
|
283
|
+
"Noise Estimation",
|
|
284
|
+
"Sensitivity",
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
def expected(name: str) -> bool:
|
|
288
|
+
return name in names
|
|
289
|
+
|
|
290
|
+
return expected
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@pytest.fixture()
|
|
294
|
+
def table_data_expected() -> Callable[[str], bool]:
|
|
295
|
+
"""
|
|
296
|
+
Tightly coupled with quality_metrics fixture and resultant report metric name
|
|
297
|
+
"""
|
|
298
|
+
# names where table_data is expected to be populated
|
|
299
|
+
names = {
|
|
300
|
+
"Average Across Dataset",
|
|
301
|
+
"Dataset RMS",
|
|
302
|
+
"Data Source Health",
|
|
303
|
+
"Frame Counts",
|
|
304
|
+
"PolCal Global Calibration Unit Fit - CI Beam 1",
|
|
305
|
+
"Historical Comparisons",
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
def expected(name: str) -> bool:
|
|
309
|
+
return name in names
|
|
310
|
+
|
|
311
|
+
return expected
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@pytest.fixture()
|
|
315
|
+
def modmat_data_expected() -> Callable[[str], bool]:
|
|
316
|
+
"""
|
|
317
|
+
Tightly coupled with quality_metrics fixture and resultant report metric name
|
|
318
|
+
"""
|
|
319
|
+
# names where modmat_data is expected to be populated
|
|
320
|
+
names = {
|
|
321
|
+
"PolCal Local Bin Fits - CI Beam 1",
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
def expected(name: str) -> bool:
|
|
325
|
+
return name in names
|
|
326
|
+
|
|
327
|
+
return expected
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@pytest.fixture()
|
|
331
|
+
def histogram_data_expected() -> Callable[[str], bool]:
|
|
332
|
+
"""
|
|
333
|
+
Tightly coupled with quality_metrics fixture and resultant report metric name
|
|
334
|
+
"""
|
|
335
|
+
# names where histogram_data is expected to be populated
|
|
336
|
+
names = {
|
|
337
|
+
"PolCal Local Bin Fits - CI Beam 1",
|
|
338
|
+
"PolCal Fit Residuals - CI Beam 1",
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
def expected(name: str) -> bool:
|
|
342
|
+
return name in names
|
|
343
|
+
|
|
344
|
+
return expected
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@pytest.fixture()
|
|
348
|
+
def raincloud_data_expected() -> Callable[[str], bool]:
|
|
349
|
+
"""
|
|
350
|
+
Tightly coupled with quality_metrics fixture and resultant report metric name
|
|
351
|
+
"""
|
|
352
|
+
# names where raincloud_data is expected to be populated
|
|
353
|
+
names = {
|
|
354
|
+
"PolCal Fit Residuals - CI Beam 1",
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
def expected(name: str) -> bool:
|
|
358
|
+
return name in names
|
|
359
|
+
|
|
360
|
+
return expected
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
@pytest.fixture()
|
|
364
|
+
def efficiency_data_expected() -> Callable[[str], bool]:
|
|
365
|
+
"""
|
|
366
|
+
Tightly coupled with quality_metrics fixture and resultant report metric name
|
|
367
|
+
"""
|
|
368
|
+
# names where efficiency_data is expected to be populated
|
|
369
|
+
names = {
|
|
370
|
+
"PolCal Modulation Efficiency - CI Beam 1",
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
def expected(name: str) -> bool:
|
|
374
|
+
return name in names
|
|
375
|
+
|
|
376
|
+
return expected
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@pytest.fixture()
|
|
380
|
+
def statement_expected() -> Callable[[str], bool]:
|
|
381
|
+
"""
|
|
382
|
+
Tightly coupled with quality_metrics fixture and resultant report metric name
|
|
383
|
+
"""
|
|
384
|
+
# names where statement is expected to be populated
|
|
385
|
+
names = {
|
|
386
|
+
"Fried Parameter",
|
|
387
|
+
"Light Level",
|
|
388
|
+
"Adaptive Optics Status",
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
def expected(name: str) -> bool:
|
|
392
|
+
return name in names
|
|
393
|
+
|
|
394
|
+
return expected
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@pytest.fixture()
|
|
398
|
+
def warnings_expected() -> Callable[[str], bool]:
|
|
399
|
+
"""
|
|
400
|
+
Tightly coupled with quality_metrics fixture and resultant report metric name
|
|
401
|
+
"""
|
|
402
|
+
# names where warnings is expected to be populated
|
|
403
|
+
names = {
|
|
404
|
+
"Data Source Health",
|
|
405
|
+
"Frame Counts",
|
|
406
|
+
"PolCal Global Calibration Unit Fit - CI Beam 1",
|
|
407
|
+
"PolCal Modulation Efficiency - CI Beam 1",
|
|
408
|
+
"Range checks",
|
|
409
|
+
"Historical Comparisons",
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
def expected(name: str) -> bool:
|
|
413
|
+
return name in names
|
|
414
|
+
|
|
415
|
+
return expected
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@pytest.fixture()
|
|
419
|
+
def scratch_with_quality_metrics(recipe_run_id, tmp_path, quality_metrics) -> WorkflowFileSystem:
|
|
420
|
+
"""Scratch instance for a recipe run id with tagged quality metrics."""
|
|
421
|
+
scratch = WorkflowFileSystem(
|
|
422
|
+
recipe_run_id=recipe_run_id,
|
|
423
|
+
scratch_base_path=tmp_path,
|
|
424
|
+
)
|
|
425
|
+
for metric in quality_metrics:
|
|
426
|
+
scratch.write(metric.value_bytes, tags=metric.tags, relative_path=metric.file_name)
|
|
427
|
+
return scratch
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@pytest.fixture
|
|
431
|
+
def assemble_quality_data_task(
|
|
432
|
+
tmp_path, recipe_run_id, scratch_with_quality_metrics, init_cryonirsp_constants_db
|
|
433
|
+
):
|
|
434
|
+
constants_db = CryonirspConstantsDb(NUM_MODSTATES=2)
|
|
435
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
436
|
+
with CIAssembleQualityData(
|
|
437
|
+
recipe_run_id=recipe_run_id,
|
|
438
|
+
workflow_name="ci_assemble_quality",
|
|
439
|
+
workflow_version="ci_assemble_quality_version",
|
|
440
|
+
) as task:
|
|
441
|
+
task.scratch = WorkflowFileSystem(
|
|
442
|
+
recipe_run_id=recipe_run_id,
|
|
443
|
+
scratch_base_path=tmp_path,
|
|
444
|
+
)
|
|
445
|
+
task.scratch = scratch_with_quality_metrics
|
|
446
|
+
yield task
|
|
447
|
+
task._purge()
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def test_assemble_quality_data(
|
|
451
|
+
assemble_quality_data_task,
|
|
452
|
+
recipe_run_id,
|
|
453
|
+
plot_data_expected,
|
|
454
|
+
table_data_expected,
|
|
455
|
+
modmat_data_expected,
|
|
456
|
+
histogram_data_expected,
|
|
457
|
+
raincloud_data_expected,
|
|
458
|
+
efficiency_data_expected,
|
|
459
|
+
statement_expected,
|
|
460
|
+
warnings_expected,
|
|
461
|
+
):
|
|
462
|
+
"""
|
|
463
|
+
:Given: An instance of CIAssembleQualityData with tagged quality metrics
|
|
464
|
+
:When: CIAssembleQualityData is run
|
|
465
|
+
:Then: A json quality data file for the dataset gets saved and tagged
|
|
466
|
+
"""
|
|
467
|
+
task = assemble_quality_data_task
|
|
468
|
+
|
|
469
|
+
# When
|
|
470
|
+
task()
|
|
471
|
+
# Then
|
|
472
|
+
# each quality_data file is a list - this will combine the elements of multiple lists into a single list
|
|
473
|
+
quality_data = list(
|
|
474
|
+
chain.from_iterable(task.read(tags=CryonirspTag.quality_data(), decoder=json_decoder))
|
|
475
|
+
)
|
|
476
|
+
# 19 with polcal
|
|
477
|
+
assert len(quality_data) == 19
|
|
478
|
+
for metric_data in quality_data:
|
|
479
|
+
rm: ReportMetric = ReportMetric.from_dict(metric_data)
|
|
480
|
+
assert isinstance(rm.name, str)
|
|
481
|
+
assert isinstance(rm.description, str)
|
|
482
|
+
if plot_data_expected(rm.name):
|
|
483
|
+
assert rm.plot_data
|
|
484
|
+
if table_data_expected(rm.name):
|
|
485
|
+
assert rm.table_data
|
|
486
|
+
if modmat_data_expected(rm.name):
|
|
487
|
+
assert rm.modmat_data
|
|
488
|
+
if histogram_data_expected(rm.name):
|
|
489
|
+
assert rm.histogram_data
|
|
490
|
+
if raincloud_data_expected(rm.name):
|
|
491
|
+
assert rm.raincloud_data
|
|
492
|
+
if efficiency_data_expected(rm.name):
|
|
493
|
+
assert rm.efficiency_data
|
|
494
|
+
if statement_expected(rm.name):
|
|
495
|
+
assert rm.statement
|
|
496
|
+
if warnings_expected(rm.name):
|
|
497
|
+
assert rm.warnings
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def test_correct_polcal_label_list(cryo_assemble_quality_data_task, quality_assemble_data_mock):
|
|
501
|
+
"""
|
|
502
|
+
Given: A CIAssembleQualityData task
|
|
503
|
+
When: Calling the task
|
|
504
|
+
Then: The correct polcal_label_list property is passed to .quality_assemble_data
|
|
505
|
+
"""
|
|
506
|
+
task, arm_id = cryo_assemble_quality_data_task
|
|
507
|
+
|
|
508
|
+
task()
|
|
509
|
+
|
|
510
|
+
if arm_id == "SP":
|
|
511
|
+
quality_assemble_data_mock.assert_called_once_with(
|
|
512
|
+
task, polcal_label_list=["SP Beam 1", "SP Beam 2"]
|
|
513
|
+
)
|
|
514
|
+
elif arm_id == "CI":
|
|
515
|
+
quality_assemble_data_mock.assert_called_once_with(task, polcal_label_list=["CI Beam 1"])
|
|
516
|
+
else:
|
|
517
|
+
raise RuntimeError(f"Invalid cryo-nirsp arm_id: {arm_id!r}")
|