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,115 @@
|
|
|
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
|
+
from dkist_service_configuration.logging import logger
|
|
11
|
+
|
|
12
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
13
|
+
from dkist_processing_cryonirsp.tasks.bad_pixel_map import BadPixelMapCalibration
|
|
14
|
+
from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
|
|
15
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
16
|
+
from dkist_processing_cryonirsp.tests.conftest import generate_fits_frame
|
|
17
|
+
from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidCISolarGainFrames
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(scope="function", params=["CI", "SP"])
|
|
21
|
+
def compute_bad_pixel_map_task(
|
|
22
|
+
tmp_path,
|
|
23
|
+
recipe_run_id,
|
|
24
|
+
assign_input_dataset_doc_to_task,
|
|
25
|
+
init_cryonirsp_constants_db,
|
|
26
|
+
request,
|
|
27
|
+
):
|
|
28
|
+
arm_id = request.param
|
|
29
|
+
if arm_id == "SP":
|
|
30
|
+
dataset_shape = (1, 100, 200)
|
|
31
|
+
array_shape = (1, 100, 200)
|
|
32
|
+
else:
|
|
33
|
+
dataset_shape = (1, 100, 100)
|
|
34
|
+
array_shape = (1, 100, 100)
|
|
35
|
+
constants_db = CryonirspConstantsDb(ARM_ID=arm_id)
|
|
36
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
37
|
+
with BadPixelMapCalibration(
|
|
38
|
+
recipe_run_id=recipe_run_id,
|
|
39
|
+
workflow_name="sp_compute_bad_pixel_map",
|
|
40
|
+
workflow_version="VX.Y",
|
|
41
|
+
) as task:
|
|
42
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
43
|
+
task.scratch = WorkflowFileSystem(
|
|
44
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
45
|
+
)
|
|
46
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
47
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
48
|
+
start_time = datetime.now()
|
|
49
|
+
ds = CryonirspHeadersValidCISolarGainFrames(
|
|
50
|
+
dataset_shape=dataset_shape,
|
|
51
|
+
array_shape=array_shape,
|
|
52
|
+
time_delta=10,
|
|
53
|
+
start_time=start_time,
|
|
54
|
+
)
|
|
55
|
+
header_generator = (
|
|
56
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
57
|
+
d.header(), return_type=fits.HDUList
|
|
58
|
+
)[0].header
|
|
59
|
+
for d in ds
|
|
60
|
+
)
|
|
61
|
+
hdul = generate_fits_frame(header_generator=header_generator, shape=array_shape)
|
|
62
|
+
# Create an array with a random number of zero and hot values
|
|
63
|
+
rng = np.random.default_rng()
|
|
64
|
+
# Generate rabdomly the number of bad pixels to be used in trange(50, 100)
|
|
65
|
+
num_bad_pixels = rng.integers(50, 100)
|
|
66
|
+
# Let 2/3 of the bad pixels be hot
|
|
67
|
+
num_hot_pixels = num_bad_pixels * 2 // 3
|
|
68
|
+
# Let the remaining 1/3 be zero
|
|
69
|
+
num_zero_pixels = num_bad_pixels - num_hot_pixels
|
|
70
|
+
logger.debug(f"{num_bad_pixels = }, {num_hot_pixels = }, {num_zero_pixels = }")
|
|
71
|
+
nelem = np.prod(array_shape)
|
|
72
|
+
array = 1000.0 * np.ones(nelem)
|
|
73
|
+
# Need choice here with replace=False to avoid generating duplicates
|
|
74
|
+
bad_pixel_locs = rng.choice(nelem, size=num_bad_pixels, replace=False)
|
|
75
|
+
hot_pixel_locs = sorted(bad_pixel_locs[:num_hot_pixels])
|
|
76
|
+
zero_pixel_locs = sorted(bad_pixel_locs[num_hot_pixels:])
|
|
77
|
+
array[zero_pixel_locs] = 0.0
|
|
78
|
+
array[hot_pixel_locs] = 2000.0
|
|
79
|
+
array = array.reshape(array_shape[1:])
|
|
80
|
+
hdul[0].data = array
|
|
81
|
+
task.write(
|
|
82
|
+
data=hdul,
|
|
83
|
+
tags=[
|
|
84
|
+
CryonirspTag.linearized(),
|
|
85
|
+
CryonirspTag.task_solar_gain(),
|
|
86
|
+
CryonirspTag.frame(),
|
|
87
|
+
],
|
|
88
|
+
encoder=fits_hdulist_encoder,
|
|
89
|
+
)
|
|
90
|
+
yield task, num_zero_pixels, num_hot_pixels
|
|
91
|
+
finally:
|
|
92
|
+
task._purge()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_compute_bad_pixel_map_task(compute_bad_pixel_map_task, mocker):
|
|
96
|
+
"""
|
|
97
|
+
Given: An BadPixelMapCalibration task
|
|
98
|
+
When: Calling the task instance with known input data
|
|
99
|
+
Then: The correct beam boundary values are created and saved as intermediate files
|
|
100
|
+
"""
|
|
101
|
+
mocker.patch(
|
|
102
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
103
|
+
)
|
|
104
|
+
# Given
|
|
105
|
+
task, num_zeros, num_hot = compute_bad_pixel_map_task
|
|
106
|
+
# When
|
|
107
|
+
task()
|
|
108
|
+
# Then
|
|
109
|
+
tags = [CryonirspTag.task_bad_pixel_map()]
|
|
110
|
+
bad_pixel_map_paths = list(task.read(tags))
|
|
111
|
+
assert len(bad_pixel_map_paths) == 1
|
|
112
|
+
bad_pixel_map_hdul = fits.open(bad_pixel_map_paths[0])
|
|
113
|
+
bad_pixel_map = bad_pixel_map_hdul[0].data
|
|
114
|
+
num_bad_pixels = bad_pixel_map.sum()
|
|
115
|
+
assert num_bad_pixels == num_zeros + num_hot
|
|
@@ -0,0 +1,106 @@
|
|
|
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.ci_beam_boundaries import CIBeamBoundariesCalibration
|
|
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 CryonirspHeadersValidCISolarGainFrames
|
|
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 = "CI"
|
|
27
|
+
dataset_shape = (1, 100, 100)
|
|
28
|
+
array_shape = (1, 100, 100)
|
|
29
|
+
constants_db = CryonirspConstantsDb(ARM_ID=arm_id)
|
|
30
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
31
|
+
with CIBeamBoundariesCalibration(
|
|
32
|
+
recipe_run_id=recipe_run_id,
|
|
33
|
+
workflow_name="ci_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 = CryonirspHeadersValidCISolarGainFrames(
|
|
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, y_min:y_max, x_min:x_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
|
|
69
|
+
plus_streak_pos = 3 * array_shape[2] // 4
|
|
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 CIBeamBoundariesCalibration 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([8, 94, 4, 91])
|
|
103
|
+
files_found = list(task.read(tags=beam_1_tags))
|
|
104
|
+
assert len(files_found) == 1
|
|
105
|
+
array = fits.open(files_found[0])[0].data
|
|
106
|
+
assert np.array_equal(array, beam_1_boundary)
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import random
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pytest
|
|
7
|
+
from astropy.io import fits
|
|
8
|
+
from astropy.time import Time
|
|
9
|
+
from astropy.time import TimeDelta
|
|
10
|
+
from dkist_header_validator import spec122_validator
|
|
11
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
12
|
+
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
13
|
+
from dkist_processing_common.tests.conftest import FakeGQLClient
|
|
14
|
+
|
|
15
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
|
|
16
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
17
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspStemName
|
|
18
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
19
|
+
from dkist_processing_cryonirsp.tasks.ci_science import CIScienceCalibration
|
|
20
|
+
from dkist_processing_cryonirsp.tests.conftest import cryonirsp_testing_parameters_factory
|
|
21
|
+
from dkist_processing_cryonirsp.tests.conftest import CryonirspConstantsDb
|
|
22
|
+
from dkist_processing_cryonirsp.tests.conftest import generate_fits_frame
|
|
23
|
+
from dkist_processing_cryonirsp.tests.header_models import CryonirspHeadersValidObserveFrames
|
|
24
|
+
|
|
25
|
+
# from dkist_processing_common.models.tags import Tag
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.fixture(scope="function", params=["Full Stokes", "Stokes-I"])
|
|
29
|
+
def ci_science_calibration_task(
|
|
30
|
+
tmp_path,
|
|
31
|
+
recipe_run_id,
|
|
32
|
+
assign_input_dataset_doc_to_task,
|
|
33
|
+
init_cryonirsp_constants_db,
|
|
34
|
+
request,
|
|
35
|
+
):
|
|
36
|
+
num_map_scans = 2
|
|
37
|
+
num_scan_steps = 2
|
|
38
|
+
num_meas = 1
|
|
39
|
+
exposure_time = 0.02 # From CryonirspHeadersValidObserveFrames fixture
|
|
40
|
+
exposure_conditions = ExposureConditions(
|
|
41
|
+
exposure_time, AllowableOpticalDensityFilterNames.OPEN.value
|
|
42
|
+
)
|
|
43
|
+
if request.param == "Full Stokes":
|
|
44
|
+
num_modstates = 2
|
|
45
|
+
else:
|
|
46
|
+
num_modstates = 1
|
|
47
|
+
array_shape = (1, 20, 20)
|
|
48
|
+
intermediate_shape = array_shape[1:]
|
|
49
|
+
dataset_shape = (num_map_scans * num_scan_steps * num_modstates,) + array_shape[1:]
|
|
50
|
+
|
|
51
|
+
constants_db = CryonirspConstantsDb(
|
|
52
|
+
ARM_ID="CI",
|
|
53
|
+
NUM_MODSTATES=num_modstates,
|
|
54
|
+
NUM_MAP_SCANS=num_map_scans,
|
|
55
|
+
NUM_SCAN_STEPS=num_scan_steps,
|
|
56
|
+
NUM_BEAMS=1,
|
|
57
|
+
OBSERVE_EXPOSURE_CONDITIONS_LIST=(exposure_conditions,),
|
|
58
|
+
MODULATOR_SPIN_MODE="Continuous" if request.param == "Full Stokes" else "Off",
|
|
59
|
+
)
|
|
60
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
61
|
+
with CIScienceCalibration(
|
|
62
|
+
recipe_run_id=recipe_run_id, workflow_name="ci_science_calibration", workflow_version="VX.Y"
|
|
63
|
+
) as task:
|
|
64
|
+
try: # This try... block is here to make sure the dbs get cleaned up if there's a failure in the fixture
|
|
65
|
+
all_zeros = np.zeros(intermediate_shape)
|
|
66
|
+
all_ones = np.ones(intermediate_shape)
|
|
67
|
+
|
|
68
|
+
task.scratch = WorkflowFileSystem(
|
|
69
|
+
scratch_base_path=tmp_path, recipe_run_id=recipe_run_id
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
param_class = cryonirsp_testing_parameters_factory(param_path=tmp_path)
|
|
73
|
+
assign_input_dataset_doc_to_task(task, param_class())
|
|
74
|
+
|
|
75
|
+
# Need a beam boundary file
|
|
76
|
+
task.intermediate_frame_write_arrays(
|
|
77
|
+
arrays=np.array([0, intermediate_shape[0], 0, intermediate_shape[1]]),
|
|
78
|
+
task_tag=CryonirspTag.task_beam_boundaries(),
|
|
79
|
+
beam=1,
|
|
80
|
+
)
|
|
81
|
+
# Create fake bad pixel map
|
|
82
|
+
task.intermediate_frame_write_arrays(
|
|
83
|
+
arrays=np.zeros(array_shape[1:]), task_tag=CryonirspTag.task_bad_pixel_map()
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Create fake demodulation matrices
|
|
87
|
+
demod_matrices = np.zeros((1, 1, 4, num_modstates))
|
|
88
|
+
for modstate in range(num_modstates):
|
|
89
|
+
demod_matrices[0, 0, :, modstate] = [1, 2, 3, 4]
|
|
90
|
+
demod_hdul = fits.HDUList([fits.PrimaryHDU(data=demod_matrices)])
|
|
91
|
+
task.write(
|
|
92
|
+
data=demod_hdul,
|
|
93
|
+
tags=[
|
|
94
|
+
CryonirspTag.intermediate(),
|
|
95
|
+
CryonirspTag.frame(),
|
|
96
|
+
CryonirspTag.task_demodulation_matrices(),
|
|
97
|
+
CryonirspTag.beam(1),
|
|
98
|
+
],
|
|
99
|
+
encoder=fits_hdulist_encoder,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Create fake dark intermediate arrays
|
|
103
|
+
task.intermediate_frame_write_arrays(
|
|
104
|
+
all_zeros,
|
|
105
|
+
beam=1,
|
|
106
|
+
task_tag=CryonirspTag.task_dark(),
|
|
107
|
+
exposure_conditions=exposure_conditions,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Create fake lamp and solar gain intermediate arrays
|
|
111
|
+
for modstate in range(1, num_modstates + 1):
|
|
112
|
+
gain_hdul = fits.HDUList([fits.PrimaryHDU(data=all_ones)])
|
|
113
|
+
task.write(
|
|
114
|
+
data=gain_hdul,
|
|
115
|
+
tags=[
|
|
116
|
+
CryonirspTag.intermediate(),
|
|
117
|
+
CryonirspTag.frame(),
|
|
118
|
+
CryonirspTag.task_lamp_gain(),
|
|
119
|
+
CryonirspTag.beam(1),
|
|
120
|
+
CryonirspTag.modstate(modstate),
|
|
121
|
+
],
|
|
122
|
+
encoder=fits_hdulist_encoder,
|
|
123
|
+
)
|
|
124
|
+
task.write(
|
|
125
|
+
data=gain_hdul,
|
|
126
|
+
tags=[
|
|
127
|
+
CryonirspTag.intermediate(),
|
|
128
|
+
CryonirspTag.frame(),
|
|
129
|
+
CryonirspTag.task_solar_gain(),
|
|
130
|
+
CryonirspTag.beam(1),
|
|
131
|
+
CryonirspTag.modstate(modstate),
|
|
132
|
+
],
|
|
133
|
+
encoder=fits_hdulist_encoder,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Create fake observe arrays
|
|
137
|
+
start_time = datetime.now()
|
|
138
|
+
for map_scan in range(1, num_map_scans + 1):
|
|
139
|
+
for scan_step in range(1, num_scan_steps + 1):
|
|
140
|
+
for modstate in range(1, num_modstates + 1):
|
|
141
|
+
for meas_num in range(1, num_meas + 1):
|
|
142
|
+
ds = CryonirspHeadersValidObserveFrames(
|
|
143
|
+
dataset_shape=dataset_shape,
|
|
144
|
+
array_shape=array_shape,
|
|
145
|
+
time_delta=10,
|
|
146
|
+
scan_step=scan_step,
|
|
147
|
+
num_scan_steps=num_scan_steps,
|
|
148
|
+
num_map_scans=num_map_scans,
|
|
149
|
+
map_scan=map_scan,
|
|
150
|
+
num_modstates=num_modstates,
|
|
151
|
+
modstate=modstate,
|
|
152
|
+
start_time=start_time,
|
|
153
|
+
num_meas=num_meas,
|
|
154
|
+
meas_num=meas_num,
|
|
155
|
+
arm_id="CI",
|
|
156
|
+
)
|
|
157
|
+
header_generator = (
|
|
158
|
+
spec122_validator.validate_and_translate_to_214_l0(
|
|
159
|
+
d.header(), return_type=fits.HDUList
|
|
160
|
+
)[0].header
|
|
161
|
+
for d in ds
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
hdul = generate_fits_frame(
|
|
165
|
+
header_generator=header_generator, shape=array_shape
|
|
166
|
+
)
|
|
167
|
+
header = hdul[0].header
|
|
168
|
+
task.write(
|
|
169
|
+
data=hdul,
|
|
170
|
+
tags=[
|
|
171
|
+
CryonirspTag.task_observe(),
|
|
172
|
+
CryonirspTag.scan_step(scan_step),
|
|
173
|
+
CryonirspTag.map_scan(map_scan),
|
|
174
|
+
CryonirspTag.modstate(modstate),
|
|
175
|
+
CryonirspTag.linearized(),
|
|
176
|
+
CryonirspTag.frame(),
|
|
177
|
+
CryonirspTag.exposure_conditions(exposure_conditions),
|
|
178
|
+
CryonirspTag.meas_num(meas_num),
|
|
179
|
+
],
|
|
180
|
+
encoder=fits_hdulist_encoder,
|
|
181
|
+
)
|
|
182
|
+
yield task, request.param, header, intermediate_shape
|
|
183
|
+
finally:
|
|
184
|
+
task._purge()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@pytest.fixture(scope="session")
|
|
188
|
+
def ci_headers_with_dates() -> tuple[list[fits.Header], str, int, int]:
|
|
189
|
+
num_headers = 5
|
|
190
|
+
start_time = "1969-12-06T18:00:00"
|
|
191
|
+
exp_time = 12
|
|
192
|
+
time_delta = 10
|
|
193
|
+
ds = CryonirspHeadersValidObserveFrames(
|
|
194
|
+
dataset_shape=(num_headers, 4, 4),
|
|
195
|
+
array_shape=(1, 4, 4),
|
|
196
|
+
time_delta=time_delta,
|
|
197
|
+
num_map_scans=1,
|
|
198
|
+
map_scan=1,
|
|
199
|
+
num_scan_steps=1,
|
|
200
|
+
scan_step=1,
|
|
201
|
+
num_meas=1,
|
|
202
|
+
meas_num=1,
|
|
203
|
+
num_modstates=num_headers,
|
|
204
|
+
modstate=1,
|
|
205
|
+
start_time=datetime.fromisoformat(start_time),
|
|
206
|
+
arm_id="CI",
|
|
207
|
+
)
|
|
208
|
+
headers = [
|
|
209
|
+
spec122_validator.validate_and_translate_to_214_l0(d.header(), return_type=fits.HDUList)[
|
|
210
|
+
0
|
|
211
|
+
].header
|
|
212
|
+
for d in ds
|
|
213
|
+
]
|
|
214
|
+
random.shuffle(headers) # Shuffle to make sure they're not already in time order
|
|
215
|
+
for h in headers:
|
|
216
|
+
h["XPOSURE"] = exp_time # Exposure time, in ms
|
|
217
|
+
|
|
218
|
+
return headers, start_time, exp_time, time_delta
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@pytest.fixture(scope="session")
|
|
222
|
+
def ci_compressed_headers_with_dates(
|
|
223
|
+
ci_headers_with_dates,
|
|
224
|
+
) -> tuple[list[fits.Header], str, int, int]:
|
|
225
|
+
headers, start_time, exp_time, time_delta = ci_headers_with_dates
|
|
226
|
+
comp_headers = [fits.hdu.compressed.CompImageHeader(h, h) for h in headers]
|
|
227
|
+
return comp_headers, start_time, exp_time, time_delta
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_ci_science_calibration_task(ci_science_calibration_task, mocker):
|
|
231
|
+
"""
|
|
232
|
+
Given: A CIScienceCalibration task
|
|
233
|
+
When: Calling the task instance
|
|
234
|
+
Then: There are the expected number of science frames with the correct tags applied and the headers have been correctly updated
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
mocker.patch(
|
|
238
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=FakeGQLClient
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# When
|
|
242
|
+
task, polarization_mode, og_header, og_single_beam_shape = ci_science_calibration_task
|
|
243
|
+
task()
|
|
244
|
+
|
|
245
|
+
# 1 from re-dummification
|
|
246
|
+
expected_final_shape = (1, og_single_beam_shape[0], og_single_beam_shape[1])
|
|
247
|
+
|
|
248
|
+
# Then
|
|
249
|
+
tags = [
|
|
250
|
+
CryonirspTag.calibrated(),
|
|
251
|
+
CryonirspTag.frame(),
|
|
252
|
+
]
|
|
253
|
+
files = list(task.read(tags=tags))
|
|
254
|
+
if polarization_mode == "Full Stokes":
|
|
255
|
+
# 2 scan steps * 2 map scans * 4 stokes params = 16 frames
|
|
256
|
+
assert len(files) == 16
|
|
257
|
+
elif polarization_mode == "Stokes-I":
|
|
258
|
+
# 2 scan steps * 2 map scans * 1 stokes param = 4 frames
|
|
259
|
+
assert len(files) == 4
|
|
260
|
+
for file in files:
|
|
261
|
+
hdul = fits.open(file)
|
|
262
|
+
assert len(hdul) == 1
|
|
263
|
+
hdu = hdul[0]
|
|
264
|
+
assert type(hdul[0]) is fits.PrimaryHDU
|
|
265
|
+
assert hdu.data.shape == expected_final_shape
|
|
266
|
+
assert "DATE-BEG" in hdu.header.keys()
|
|
267
|
+
assert "DATE-END" in hdu.header.keys()
|
|
268
|
+
if polarization_mode == "Full Stokes":
|
|
269
|
+
assert "POL_NOIS" in hdu.header.keys()
|
|
270
|
+
assert "POL_SENS" in hdu.header.keys()
|
|
271
|
+
|
|
272
|
+
# Check that scan step keys were updated
|
|
273
|
+
scan_step = [
|
|
274
|
+
int(t.split("_")[-1]) for t in task.tags(file) if CryonirspStemName.scan_step.value in t
|
|
275
|
+
][0]
|
|
276
|
+
|
|
277
|
+
assert hdu.header["CNNUMSCN"] == 2
|
|
278
|
+
assert hdu.header["CNCURSCN"] == scan_step
|
|
279
|
+
|
|
280
|
+
quality_files = task.read(tags=[CryonirspTag.quality("TASK_TYPES")])
|
|
281
|
+
for file in quality_files:
|
|
282
|
+
with file.open() as f:
|
|
283
|
+
data = json.load(f)
|
|
284
|
+
assert isinstance(data, dict)
|
|
285
|
+
assert data["total_frames"] == task.scratch.count_all(
|
|
286
|
+
tags=[CryonirspTag.linearized(), CryonirspTag.frame(), CryonirspTag.task_observe()]
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def test_compute_ci_date_keys(ci_headers_with_dates, recipe_run_id, init_cryonirsp_constants_db):
|
|
291
|
+
"""
|
|
292
|
+
Given: A set of CI headers with different DATE-OBS values
|
|
293
|
+
When: Computing the time over which the headers were taken
|
|
294
|
+
Then: A header with correct DATE-BEG, DATE-END, and DATE-AVG keys is returned
|
|
295
|
+
"""
|
|
296
|
+
headers, start_time, exp_time, time_delta = ci_headers_with_dates
|
|
297
|
+
constants_db = CryonirspConstantsDb()
|
|
298
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
299
|
+
with CIScienceCalibration(
|
|
300
|
+
recipe_run_id=recipe_run_id, workflow_name="science_calibration", workflow_version="VX.Y"
|
|
301
|
+
) as task:
|
|
302
|
+
final_header = task.compute_date_keys(headers)
|
|
303
|
+
final_header_from_single = task.compute_date_keys(headers[0])
|
|
304
|
+
|
|
305
|
+
date_end = (
|
|
306
|
+
Time(start_time)
|
|
307
|
+
+ (len(headers) - 1) * TimeDelta(time_delta, format="sec")
|
|
308
|
+
+ TimeDelta(exp_time / 1000.0, format="sec")
|
|
309
|
+
).isot
|
|
310
|
+
|
|
311
|
+
assert final_header["DATE-BEG"] == start_time
|
|
312
|
+
assert final_header["DATE-END"] == date_end
|
|
313
|
+
|
|
314
|
+
date_end_from_single = (
|
|
315
|
+
Time(headers[0]["DATE-BEG"])
|
|
316
|
+
# + TimeDelta(time_delta, format="sec")
|
|
317
|
+
+ TimeDelta(exp_time / 1000.0, format="sec")
|
|
318
|
+
).isot
|
|
319
|
+
|
|
320
|
+
assert final_header_from_single["DATE-BEG"] == headers[0]["DATE-BEG"]
|
|
321
|
+
assert final_header_from_single["DATE-END"] == date_end_from_single
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def test_compute_ci_date_keys_compressed_headers(
|
|
325
|
+
ci_compressed_headers_with_dates, recipe_run_id, init_cryonirsp_constants_db
|
|
326
|
+
):
|
|
327
|
+
"""
|
|
328
|
+
Given: A set of CI compressed headers with different DATE-OBS values
|
|
329
|
+
When: Computing the time over which the headers were taken
|
|
330
|
+
Then: A header with correct DATE-BEG, DATE-END, and DATE-AVG keys is returned
|
|
331
|
+
"""
|
|
332
|
+
headers, start_time, exp_time, time_delta = ci_compressed_headers_with_dates
|
|
333
|
+
constants_db = CryonirspConstantsDb()
|
|
334
|
+
init_cryonirsp_constants_db(recipe_run_id, constants_db)
|
|
335
|
+
with CIScienceCalibration(
|
|
336
|
+
recipe_run_id=recipe_run_id, workflow_name="science_calibration", workflow_version="VX.Y"
|
|
337
|
+
) as task:
|
|
338
|
+
final_header = task.compute_date_keys(headers)
|
|
339
|
+
final_header_from_single = task.compute_date_keys(headers[0])
|
|
340
|
+
|
|
341
|
+
date_end = (
|
|
342
|
+
Time(start_time)
|
|
343
|
+
+ (len(headers) - 1) * TimeDelta(time_delta, format="sec")
|
|
344
|
+
+ TimeDelta(exp_time / 1000.0, format="sec")
|
|
345
|
+
).isot
|
|
346
|
+
|
|
347
|
+
assert final_header["DATE-BEG"] == start_time
|
|
348
|
+
assert final_header["DATE-END"] == date_end
|
|
349
|
+
|
|
350
|
+
date_end_from_single = (
|
|
351
|
+
Time(headers[0]["DATE-BEG"]) + TimeDelta(exp_time / 1000.0, format="sec")
|
|
352
|
+
).isot
|
|
353
|
+
|
|
354
|
+
assert final_header_from_single["DATE-BEG"] == headers[0]["DATE-BEG"]
|
|
355
|
+
assert final_header_from_single["DATE-END"] == date_end_from_single
|