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,203 @@
|
|
|
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_array_encoder
|
|
9
|
+
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
10
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
11
|
+
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
12
|
+
|
|
13
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
14
|
+
from dkist_processing_cryonirsp.tasks.quality_metrics import CryonirspL0QualityMetrics
|
|
15
|
+
from dkist_processing_cryonirsp.tasks.quality_metrics import CryonirspL1QualityMetrics
|
|
16
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
17
|
+
from dkist_processing_cryonirsp.tests.conftest import generate_214_l1_fits_frame
|
|
18
|
+
from dkist_processing_cryonirsp.tests.header_models import Cryonirsp122ObserveFrames
|
|
19
|
+
from dkist_processing_cryonirsp.tests.header_models import CryonirspHeaders
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def cryonirsp_l0_quality_task(recipe_run_id, num_modstates, init_cryonirsp_constants_db, tmp_path):
|
|
24
|
+
constants = CryonirspConstantsDb(
|
|
25
|
+
NUM_MODSTATES=num_modstates,
|
|
26
|
+
MODULATOR_SPIN_MODE="Stepped" if num_modstates > 1 else "Off",
|
|
27
|
+
)
|
|
28
|
+
init_cryonirsp_constants_db(recipe_run_id, constants)
|
|
29
|
+
|
|
30
|
+
with CryonirspL0QualityMetrics(
|
|
31
|
+
recipe_run_id=recipe_run_id,
|
|
32
|
+
workflow_name="workflow_name",
|
|
33
|
+
workflow_version="workflow_version",
|
|
34
|
+
) as task:
|
|
35
|
+
task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
|
|
36
|
+
|
|
37
|
+
yield task
|
|
38
|
+
task._purge()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.fixture()
|
|
42
|
+
def l0_quality_task_types() -> list[str]:
|
|
43
|
+
# The tasks types we want to build l0 metrics for
|
|
44
|
+
return [TaskName.lamp_gain.value, TaskName.dark.value]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture()
|
|
48
|
+
def dataset_task_types(l0_quality_task_types) -> list[str]:
|
|
49
|
+
# The task types that exist in the dataset. I.e., a larger set than we want to build metrics for.
|
|
50
|
+
return l0_quality_task_types + [TaskName.solar_gain.value, TaskName.observe.value]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def write_l0_task_frames_to_task(num_modstates, dataset_task_types):
|
|
55
|
+
array_shape = (1, 4, 4)
|
|
56
|
+
data = np.ones(array_shape)
|
|
57
|
+
|
|
58
|
+
def writer(task):
|
|
59
|
+
for task_type in dataset_task_types:
|
|
60
|
+
ds = CryonirspHeaders(
|
|
61
|
+
dataset_shape=(num_modstates,) + array_shape[-2:],
|
|
62
|
+
array_shape=array_shape,
|
|
63
|
+
time_delta=1.0,
|
|
64
|
+
file_schema="level0_spec214",
|
|
65
|
+
)
|
|
66
|
+
for modstate, frame in enumerate(ds, start=1):
|
|
67
|
+
header = frame.header()
|
|
68
|
+
task.write(
|
|
69
|
+
data=data,
|
|
70
|
+
header=header,
|
|
71
|
+
tags=[
|
|
72
|
+
CryonirspTag.linearized(),
|
|
73
|
+
CryonirspTag.frame(),
|
|
74
|
+
CryonirspTag.task(task_type),
|
|
75
|
+
CryonirspTag.modstate(modstate),
|
|
76
|
+
],
|
|
77
|
+
encoder=fits_array_encoder,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return writer
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.fixture(scope="function")
|
|
84
|
+
def cryo_l1_quality_task(tmp_path, recipe_run_id, init_cryonirsp_constants_db):
|
|
85
|
+
num_map_scans = 3
|
|
86
|
+
num_scan_steps = 1
|
|
87
|
+
constants_db = CryonirspConstantsDb(
|
|
88
|
+
NUM_MAP_SCANS=num_map_scans,
|
|
89
|
+
NUM_SCAN_STEPS=num_scan_steps,
|
|
90
|
+
)
|
|
91
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
92
|
+
with CryonirspL1QualityMetrics(
|
|
93
|
+
recipe_run_id=recipe_run_id, workflow_name="science_calibration", workflow_version="VX.Y"
|
|
94
|
+
) as task:
|
|
95
|
+
task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path)
|
|
96
|
+
|
|
97
|
+
# Create fake stokes frames
|
|
98
|
+
for map_scan in range(1, num_map_scans + 1):
|
|
99
|
+
for scan_step in range(1, num_scan_steps + 1):
|
|
100
|
+
for stokes_param, index in zip(("I", "Q", "U", "V"), (1, 2, 3, 4)):
|
|
101
|
+
ds = Cryonirsp122ObserveFrames(
|
|
102
|
+
array_shape=(1, 10, 10),
|
|
103
|
+
num_steps=num_scan_steps,
|
|
104
|
+
num_map_scans=num_map_scans,
|
|
105
|
+
)
|
|
106
|
+
header_generator = (
|
|
107
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
108
|
+
d.header(), return_type=fits.HDUList
|
|
109
|
+
)[0].header
|
|
110
|
+
for d in ds
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
hdul = generate_214_l1_fits_frame(s122_header=next(header_generator))
|
|
114
|
+
hdul[1].header["DINDEX5"] = index
|
|
115
|
+
task.write(
|
|
116
|
+
data=hdul,
|
|
117
|
+
tags=[
|
|
118
|
+
CryonirspTag.calibrated(),
|
|
119
|
+
CryonirspTag.frame(),
|
|
120
|
+
CryonirspTag.stokes(stokes_param),
|
|
121
|
+
CryonirspTag.scan_step(scan_step),
|
|
122
|
+
CryonirspTag.map_scan(map_scan),
|
|
123
|
+
CryonirspTag.meas_num(1),
|
|
124
|
+
],
|
|
125
|
+
encoder=fits_hdulist_encoder,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
yield task
|
|
129
|
+
task._purge()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.parametrize(
|
|
133
|
+
"num_modstates", [pytest.param(4, id="polarimetric"), pytest.param(1, id="intensity")]
|
|
134
|
+
)
|
|
135
|
+
def test_l0_quality_task(
|
|
136
|
+
cryonirsp_l0_quality_task, num_modstates, write_l0_task_frames_to_task, l0_quality_task_types
|
|
137
|
+
):
|
|
138
|
+
"""
|
|
139
|
+
Given: A `CryonirspL0QualityMetrics` task and some INPUT frames tagged with their task type and modstate
|
|
140
|
+
When: Running the task
|
|
141
|
+
Then: The expected L0 quality metric files exist
|
|
142
|
+
"""
|
|
143
|
+
# NOTE: We rely on the unit tests in `*-common` to verify the correct format/data of the metric files
|
|
144
|
+
task = cryonirsp_l0_quality_task
|
|
145
|
+
write_l0_task_frames_to_task(task)
|
|
146
|
+
|
|
147
|
+
task()
|
|
148
|
+
|
|
149
|
+
task_metric_names = ["FRAME_RMS", "FRAME_AVERAGE"]
|
|
150
|
+
for metric_name in task_metric_names:
|
|
151
|
+
for modstate in range(1, num_modstates + 1):
|
|
152
|
+
for task_type in l0_quality_task_types:
|
|
153
|
+
tags = [CryonirspTag.quality(metric_name), CryonirspTag.quality_task(task_type)]
|
|
154
|
+
if num_modstates > 1:
|
|
155
|
+
tags.append(CryonirspTag.modstate(modstate))
|
|
156
|
+
files = list(task.read(tags))
|
|
157
|
+
assert len(files) == 1
|
|
158
|
+
|
|
159
|
+
global_metric_names = ["DATASET_AVERAGE", "DATASET_RMS"]
|
|
160
|
+
for metric_name in global_metric_names:
|
|
161
|
+
files = list(task.read(tags=[CryonirspTag.quality(metric_name)]))
|
|
162
|
+
assert len(files) > 0
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_l1_quality_task(cryo_l1_quality_task, mocker):
|
|
166
|
+
"""
|
|
167
|
+
Given: A CryonirspL1QualityMetrics task
|
|
168
|
+
When: Calling the task instance
|
|
169
|
+
Then: A single sensitivity measurement and datetime is recorded for each map scan for each Stokes Q, U, and V,
|
|
170
|
+
and a single noise measurement and datetime is recorded for L1 file for each Stokes Q, U, and V
|
|
171
|
+
"""
|
|
172
|
+
mocker.patch(
|
|
173
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
174
|
+
)
|
|
175
|
+
# When
|
|
176
|
+
task = cryo_l1_quality_task
|
|
177
|
+
task()
|
|
178
|
+
# Then
|
|
179
|
+
num_map_scans = task.constants.num_map_scans
|
|
180
|
+
num_steps = task.constants.num_scan_steps
|
|
181
|
+
sensitivity_files = list(task.read(tags=[CryonirspTag.quality("SENSITIVITY")]))
|
|
182
|
+
assert len(sensitivity_files) == 4
|
|
183
|
+
for file in sensitivity_files:
|
|
184
|
+
with file.open() as f:
|
|
185
|
+
data = json.load(f)
|
|
186
|
+
assert isinstance(data, dict)
|
|
187
|
+
for time in data["x_values"]:
|
|
188
|
+
assert isinstance(time, str)
|
|
189
|
+
for sensitivity in data["y_values"]:
|
|
190
|
+
assert isinstance(sensitivity, float)
|
|
191
|
+
assert len(data["x_values"]) == len(data["y_values"]) == num_map_scans
|
|
192
|
+
|
|
193
|
+
noise_files = list(task.read(tags=[CryonirspTag.quality("NOISE")]))
|
|
194
|
+
assert len(noise_files) == 4
|
|
195
|
+
for file in noise_files:
|
|
196
|
+
with file.open() as f:
|
|
197
|
+
data = json.load(f)
|
|
198
|
+
assert isinstance(data, dict)
|
|
199
|
+
for time in data["x_values"]:
|
|
200
|
+
assert isinstance(time, str)
|
|
201
|
+
for noise in data["y_values"]:
|
|
202
|
+
assert isinstance(noise, float)
|
|
203
|
+
assert len(data["x_values"]) == len(data["y_values"]) == num_map_scans * num_steps
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from astropy.io import fits
|
|
6
|
+
from dkist_header_validator import spec122_validator
|
|
7
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
8
|
+
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
9
|
+
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
10
|
+
|
|
11
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
12
|
+
from dkist_processing_cryonirsp.tasks.sp_beam_boundaries import SPBeamBoundariesCalibration
|
|
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_fits_frame
|
|
16
|
+
from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidSPSolarGainFrames
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture(scope="function")
|
|
20
|
+
def compute_beam_boundaries_task(
|
|
21
|
+
tmp_path,
|
|
22
|
+
recipe_run_id,
|
|
23
|
+
assign_input_dataset_doc_to_task,
|
|
24
|
+
init_cryonirsp_constants_db,
|
|
25
|
+
):
|
|
26
|
+
arm_id = "SP"
|
|
27
|
+
dataset_shape = (1, 100, 200)
|
|
28
|
+
array_shape = (1, 100, 200)
|
|
29
|
+
constants_db = CryonirspConstantsDb(ARM_ID=arm_id)
|
|
30
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
31
|
+
with SPBeamBoundariesCalibration(
|
|
32
|
+
recipe_run_id=recipe_run_id,
|
|
33
|
+
workflow_name="sp_compute_beam_boundaries",
|
|
34
|
+
workflow_version="VX.Y",
|
|
35
|
+
) as task:
|
|
36
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
37
|
+
task.scratch = WorkflowFileSystem(
|
|
38
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
39
|
+
)
|
|
40
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
41
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
42
|
+
# Create fake bad pixel map
|
|
43
|
+
task.intermediate_frame_write_arrays(
|
|
44
|
+
arrays=np.zeros(array_shape[1:]),
|
|
45
|
+
task_tag=CryonirspTag.task_bad_pixel_map(),
|
|
46
|
+
)
|
|
47
|
+
start_time = datetime.now()
|
|
48
|
+
ds = CryonirspHeadersValidSPSolarGainFrames(
|
|
49
|
+
dataset_shape=dataset_shape,
|
|
50
|
+
array_shape=array_shape,
|
|
51
|
+
time_delta=10,
|
|
52
|
+
start_time=start_time,
|
|
53
|
+
)
|
|
54
|
+
header_generator = (
|
|
55
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
56
|
+
d.header(), return_type=fits.HDUList
|
|
57
|
+
)[0].header
|
|
58
|
+
for d in ds
|
|
59
|
+
)
|
|
60
|
+
hdul = generate_fits_frame(header_generator=header_generator, shape=array_shape)
|
|
61
|
+
# Tweak data to form a beam illumination pattern
|
|
62
|
+
# Data from generate_fits_frame are value 150
|
|
63
|
+
array = hdul[0].data
|
|
64
|
+
# Initial illumination borders that are made up. Precise border depends on the algorithm.
|
|
65
|
+
# [0:0, v_min:v_max, h_min:h_max]
|
|
66
|
+
array[:, 7:-5, 3:-8] = 1000.0
|
|
67
|
+
# Put some large vertical streaks in the image to help the shift measurement converge
|
|
68
|
+
minus_streak_pos = array_shape[2] // 4 - 1
|
|
69
|
+
plus_streak_pos = 3 * array_shape[2] // 4 + 1
|
|
70
|
+
array[:, :, minus_streak_pos - 5 : minus_streak_pos + 5] += 100
|
|
71
|
+
array[:, :, plus_streak_pos - 5 : plus_streak_pos + 5] += 100
|
|
72
|
+
hdul[0].data = array
|
|
73
|
+
task.write(
|
|
74
|
+
data=hdul,
|
|
75
|
+
tags=[
|
|
76
|
+
CryonirspTag.linearized(),
|
|
77
|
+
CryonirspTag.task_solar_gain(),
|
|
78
|
+
CryonirspTag.frame(),
|
|
79
|
+
],
|
|
80
|
+
encoder=fits_hdulist_encoder,
|
|
81
|
+
)
|
|
82
|
+
yield task, arm_id
|
|
83
|
+
finally:
|
|
84
|
+
task._purge()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_compute_beam_boundaries_task(compute_beam_boundaries_task, mocker):
|
|
88
|
+
"""
|
|
89
|
+
Given: A SPBeamBoundariesCalibration task
|
|
90
|
+
When: Calling the task instance with known input data
|
|
91
|
+
Then: The correct beam boundary values are created and saved as intermediate files
|
|
92
|
+
"""
|
|
93
|
+
mocker.patch(
|
|
94
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
95
|
+
)
|
|
96
|
+
# Given
|
|
97
|
+
task, arm_id = compute_beam_boundaries_task
|
|
98
|
+
# When
|
|
99
|
+
task()
|
|
100
|
+
# Then
|
|
101
|
+
beam_1_tags = [CryonirspTag.task_beam_boundaries(), CryonirspTag.beam(1)]
|
|
102
|
+
beam_1_boundary = np.array([7, 94, 10, 88])
|
|
103
|
+
beam_2_tags = [CryonirspTag.task_beam_boundaries(), CryonirspTag.beam(2)]
|
|
104
|
+
beam_2_boundary = np.array([8, 95, 112, 190])
|
|
105
|
+
files_found = list(task.read(tags=beam_1_tags))
|
|
106
|
+
assert len(files_found) == 1
|
|
107
|
+
array1 = fits.open(files_found[0])[0].data
|
|
108
|
+
assert np.array_equal(array1, beam_1_boundary)
|
|
109
|
+
files_found = list(task.read(tags=beam_2_tags))
|
|
110
|
+
assert len(files_found) == 1
|
|
111
|
+
array2 = fits.open(files_found[0])[0].data
|
|
112
|
+
assert np.array_equal(array2, beam_2_boundary)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from astropy import units as u
|
|
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.asdf import asdf_decoder
|
|
10
|
+
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
11
|
+
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
12
|
+
|
|
13
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
14
|
+
from dkist_processing_cryonirsp.tasks.sp_dispersion_axis_correction import (
|
|
15
|
+
SPDispersionAxisCorrection,
|
|
16
|
+
)
|
|
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 CryonirspHeadersValidSPSolarGainFrames
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture(scope="function")
|
|
24
|
+
def sp_dispersion_axis_correction_task(
|
|
25
|
+
tmp_path,
|
|
26
|
+
recipe_run_id,
|
|
27
|
+
assign_input_dataset_doc_to_task,
|
|
28
|
+
init_cryonirsp_constants_db,
|
|
29
|
+
):
|
|
30
|
+
num_map_scans = 2
|
|
31
|
+
num_beams = 2
|
|
32
|
+
num_scan_steps = 2
|
|
33
|
+
num_modstates = 2
|
|
34
|
+
array_shape = (1, 30, 60)
|
|
35
|
+
char_spec_shape = (916,)
|
|
36
|
+
grating_position_deg = 62.505829779431224
|
|
37
|
+
grating_littrow_angle = -5.5
|
|
38
|
+
grating_constant = 31.6
|
|
39
|
+
dataset_shape = (num_beams * num_map_scans * num_scan_steps * num_modstates,) + array_shape[1:]
|
|
40
|
+
|
|
41
|
+
constants_db = CryonirspConstantsDb(
|
|
42
|
+
NUM_BEAMS=num_beams,
|
|
43
|
+
GRATING_POSITION_DEG=grating_position_deg,
|
|
44
|
+
GRATING_LITTROW_ANGLE_DEG=grating_littrow_angle,
|
|
45
|
+
GRATING_CONSTANT=grating_constant,
|
|
46
|
+
SOLAR_GAIN_IP_START_TIME="2021-01-01T00:00:00",
|
|
47
|
+
)
|
|
48
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
49
|
+
with SPDispersionAxisCorrection(
|
|
50
|
+
recipe_run_id=recipe_run_id,
|
|
51
|
+
workflow_name="sp_dispersion_axis_correction",
|
|
52
|
+
workflow_version="VX.Y",
|
|
53
|
+
) as task:
|
|
54
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
55
|
+
all_ones = np.ones(char_spec_shape)
|
|
56
|
+
task.scratch = WorkflowFileSystem(
|
|
57
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
58
|
+
)
|
|
59
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
60
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
61
|
+
|
|
62
|
+
# Create fake solar charcteristic spectra
|
|
63
|
+
for beam in range(1, num_beams + 1):
|
|
64
|
+
char_spec_hdul = fits.HDUList([fits.PrimaryHDU(data=all_ones)])
|
|
65
|
+
task.write(
|
|
66
|
+
data=char_spec_hdul,
|
|
67
|
+
tags=[
|
|
68
|
+
CryonirspTag.intermediate(),
|
|
69
|
+
CryonirspTag.frame(),
|
|
70
|
+
CryonirspTag.task_characteristic_spectra(),
|
|
71
|
+
CryonirspTag.beam(beam),
|
|
72
|
+
],
|
|
73
|
+
encoder=fits_hdulist_encoder,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# And a beam border intermediate array
|
|
77
|
+
task.intermediate_frame_write_arrays(
|
|
78
|
+
arrays=np.array([0, 30, ((beam - 1) * 30), (30 + (beam - 1) * 30)]),
|
|
79
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
80
|
+
beam=beam,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Create fake linearized solar gain array with headers
|
|
84
|
+
start_time = datetime.now()
|
|
85
|
+
ds = CryonirspHeadersValidSPSolarGainFrames(
|
|
86
|
+
dataset_shape=dataset_shape,
|
|
87
|
+
array_shape=array_shape,
|
|
88
|
+
time_delta=10,
|
|
89
|
+
start_time=start_time,
|
|
90
|
+
)
|
|
91
|
+
header_generator = (
|
|
92
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
93
|
+
d.header(), return_type=fits.HDUList
|
|
94
|
+
)[0].header
|
|
95
|
+
for d in ds
|
|
96
|
+
)
|
|
97
|
+
hdul = generate_fits_frame(header_generator=header_generator, shape=array_shape)
|
|
98
|
+
header = hdul[0].header
|
|
99
|
+
task.write(
|
|
100
|
+
data=hdul,
|
|
101
|
+
tags=[
|
|
102
|
+
CryonirspTag.linearized(),
|
|
103
|
+
CryonirspTag.frame(),
|
|
104
|
+
CryonirspTag.task_solar_gain(),
|
|
105
|
+
CryonirspTag.beam(beam),
|
|
106
|
+
],
|
|
107
|
+
encoder=fits_hdulist_encoder,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
yield task, header
|
|
111
|
+
except:
|
|
112
|
+
raise
|
|
113
|
+
finally:
|
|
114
|
+
task._purge()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_sp_dispersion_axis_correction(sp_dispersion_axis_correction_task, mocker):
|
|
118
|
+
"""
|
|
119
|
+
Given: A SPDispersionAxisCorrection task
|
|
120
|
+
When: Calling the task instance
|
|
121
|
+
Then: There are the expected number of intermediate fit frames with the correct tags applied and the values have been correctly fit
|
|
122
|
+
"""
|
|
123
|
+
mocker.patch(
|
|
124
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
task, original_header = sp_dispersion_axis_correction_task
|
|
128
|
+
task()
|
|
129
|
+
tags = [
|
|
130
|
+
CryonirspTag.task_spectral_fit(),
|
|
131
|
+
CryonirspTag.intermediate(),
|
|
132
|
+
]
|
|
133
|
+
fit_result = next(task.read(tags=tags, decoder=asdf_decoder))
|
|
134
|
+
del fit_result["asdf_library"]
|
|
135
|
+
del fit_result["history"]
|
|
136
|
+
|
|
137
|
+
expected_wavelength_vector = task.compute_expected_wavelength_vector(
|
|
138
|
+
np.random.rand(100), original_header
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
(
|
|
142
|
+
telluric_atlas_wave,
|
|
143
|
+
telluric_atlas_trans,
|
|
144
|
+
solar_atlas_wave_air,
|
|
145
|
+
solar_atlas_trans_flipped,
|
|
146
|
+
) = task.load_and_resample_fts_atlas(expected_wavelength_vector)
|
|
147
|
+
|
|
148
|
+
assert len(telluric_atlas_wave) == len(telluric_atlas_trans)
|
|
149
|
+
assert len(solar_atlas_wave_air) == len(solar_atlas_trans_flipped)
|
|
150
|
+
|
|
151
|
+
# make sure that the values have changed
|
|
152
|
+
assert original_header["CRVAL1"] != fit_result["CRVAL1"]
|
|
153
|
+
assert original_header["CDELT1"] != fit_result["CDELT1"]
|
|
154
|
+
assert original_header["CRVAL1A"] != fit_result["CRVAL1A"]
|
|
155
|
+
assert original_header["CDELT1A"] != fit_result["CDELT1A"]
|