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 @@
1
+ """Init."""
@@ -0,0 +1,456 @@
1
+ import json
2
+ import os
3
+ from collections.abc import Iterable
4
+ from dataclasses import asdict
5
+ from dataclasses import dataclass
6
+ from dataclasses import field
7
+ from dataclasses import is_dataclass
8
+ from pathlib import Path
9
+ from random import randint
10
+ from typing import Any
11
+ from typing import Callable
12
+ from typing import Type
13
+ from typing import TypeVar
14
+
15
+ import numpy as np
16
+ import pytest
17
+ from astropy.io import fits
18
+ from astropy.time import Time
19
+ from astropy.time import TimeDelta
20
+ from dkist_data_simulator.spec122 import Spec122Dataset
21
+ from dkist_header_validator import spec122_validator
22
+ from dkist_header_validator.translator import sanitize_to_spec214_level1
23
+ from dkist_header_validator.translator import translate_spec122_to_spec214_l0
24
+ from dkist_processing_common.codecs.fits import fits_array_encoder
25
+ from dkist_processing_common.tasks import WorkflowTaskBase
26
+
27
+ from dkist_processing_cryonirsp.models.constants import CryonirspConstants
28
+ from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
29
+ from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
30
+ from dkist_processing_cryonirsp.models.parameters import CryonirspParameters
31
+ from dkist_processing_cryonirsp.models.tags import CryonirspTag
32
+ from dkist_processing_cryonirsp.tests.header_models import CryonirspCIHeaders
33
+ from dkist_processing_cryonirsp.tests.header_models import CryonirspHeaders
34
+
35
+
36
+ def generate_fits_frame(header_generator: Iterable, shape=None) -> fits.HDUList:
37
+ shape = shape or (1, 10, 10)
38
+ generated_header = next(header_generator)
39
+ translated_header = translate_spec122_to_spec214_l0(generated_header)
40
+ del translated_header["COMMENT"]
41
+ hdu = fits.PrimaryHDU(data=np.ones(shape=shape) * 150, header=fits.Header(translated_header))
42
+ return fits.HDUList([hdu])
43
+
44
+
45
+ def generate_full_cryonirsp_fits_frame(
46
+ header_generator: Iterable, data: np.ndarray | None = None
47
+ ) -> fits.HDUList:
48
+ if data is None:
49
+ data = np.ones(shape=(1, 2000, 2560))
50
+ data[0, 1000:, :] *= np.arange(1000)[:, None][::-1, :] # Make beam 2 different and flip it
51
+ generated_header = next(header_generator)
52
+ translated_header = translate_spec122_to_spec214_l0(generated_header)
53
+ del translated_header["COMMENT"]
54
+ hdu = fits.PrimaryHDU(data=data, header=fits.Header(translated_header))
55
+ return fits.HDUList([hdu])
56
+
57
+
58
+ def generate_214_l0_fits_frame(
59
+ s122_header: fits.Header | dict[str, Any], data: np.ndarray | None = None
60
+ ) -> fits.HDUList:
61
+ """Convert S122 header into 214 L0"""
62
+ if data is None:
63
+ data = np.ones((1, 10, 10))
64
+ translated_header = translate_spec122_to_spec214_l0(s122_header)
65
+ del translated_header["COMMENT"]
66
+ hdu = fits.PrimaryHDU(data=data, header=fits.Header(translated_header))
67
+ return fits.HDUList([hdu])
68
+
69
+
70
+ def generate_214_l1_fits_frame(
71
+ s122_header: fits.Header, data: np.ndarray | None = None
72
+ ) -> fits.HDUList:
73
+ """Convert S122 header into 214 L1 only.
74
+
75
+ This does NOT include populating all L1 headers, just removing 214 L0 only headers
76
+
77
+ NOTE: The stuff you care about will be in hdulist[1]
78
+ """
79
+ l0_s214_hdul = generate_214_l0_fits_frame(s122_header, data)
80
+ l0_header = l0_s214_hdul[0].header
81
+ l0_header["DNAXIS"] = 5
82
+ l0_header["DAAXES"] = 2
83
+ l0_header["DEAXES"] = 3
84
+ l1_header = sanitize_to_spec214_level1(input_headers=l0_header)
85
+ hdu = fits.CompImageHDU(header=l1_header, data=l0_s214_hdul[0].data)
86
+
87
+ return fits.HDUList([fits.PrimaryHDU(), hdu])
88
+
89
+
90
+ @pytest.fixture()
91
+ def init_cryonirsp_constants_db():
92
+ def constants_maker(recipe_run_id: int, constants_obj):
93
+ if is_dataclass(constants_obj):
94
+ constants_obj = asdict(constants_obj)
95
+ constants = CryonirspConstants(recipe_run_id=recipe_run_id, task_name="test")
96
+ constants._purge()
97
+ constants._update(constants_obj)
98
+ return
99
+
100
+ return constants_maker
101
+
102
+
103
+ @dataclass
104
+ class CryonirspConstantsDb:
105
+ OBS_IP_START_TIME: str = "1999-12-31T23:59:59"
106
+ ARM_ID: str = "SP"
107
+ NUM_MODSTATES: int = 10
108
+ NUM_MAP_SCANS: int = 2
109
+ NUM_BEAMS: int = 2
110
+ NUM_CS_STEPS: int = 18
111
+ NUM_SPECTRAL_BINS: int = 1
112
+ NUM_SPATIAL_BINS: int = 1
113
+ NUM_SCAN_STEPS: int = 1
114
+ NUM_SPATIAL_STEPS: int = 1
115
+ NUM_MEAS: int = 1
116
+ INSTRUMENT: str = "CRYO-NIRSP"
117
+ AVERAGE_CADENCE: float = 10.0
118
+ MINIMUM_CADENCE: float = 10.0
119
+ MAXIMUM_CADENCE: float = 10.0
120
+ VARIANCE_CADENCE: float = 0.0
121
+ WAVELENGTH: float = 1082.0
122
+ LAMP_GAIN_EXPOSURE_CONDITIONS_LIST: tuple[ExposureConditions, ...] = (
123
+ ExposureConditions(100.0, AllowableOpticalDensityFilterNames.OPEN.value),
124
+ )
125
+ SOLAR_GAIN_EXPOSURE_CONDITIONS_LIST: tuple[ExposureConditions, ...] = (
126
+ ExposureConditions(1.0, AllowableOpticalDensityFilterNames.OPEN.value),
127
+ )
128
+ OBSERVE_EXPOSURE_CONDITIONS_LIST: tuple[ExposureConditions, ...] = (
129
+ ExposureConditions(0.01, AllowableOpticalDensityFilterNames.OPEN.value),
130
+ )
131
+ POLCAL_EXPOSURE_CONDITIONS_LIST: tuple[ExposureConditions] | tuple = ()
132
+ NON_DARK_AND_NON_POLCAL_TASK_EXPOSURE_CONDITIONS_LIST: tuple[ExposureConditions, ...] = (
133
+ ExposureConditions(100.0, AllowableOpticalDensityFilterNames.OPEN.value),
134
+ ExposureConditions(1.0, AllowableOpticalDensityFilterNames.OPEN.value),
135
+ ExposureConditions(0.01, AllowableOpticalDensityFilterNames.OPEN.value),
136
+ )
137
+ SPECTRAL_LINE: str = "CRSP Ca II H"
138
+ MODULATOR_SPIN_MODE: str = "Continuous"
139
+ STOKES_PARAMS: tuple[str] = (
140
+ "I",
141
+ "Q",
142
+ "U",
143
+ "V",
144
+ )
145
+ TIME_OBS_LIST: tuple[str] = ()
146
+ CONTRIBUTING_PROPOSAL_IDS: tuple[str] = (
147
+ "PROPID1",
148
+ "PROPID2",
149
+ )
150
+ CONTRIBUTING_EXPERIMENT_IDS: tuple[str] = (
151
+ "EXPERID1",
152
+ "EXPERID2",
153
+ "EXPERID3",
154
+ )
155
+ # These are SP defaults...
156
+ AXIS_1_TYPE: str = "AWAV"
157
+ AXIS_2_TYPE: str = "HPLT-TAN"
158
+ AXIS_3_TYPE: str = "HPLN-TAN"
159
+ ROI_1_ORIGIN_X: int = 0
160
+ ROI_1_ORIGIN_Y: int = 0
161
+ ROI_1_SIZE_X: int = 2048
162
+ ROI_1_SIZE_Y: int = 2048
163
+ GRATING_POSITION_DEG: float = 62.505829779431224
164
+ GRATING_LITTROW_ANGLE_DEG: float = -5.5
165
+ GRATING_CONSTANT: float = 31.6
166
+ SOLAR_GAIN_IP_START_TIME: str = "2021-01-01T00:00:00"
167
+
168
+
169
+ @pytest.fixture()
170
+ def recipe_run_id():
171
+ return randint(0, 999999)
172
+
173
+
174
+ @pytest.fixture()
175
+ def cryonirsp_ci_headers() -> fits.Header:
176
+ """
177
+ A header with some common by-frame CI keywords
178
+ """
179
+ ds = CryonirspCIHeaders(dataset_shape=(2, 10, 10), array_shape=(1, 10, 10), time_delta=1)
180
+ header_list = [
181
+ spec122_validator.validate_and_translate_to_214_l0(d.header(), return_type=fits.HDUList)[
182
+ 0
183
+ ].header
184
+ for d in ds
185
+ ]
186
+
187
+ return header_list[0]
188
+
189
+
190
+ @pytest.fixture()
191
+ def calibrated_ci_cryonirsp_headers(cryonirsp_ci_headers) -> fits.Header:
192
+ """
193
+ Same as cryonirsp_ci_headers but with a DATE-END key.
194
+
195
+ Because now that's added during ScienceCal
196
+ """
197
+ cryonirsp_ci_headers["DATE-END"] = (
198
+ Time(cryonirsp_ci_headers["DATE-BEG"], format="isot", precision=6)
199
+ + TimeDelta(float(cryonirsp_ci_headers["TEXPOSUR"]) / 1000, format="sec")
200
+ ).to_value("isot")
201
+
202
+ return cryonirsp_ci_headers
203
+
204
+
205
+ @pytest.fixture()
206
+ def cryonirsp_headers() -> fits.Header:
207
+ """
208
+ A header with some common by-frame keywords
209
+ """
210
+ ds = CryonirspHeaders(dataset_shape=(2, 10, 10), array_shape=(1, 10, 10), time_delta=1)
211
+ header_list = [
212
+ spec122_validator.validate_and_translate_to_214_l0(d.header(), return_type=fits.HDUList)[
213
+ 0
214
+ ].header
215
+ for d in ds
216
+ ]
217
+
218
+ return header_list[0]
219
+
220
+
221
+ @pytest.fixture()
222
+ def calibrated_cryonirsp_headers(cryonirsp_headers) -> fits.Header:
223
+ """
224
+ Same as cryonirsp_headers but with a DATE-END key.
225
+
226
+ Because now that's added during ScienceCal
227
+ """
228
+ cryonirsp_headers["DATE-END"] = (
229
+ Time(cryonirsp_headers["DATE-BEG"], format="isot", precision=6)
230
+ + TimeDelta(float(cryonirsp_headers["TEXPOSUR"]) / 1000, format="sec")
231
+ ).to_value("isot")
232
+
233
+ return cryonirsp_headers
234
+
235
+
236
+ @dataclass
237
+ class WavelengthParameter:
238
+ values: tuple
239
+ wavelength: tuple = (1074.5, 1074.7, 1079.8, 1083) # This must always be in order
240
+
241
+ def __hash__(self):
242
+ return hash((self.values, self.wavelength))
243
+
244
+
245
+ @dataclass
246
+ class FileParameter:
247
+ """For parameters that are files on disk."""
248
+
249
+ param_path: str
250
+ is_file: bool = True
251
+ objectKey: str = "not_used_because_its_already_converted"
252
+ bucket: str = "not_used_because_we_dont_transfer"
253
+ # Note: we have these as already-parsed file parameter (i.e., no "__file__") mainly because it allows us to have
254
+ # parameter files that are outside of the workflow basepath (where they would not be able to be tagged with
255
+ # PARAMETER_FILE). This is a pattern that we see in grogu testing.
256
+ # A downside of this approach is that we are slightly more fragile to changes in the underlying __file__ parsing
257
+ # in `*-common`. Apologies to any future devs who run into this problem. To fix it you'll need to make
258
+ # downstream fixtures aware of the actual files so they can be tagged prior to the instantiation of the
259
+ # Parameter object on some Task.
260
+
261
+
262
+ # These constants are used to prevent name errors in _create_parameter_files
263
+ # and in CryonirspTestingParameters
264
+ LINEARIZATION_THRESHOLDS_CI = "cryonirsp_linearization_thresholds_ci.npy"
265
+ LINEARIZATION_THRESHOLDS_SP = "cryonirsp_linearization_thresholds_sp.npy"
266
+ SOLAR_ATLAS = "cryonirsp_solar_atlas.npy"
267
+ TELLURIC_ATLAS = "cryonirsp_telluric_atlas.npy"
268
+
269
+
270
+ def _create_parameter_files(param_path: Path) -> None:
271
+ # linearization thresholds
272
+ thresh = np.ones((10, 10), dtype=np.float64) * 100.0
273
+ np.save(os.path.join(param_path, LINEARIZATION_THRESHOLDS_CI), thresh)
274
+ np.save(os.path.join(param_path, LINEARIZATION_THRESHOLDS_SP), thresh)
275
+
276
+ # solar and telluric atlases
277
+ atlas_wavelengths = range(300, 16600)
278
+ atlas_transmission = np.random.rand(16300)
279
+ atlas = [atlas_wavelengths, atlas_transmission]
280
+ np.save(os.path.join(param_path, SOLAR_ATLAS), atlas)
281
+ np.save(os.path.join(param_path, TELLURIC_ATLAS), atlas)
282
+
283
+
284
+ TestingParameters = TypeVar("TestingParameters", bound="CryonirspTestingParameters")
285
+
286
+
287
+ def cryonirsp_testing_parameters_factory(
288
+ param_path: Path | str = "", create_files: bool = True
289
+ ) -> TestingParameters:
290
+ """Create the InputDatasetParameterValue objects and write the parameter files."""
291
+ if isinstance(param_path, str):
292
+ param_path = Path(param_path)
293
+ absolute_path = param_path.absolute()
294
+
295
+ if create_files:
296
+ _create_parameter_files(absolute_path)
297
+
298
+ @dataclass
299
+ class CryonirspTestingParameters:
300
+ cryonirsp_polcal_num_spatial_bins: int = 1
301
+ cryonirsp_polcal_num_spectral_bins: int = 1
302
+ cryonirsp_polcal_pac_fit_mode: str = "use_M12_I_sys_per_step"
303
+ cryonirsp_polcal_pac_init_set: str = "OCCal_VIS"
304
+ cryonirsp_geo_upsample_factor: int = 100
305
+ cryonirsp_geo_max_shift: int = 80
306
+ cryonirsp_geo_poly_fit_order: int = 3
307
+ cryonirsp_geo_long_axis_gradient_displacement: int = 4
308
+ cryonirsp_geo_strip_long_axis_size_fraction: float = 0.8
309
+ cryonirsp_geo_strip_short_axis_size_fraction: float = 0.1
310
+ cryonirsp_geo_strip_spectral_offset_size_fraction: float = 0.25
311
+ cryonirsp_solar_characteristic_spatial_normalization_percentile: float = 80.0
312
+ cryonirsp_max_cs_step_time_sec: float = 180.0
313
+ cryonirsp_beam_boundaries_smoothing_disk_size: int = 3
314
+ cryonirsp_beam_boundaries_upsample_factor: int = 10
315
+ cryonirsp_beam_boundaries_sp_beam_transition_region_size_fraction: float = 0.05
316
+ cryonirsp_bad_pixel_map_median_filter_size_sp: list[int] = field(
317
+ default_factory=lambda: [20, 1]
318
+ )
319
+ cryonirsp_bad_pixel_map_median_filter_size_ci: list[int] = field(
320
+ default_factory=lambda: [5, 5]
321
+ )
322
+ cryonirsp_bad_pixel_map_threshold_factor: float = 5.0
323
+ cryonirsp_corrections_bad_pixel_median_filter_size: int = 8
324
+ cryonirsp_corrections_bad_pixel_fraction_threshold: float = 0.03
325
+ cryonirsp_fringe_correction_on: bool = True
326
+ cryonirsp_fringe_correction_spectral_filter_size: list[int] = field(
327
+ default_factory=lambda: [1, 20]
328
+ )
329
+ cryonirsp_fringe_correction_spatial_filter_size: list[int] = field(
330
+ default_factory=lambda: [5, 1]
331
+ )
332
+ cryonirsp_fringe_correction_lowpass_cutoff_period: float = 40.0
333
+ cryonirsp_linearization_thresholds_ci: FileParameter = field(
334
+ default_factory=lambda: FileParameter(
335
+ param_path=str(absolute_path / LINEARIZATION_THRESHOLDS_CI)
336
+ )
337
+ )
338
+ cryonirsp_linearization_polyfit_coeffs_ci: list[float] = field(
339
+ default_factory=lambda: [1.0, 0.0, 0.0, 0.0]
340
+ )
341
+ cryonirsp_linearization_thresholds_sp: FileParameter = field(
342
+ default_factory=lambda: FileParameter(
343
+ param_path=str(absolute_path / LINEARIZATION_THRESHOLDS_SP)
344
+ )
345
+ )
346
+ cryonirsp_linearization_polyfit_coeffs_sp: list[float] = field(
347
+ default_factory=lambda: [1.0, 0.0, 0.0, 0.0]
348
+ )
349
+ cryonirsp_linearization_max_memory_gb: float = 4.0
350
+ cryonirsp_linearization_optical_density_filter_attenuation_g278: WavelengthParameter = (
351
+ WavelengthParameter(values=(-1.64, -1.64, -1.64, -1.64))
352
+ )
353
+ cryonirsp_linearization_optical_density_filter_attenuation_g358: WavelengthParameter = (
354
+ WavelengthParameter(values=(-3.75, -3.75, -3.75, -3.75))
355
+ )
356
+ cryonirsp_linearization_optical_density_filter_attenuation_g408: WavelengthParameter = (
357
+ WavelengthParameter(values=(-4.26, -4.26, -4.26, -4.26))
358
+ )
359
+ cryonirsp_solar_atlas: FileParameter = field(
360
+ default_factory=lambda: FileParameter(param_path=str(absolute_path / SOLAR_ATLAS))
361
+ )
362
+ cryonirsp_telluric_atlas: FileParameter = field(
363
+ default_factory=lambda: FileParameter(param_path=str(absolute_path / TELLURIC_ATLAS))
364
+ )
365
+ cryonirsp_camera_mirror_focal_length_mm: float = 932.0
366
+ cryonirsp_pixel_pitch_micron: float = 18.0
367
+
368
+ return CryonirspTestingParameters
369
+
370
+
371
+ @pytest.fixture(scope="session")
372
+ def testing_wavelength() -> float:
373
+ return 1079.6
374
+
375
+
376
+ @pytest.fixture(scope="session")
377
+ def testing_obs_ip_start_time() -> str:
378
+ return "1946-11-20T12:34:56"
379
+
380
+
381
+ @pytest.fixture(scope="session")
382
+ def input_dataset_document_simple_parameters_part():
383
+ def get_input_dataset_parameters_part(parameters):
384
+ parameters_list = []
385
+
386
+ value_id = randint(1000, 2000)
387
+ for pn, pv in asdict(parameters).items():
388
+ values = [
389
+ {
390
+ "parameterValueId": value_id,
391
+ "parameterValue": json.dumps(pv),
392
+ "parameterValueStartDate": "1946-11-20", # Remember Duane Allman
393
+ }
394
+ ]
395
+ parameter = {"parameterName": pn, "parameterValues": values}
396
+ parameters_list.append(parameter)
397
+ return parameters_list
398
+
399
+ return get_input_dataset_parameters_part
400
+
401
+
402
+ @pytest.fixture(scope="session")
403
+ def assign_input_dataset_doc_to_task(
404
+ input_dataset_document_simple_parameters_part,
405
+ testing_wavelength,
406
+ testing_obs_ip_start_time,
407
+ ):
408
+ def update_task(
409
+ task,
410
+ parameters,
411
+ parameter_class=CryonirspParameters,
412
+ arm_id: str = "SP",
413
+ obs_ip_start_time=testing_obs_ip_start_time,
414
+ ):
415
+ doc_path = task.scratch.workflow_base_path / "dataset_doc.json"
416
+ with open(doc_path, "w") as f:
417
+ f.write(json.dumps(input_dataset_document_simple_parameters_part(parameters)))
418
+ task.tag(doc_path, CryonirspTag.input_dataset_parameters())
419
+ task.parameters = parameter_class(
420
+ task.input_dataset_parameters,
421
+ wavelength=testing_wavelength,
422
+ arm_id=arm_id,
423
+ obs_ip_start_time=obs_ip_start_time,
424
+ )
425
+
426
+ return update_task
427
+
428
+
429
+ def _write_frames_to_task(
430
+ task: Type[WorkflowTaskBase],
431
+ frame_generator: Spec122Dataset,
432
+ change_translated_headers: Callable[[fits.Header], fits.Header] = lambda x: x,
433
+ tag_ramp_frames: Callable[[fits.Header], list[str]] = lambda x: [],
434
+ extra_tags: list[str] | None = None,
435
+ tag_func: Callable[[CryonirspHeaders], list[str]] = lambda x: [],
436
+ ):
437
+ if not extra_tags:
438
+ extra_tags = []
439
+ tags = [CryonirspTag.frame()] + extra_tags
440
+
441
+ num_frames = 0
442
+
443
+ frame = next(frame_generator)
444
+
445
+ header = frame.header()
446
+ data = frame.data
447
+ frame_tags = tags + tag_func(frame)
448
+ translated_header = fits.Header(translate_spec122_to_spec214_l0(header))
449
+ translated_header = change_translated_headers(translated_header)
450
+ ramp_tags = tag_ramp_frames(translated_header)
451
+ frame_tags = frame_tags + ramp_tags
452
+
453
+ task.write(data=data, header=translated_header, tags=frame_tags, encoder=fits_array_encoder)
454
+ num_frames += 1
455
+
456
+ return num_frames