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.

Files changed (111) hide show
  1. changelog/.gitempty +0 -0
  2. dkist_processing_cryonirsp/__init__.py +11 -0
  3. dkist_processing_cryonirsp/config.py +12 -0
  4. dkist_processing_cryonirsp/models/__init__.py +1 -0
  5. dkist_processing_cryonirsp/models/constants.py +248 -0
  6. dkist_processing_cryonirsp/models/exposure_conditions.py +26 -0
  7. dkist_processing_cryonirsp/models/parameters.py +296 -0
  8. dkist_processing_cryonirsp/models/tags.py +168 -0
  9. dkist_processing_cryonirsp/models/task_name.py +14 -0
  10. dkist_processing_cryonirsp/parsers/__init__.py +1 -0
  11. dkist_processing_cryonirsp/parsers/cryonirsp_l0_fits_access.py +111 -0
  12. dkist_processing_cryonirsp/parsers/cryonirsp_l1_fits_access.py +30 -0
  13. dkist_processing_cryonirsp/parsers/exposure_conditions.py +163 -0
  14. dkist_processing_cryonirsp/parsers/map_repeats.py +40 -0
  15. dkist_processing_cryonirsp/parsers/measurements.py +55 -0
  16. dkist_processing_cryonirsp/parsers/modstates.py +31 -0
  17. dkist_processing_cryonirsp/parsers/optical_density_filters.py +40 -0
  18. dkist_processing_cryonirsp/parsers/polarimetric_check.py +120 -0
  19. dkist_processing_cryonirsp/parsers/scan_step.py +412 -0
  20. dkist_processing_cryonirsp/parsers/time.py +80 -0
  21. dkist_processing_cryonirsp/parsers/wavelength.py +26 -0
  22. dkist_processing_cryonirsp/tasks/__init__.py +19 -0
  23. dkist_processing_cryonirsp/tasks/assemble_movie.py +202 -0
  24. dkist_processing_cryonirsp/tasks/bad_pixel_map.py +96 -0
  25. dkist_processing_cryonirsp/tasks/beam_boundaries_base.py +279 -0
  26. dkist_processing_cryonirsp/tasks/ci_beam_boundaries.py +55 -0
  27. dkist_processing_cryonirsp/tasks/ci_science.py +169 -0
  28. dkist_processing_cryonirsp/tasks/cryonirsp_base.py +67 -0
  29. dkist_processing_cryonirsp/tasks/dark.py +98 -0
  30. dkist_processing_cryonirsp/tasks/gain.py +251 -0
  31. dkist_processing_cryonirsp/tasks/instrument_polarization.py +447 -0
  32. dkist_processing_cryonirsp/tasks/l1_output_data.py +44 -0
  33. dkist_processing_cryonirsp/tasks/linearity_correction.py +582 -0
  34. dkist_processing_cryonirsp/tasks/make_movie_frames.py +302 -0
  35. dkist_processing_cryonirsp/tasks/mixin/__init__.py +1 -0
  36. dkist_processing_cryonirsp/tasks/mixin/beam_access.py +52 -0
  37. dkist_processing_cryonirsp/tasks/mixin/corrections.py +177 -0
  38. dkist_processing_cryonirsp/tasks/mixin/intermediate_frame.py +193 -0
  39. dkist_processing_cryonirsp/tasks/mixin/linearized_frame.py +309 -0
  40. dkist_processing_cryonirsp/tasks/mixin/shift_measurements.py +297 -0
  41. dkist_processing_cryonirsp/tasks/parse.py +281 -0
  42. dkist_processing_cryonirsp/tasks/quality_metrics.py +271 -0
  43. dkist_processing_cryonirsp/tasks/science_base.py +511 -0
  44. dkist_processing_cryonirsp/tasks/sp_beam_boundaries.py +270 -0
  45. dkist_processing_cryonirsp/tasks/sp_dispersion_axis_correction.py +484 -0
  46. dkist_processing_cryonirsp/tasks/sp_geometric.py +585 -0
  47. dkist_processing_cryonirsp/tasks/sp_science.py +299 -0
  48. dkist_processing_cryonirsp/tasks/sp_solar_gain.py +475 -0
  49. dkist_processing_cryonirsp/tasks/trial_output_data.py +61 -0
  50. dkist_processing_cryonirsp/tasks/write_l1.py +1033 -0
  51. dkist_processing_cryonirsp/tests/__init__.py +1 -0
  52. dkist_processing_cryonirsp/tests/conftest.py +456 -0
  53. dkist_processing_cryonirsp/tests/header_models.py +592 -0
  54. dkist_processing_cryonirsp/tests/local_trial_workflows/__init__.py +0 -0
  55. dkist_processing_cryonirsp/tests/local_trial_workflows/l0_cals_only.py +541 -0
  56. dkist_processing_cryonirsp/tests/local_trial_workflows/l0_to_l1.py +615 -0
  57. dkist_processing_cryonirsp/tests/local_trial_workflows/linearize_only.py +96 -0
  58. dkist_processing_cryonirsp/tests/local_trial_workflows/local_trial_helpers.py +592 -0
  59. dkist_processing_cryonirsp/tests/test_assemble_movie.py +144 -0
  60. dkist_processing_cryonirsp/tests/test_assemble_qualilty.py +517 -0
  61. dkist_processing_cryonirsp/tests/test_bad_pixel_maps.py +115 -0
  62. dkist_processing_cryonirsp/tests/test_ci_beam_boundaries.py +106 -0
  63. dkist_processing_cryonirsp/tests/test_ci_science.py +355 -0
  64. dkist_processing_cryonirsp/tests/test_corrections.py +126 -0
  65. dkist_processing_cryonirsp/tests/test_cryo_base.py +202 -0
  66. dkist_processing_cryonirsp/tests/test_cryo_constants.py +76 -0
  67. dkist_processing_cryonirsp/tests/test_dark.py +287 -0
  68. dkist_processing_cryonirsp/tests/test_gain.py +278 -0
  69. dkist_processing_cryonirsp/tests/test_instrument_polarization.py +531 -0
  70. dkist_processing_cryonirsp/tests/test_linearity_correction.py +245 -0
  71. dkist_processing_cryonirsp/tests/test_make_movie_frames.py +111 -0
  72. dkist_processing_cryonirsp/tests/test_parameters.py +266 -0
  73. dkist_processing_cryonirsp/tests/test_parse.py +1439 -0
  74. dkist_processing_cryonirsp/tests/test_quality.py +203 -0
  75. dkist_processing_cryonirsp/tests/test_sp_beam_boundaries.py +112 -0
  76. dkist_processing_cryonirsp/tests/test_sp_dispersion_axis_correction.py +155 -0
  77. dkist_processing_cryonirsp/tests/test_sp_geometric.py +319 -0
  78. dkist_processing_cryonirsp/tests/test_sp_make_movie_frames.py +121 -0
  79. dkist_processing_cryonirsp/tests/test_sp_science.py +483 -0
  80. dkist_processing_cryonirsp/tests/test_sp_solar.py +198 -0
  81. dkist_processing_cryonirsp/tests/test_trial_create_quality_report.py +79 -0
  82. dkist_processing_cryonirsp/tests/test_trial_output_data.py +251 -0
  83. dkist_processing_cryonirsp/tests/test_workflows.py +9 -0
  84. dkist_processing_cryonirsp/tests/test_write_l1.py +436 -0
  85. dkist_processing_cryonirsp/workflows/__init__.py +2 -0
  86. dkist_processing_cryonirsp/workflows/ci_l0_processing.py +77 -0
  87. dkist_processing_cryonirsp/workflows/sp_l0_processing.py +84 -0
  88. dkist_processing_cryonirsp/workflows/trial_workflows.py +190 -0
  89. dkist_processing_cryonirsp-1.3.4.dist-info/METADATA +194 -0
  90. dkist_processing_cryonirsp-1.3.4.dist-info/RECORD +111 -0
  91. dkist_processing_cryonirsp-1.3.4.dist-info/WHEEL +5 -0
  92. dkist_processing_cryonirsp-1.3.4.dist-info/top_level.txt +4 -0
  93. docs/Makefile +134 -0
  94. docs/bad_pixel_calibration.rst +47 -0
  95. docs/beam_angle_calculation.rst +53 -0
  96. docs/beam_boundary_computation.rst +88 -0
  97. docs/changelog.rst +7 -0
  98. docs/ci_science_calibration.rst +33 -0
  99. docs/conf.py +52 -0
  100. docs/index.rst +21 -0
  101. docs/l0_to_l1_cryonirsp_ci-full-trial.rst +10 -0
  102. docs/l0_to_l1_cryonirsp_ci.rst +10 -0
  103. docs/l0_to_l1_cryonirsp_sp-full-trial.rst +10 -0
  104. docs/l0_to_l1_cryonirsp_sp.rst +10 -0
  105. docs/linearization.rst +43 -0
  106. docs/make.bat +170 -0
  107. docs/requirements.txt +1 -0
  108. docs/requirements_table.rst +8 -0
  109. docs/scientific_changelog.rst +10 -0
  110. docs/sp_science_calibration.rst +59 -0
  111. 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"]