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,297 @@
1
+ """Mixin to support array shift calculations."""
2
+ import math
3
+ from dataclasses import dataclass
4
+
5
+ import numpy as np
6
+ import skimage.registration as skir
7
+ from dkist_service_configuration.logging import logger
8
+
9
+ SPATIAL = "SPATIAL"
10
+ SPECTRAL = "SPECTRAL"
11
+ ALLOWABLE_AXES = SPATIAL, SPECTRAL
12
+
13
+
14
+ @dataclass
15
+ class AxisParams:
16
+ """
17
+ Dataclass to support shift computations along an axis.
18
+
19
+ Parameters
20
+ ----------
21
+ axis
22
+ The axis along which a shift is to be computed
23
+ long_axis
24
+ The axis number that represents the long axis of the strip
25
+ axis_0_size
26
+ The size of the numpy 0 axis
27
+ axis_1_size:
28
+ The size of the numpy 1 axis
29
+ axis_0_offset
30
+ The offset from the center of the 0 axis about which to align the center of the strip
31
+ axis_1_offset
32
+ The offset from the center of the 1 axis about which to align the center of the strip
33
+ """
34
+
35
+ axis: str
36
+ long_axis: int
37
+ axis_0_size: int
38
+ axis_1_size: int
39
+ axis_0_offset: int = 0
40
+ axis_1_offset: int = 0
41
+
42
+
43
+ class ShiftMeasurementsMixin:
44
+ """Methods to support array shift computations along a specified axis."""
45
+
46
+ def shift_measurements_compute_shift_along_axis(
47
+ self,
48
+ axis: str,
49
+ array_1: np.ndarray,
50
+ array_2: np.ndarray,
51
+ array_1_offset: tuple[int, int] = (0, 0),
52
+ array_2_offset: tuple[int, int] = (0, 0),
53
+ # Use no upsampling unless specified
54
+ upsample_factor: int = 1,
55
+ ) -> float:
56
+ """
57
+ Compute the relative shift between two images along a specified axis.
58
+
59
+ This method computes the relative shift of two images along an axis by extracting
60
+ two long, narrow strips, one from each image, computing the median along the narrow
61
+ axis, and then correlating the two signals to find the relative shift between them.
62
+
63
+ The long axis of the strip is the axis along which the shift measurement is to be made.
64
+ The narrow axis is the opposite axis (orthogonal axis) from the measurement axis.
65
+
66
+ Parameters
67
+ ----------
68
+ axis
69
+ The axis along which to measure the shift, SPATIAL or SPECTRAL
70
+ array_1
71
+ The reference, or non-moving array
72
+ array_2
73
+ The array whose shift is to be measured, or the moving array
74
+ array_1_offset
75
+ The offsets to be applied to each axis of array_1 when defining the strip slice
76
+ array_2_offset
77
+ The offsets to be applied to each axis of array_2 when defining the strip slice
78
+ upsample_factor
79
+ The upsample factor to be used when computing the correlation of the two signals.
80
+
81
+ Returns
82
+ -------
83
+ shift
84
+ The measured displacement of array_2 relative to array_1
85
+ """
86
+ # "axis" is the axis along which we compute the gradient
87
+ # SPECTRAL means along the spectral axis, axis 1 in numpy, horizontal axis
88
+ # SPATIAL means along the spatial axis, axis 0 in numpy, vertical axis
89
+
90
+ array_1_axis_params = self.shift_measurements_get_axis_params(
91
+ axis, array_1.shape, array_1_offset
92
+ )
93
+ array_2_axis_params = self.shift_measurements_get_axis_params(
94
+ axis, array_2.shape, array_2_offset
95
+ )
96
+
97
+ array_1_axis_0_slice, array_1_axis_1_slice = self.shift_measurements_get_strip_slices(
98
+ array_1_axis_params, array_num=1
99
+ )
100
+ array_2_axis_0_slice, array_2_axis_1_slice = self.shift_measurements_get_strip_slices(
101
+ array_2_axis_params, array_num=2
102
+ )
103
+
104
+ # Make sure long axis slices start and end at same point.
105
+ long_axis = array_1_axis_params.long_axis
106
+ if long_axis == 0:
107
+ new_min = max(array_1_axis_0_slice.start, array_1_axis_0_slice.start)
108
+ new_max = min(array_1_axis_0_slice.stop, array_1_axis_0_slice.stop)
109
+ long_axis_slice = slice(new_min, new_max)
110
+ array_1_strip = array_1[long_axis_slice, array_1_axis_1_slice]
111
+ array_2_strip = array_2[long_axis_slice, array_2_axis_1_slice]
112
+ else:
113
+ new_min = max(array_1_axis_1_slice.start, array_1_axis_1_slice.start)
114
+ new_max = min(array_1_axis_1_slice.stop, array_1_axis_1_slice.stop)
115
+ long_axis_slice = slice(new_min, new_max)
116
+ array_1_strip = array_1[array_1_axis_0_slice, long_axis_slice]
117
+ array_2_strip = array_2[array_2_axis_0_slice, long_axis_slice]
118
+
119
+ array_1_gradient = self.shift_measurements_compute_gradient(
120
+ array_1_strip, axis=array_1_axis_params.long_axis
121
+ )
122
+ array_2_gradient = self.shift_measurements_compute_gradient(
123
+ array_2_strip, axis=array_2_axis_params.long_axis
124
+ )
125
+
126
+ # Correlate the gradient signals to get the shift of array 2 relative to array 1
127
+ shift_array, error, phasediff = skir.phase_cross_correlation(
128
+ array_1_gradient,
129
+ array_2_gradient,
130
+ upsample_factor=upsample_factor,
131
+ )
132
+ # Flip the sign to convert from correction to measurement.
133
+ shift = -shift_array[0]
134
+ logger.info(f"Measured shift of array 2 relative to array 1 in {axis} axis = {shift}")
135
+
136
+ return shift
137
+
138
+ @staticmethod
139
+ def shift_measurements_get_axis_params(
140
+ axis: str, array_shape: tuple[int, ...], offset: tuple[int, ...]
141
+ ) -> AxisParams:
142
+ """
143
+ Populate an AxisParams dataclass with information about this axis.
144
+
145
+ Parameters
146
+ ----------
147
+ axis
148
+ The axis along which the measurement is to be made, SPATIAL or SPECTRAL
149
+ array_shape
150
+ The numpy shape tuple for the array
151
+ offset
152
+ The offset along each axis where the extracted strip is to be centered
153
+
154
+ Returns
155
+ -------
156
+ AxisParams
157
+ An AxisParams object, populated for the requested array and axis.
158
+ """
159
+ long_axis = None
160
+ if axis in ALLOWABLE_AXES:
161
+ if axis == SPECTRAL:
162
+ long_axis = 1
163
+ elif axis == SPATIAL:
164
+ long_axis = 0
165
+ return AxisParams(
166
+ axis=axis,
167
+ long_axis=long_axis,
168
+ axis_0_size=array_shape[0],
169
+ axis_1_size=array_shape[1],
170
+ axis_0_offset=offset[0],
171
+ axis_1_offset=offset[1],
172
+ )
173
+ raise ValueError(f"Unknown value for {axis = }, allowable values are {ALLOWABLE_AXES}")
174
+
175
+ def shift_measurements_get_strip_slices(
176
+ self, axis_params: AxisParams, array_num: int
177
+ ) -> tuple[slice, slice]:
178
+ """
179
+ Return the axis slices for the desired strip based on the AxisParams object.
180
+
181
+ Parameters
182
+ ----------
183
+ axis_params
184
+ The AxisParams object defining the desired strip
185
+
186
+ Returns
187
+ -------
188
+ slice, slice
189
+ A tuple of slice objects for extracting the desired srtip from the array
190
+ """
191
+ long_axis_fraction = self.parameters.geo_strip_long_axis_size_fraction
192
+ short_axis_fraction = self.parameters.geo_strip_short_axis_size_fraction
193
+
194
+ if axis_params.axis == SPECTRAL:
195
+ axis_0_fraction = short_axis_fraction
196
+ axis_1_fraction = long_axis_fraction
197
+ else:
198
+ axis_0_fraction = long_axis_fraction
199
+ axis_1_fraction = short_axis_fraction
200
+
201
+ # Compute the strip sizes
202
+ axis_0_strip_size = math.ceil(axis_0_fraction * axis_params.axis_0_size)
203
+ if not axis_0_strip_size % 2 == 0:
204
+ axis_0_strip_size += 1
205
+ axis_1_strip_size = math.ceil(axis_1_fraction * axis_params.axis_1_size)
206
+ if not axis_1_strip_size % 2 == 0:
207
+ axis_1_strip_size += 1
208
+
209
+ # compute the slices from the strip sizes
210
+ axis_0_slice_idx = (
211
+ np.array([-axis_0_strip_size, axis_0_strip_size]) + axis_params.axis_0_size
212
+ ) // 2 + axis_params.axis_0_offset
213
+ axis_1_slice_idx = (
214
+ np.array([-axis_1_strip_size, axis_1_strip_size]) + axis_params.axis_1_size
215
+ ) // 2 + axis_params.axis_1_offset
216
+
217
+ axis_0_slice = slice(*axis_0_slice_idx)
218
+ axis_1_slice = slice(*axis_1_slice_idx)
219
+
220
+ logger.info(
221
+ f"Slice results: array {array_num}, axis: {axis_params.axis}, axis 0: {axis_0_slice}, axis 1: {axis_1_slice}"
222
+ )
223
+
224
+ return axis_0_slice, axis_1_slice
225
+
226
+ def shift_measurements_compute_gradient(self, strip: np.ndarray, axis: int) -> np.ndarray:
227
+ """
228
+ Compute the gradient of the strip.
229
+
230
+ This method computes a normalized difference array (essentially a gradient) along the
231
+ long axis of the strip. The difference array is computed by subtracting a shifted version
232
+ of the strip along a desired axis. The shift is accomplished using the np.roll() method.
233
+ The difference is normalized by dividing by the sum of the strip and the shifted strip.
234
+
235
+ The gradient computation looks like this::
236
+
237
+ numerator = np.roll(strip, roll_amount, axis=axis) - np.roll(strip, -roll_amount, axis=axis)
238
+ denominator = np.roll(strip, roll_amount, axis=axis) + np.roll(strip, -roll_amount, axis=axis)
239
+ gradient = numerator / denominator
240
+
241
+ Finally, the ends are trimmed to remove edge effects from the partial overlap.
242
+
243
+ Parameters
244
+ ----------
245
+ strip
246
+ The array strip to be processed
247
+ axis
248
+ The numpy axis along which the difference is to be computed.
249
+
250
+ Returns
251
+ -------
252
+ The normalized difference array
253
+
254
+ """
255
+ roll_amount = self.parameters.geo_long_axis_gradient_displacement
256
+ numerator = np.roll(strip, roll_amount, axis=axis) - np.roll(strip, -roll_amount, axis=axis)
257
+ denominator = np.roll(strip, roll_amount, axis=axis) + np.roll(
258
+ strip, -roll_amount, axis=axis
259
+ )
260
+ gradient = numerator / denominator
261
+ # Take the median along the opposite axis from which we compute the gradient.
262
+ gradient_median = np.nanmedian(gradient, axis=self.shift_measurements_opposite_axis(axis))
263
+ # Trim the ends by twice the roll amount to remove edge effects resulting from the edge wrap.
264
+ trim_amount = 2 * roll_amount
265
+ trimmed_gradient = gradient_median[trim_amount:-trim_amount]
266
+ return trimmed_gradient
267
+
268
+ @staticmethod
269
+ def shift_measurements_opposite_axis(current_axis: int) -> int:
270
+ """
271
+ Return the opposite axis relative to the one in use.
272
+
273
+ We assume a 2D coordinate system in numpy, with axes 0 and 1
274
+ This method returns the "other" or "opposite" axis from the one being
275
+ used, which is the current_axis.
276
+
277
+ Truth table::
278
+
279
+ current axis opposite axis
280
+ 0 1
281
+ 1 0
282
+
283
+ The bitwise exclusive or (XOR) operator '^' is used to flip the axis::
284
+
285
+ 0 ^ 1 = 1
286
+ 1 ^ 1 = 0
287
+ opposite_axis = current_axis ^ 1
288
+
289
+ Parameters
290
+ ----------
291
+ current_axis
292
+
293
+ Returns
294
+ -------
295
+ int representing the opposite axis
296
+ """
297
+ return current_axis ^ 1
@@ -0,0 +1,281 @@
1
+ """Parse CryoNIRSP data."""
2
+ from typing import TypeVar
3
+
4
+ from dkist_processing_common.models.flower_pot import Stem
5
+ from dkist_processing_common.models.tags import Tag
6
+ from dkist_processing_common.models.task_name import TaskName
7
+ from dkist_processing_common.parsers.cs_step import CSStepFlower
8
+ from dkist_processing_common.parsers.cs_step import NumCSStepBud
9
+ from dkist_processing_common.parsers.near_bud import TaskNearFloatBud
10
+ from dkist_processing_common.parsers.single_value_single_key_flower import (
11
+ SingleValueSingleKeyFlower,
12
+ )
13
+ from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
14
+ from dkist_processing_common.parsers.task import PolcalTaskFlower
15
+ from dkist_processing_common.parsers.task import TaskTypeFlower
16
+ from dkist_processing_common.parsers.time import ObsIpStartTimeBud
17
+ from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
18
+ from dkist_processing_common.parsers.unique_bud import UniqueBud
19
+ from dkist_processing_common.tasks import default_constant_bud_factory
20
+ from dkist_processing_common.tasks import default_tag_flower_factory
21
+ from dkist_processing_common.tasks import ParseDataBase
22
+ from dkist_processing_common.tasks.mixin.input_dataset import InputDatasetMixin
23
+
24
+ from dkist_processing_cryonirsp.models.constants import CryonirspBudName
25
+ from dkist_processing_cryonirsp.models.parameters import CryonirspParsingParameters
26
+ from dkist_processing_cryonirsp.models.tags import CryonirspStemName
27
+ from dkist_processing_cryonirsp.models.tags import CryonirspTag
28
+ from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
29
+ from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspRampFitsAccess
30
+ from dkist_processing_cryonirsp.parsers.exposure_conditions import (
31
+ CryonirspNonDarkAndNonPolcalTaskExposureConditionsBud,
32
+ )
33
+ from dkist_processing_cryonirsp.parsers.exposure_conditions import (
34
+ CryonirspPickyDarkExposureConditionsBud,
35
+ )
36
+ from dkist_processing_cryonirsp.parsers.exposure_conditions import (
37
+ CryonirspTaskExposureConditionsBud,
38
+ )
39
+ from dkist_processing_cryonirsp.parsers.map_repeats import MapScanFlower
40
+ from dkist_processing_cryonirsp.parsers.map_repeats import NumMapScansBud
41
+ from dkist_processing_cryonirsp.parsers.measurements import MeasurementNumberFlower
42
+ from dkist_processing_cryonirsp.parsers.measurements import NumberOfMeasurementsBud
43
+ from dkist_processing_cryonirsp.parsers.modstates import ModstateNumberFlower
44
+ from dkist_processing_cryonirsp.parsers.optical_density_filters import OpticalDensityFiltersPickyBud
45
+ from dkist_processing_cryonirsp.parsers.polarimetric_check import PolarimetricCheckingUniqueBud
46
+ from dkist_processing_cryonirsp.parsers.scan_step import NumberOfScanStepsBud
47
+ from dkist_processing_cryonirsp.parsers.scan_step import ScanStepNumberFlower
48
+ from dkist_processing_cryonirsp.parsers.time import CryonirspSolarGainStartTimeBud
49
+ from dkist_processing_cryonirsp.parsers.time import CryonirspTimeObsBud
50
+ from dkist_processing_cryonirsp.parsers.wavelength import ObserveWavelengthBud
51
+
52
+
53
+ __all__ = [
54
+ "ParseL0CryonirspRampData",
55
+ "ParseL0CryonirspLinearizedData",
56
+ "ParseL0CryonirspCILinearizedData",
57
+ "ParseL0CryonirspSPLinearizedData",
58
+ ]
59
+ S = TypeVar("S", bound=Stem)
60
+
61
+
62
+ class ParseL0CryonirspRampData(ParseDataBase):
63
+ """
64
+ Parse CryoNIRSP ramp data (raw Cryo data) to prepare for Linearity Correction, after which the rest of the common parsing will occur.
65
+
66
+ Parameters
67
+ ----------
68
+ recipe_run_id : int
69
+ id of the recipe run used to identify the workflow run this task is part of
70
+ workflow_name : str
71
+ name of the workflow to which this instance of the task belongs
72
+ workflow_version : str
73
+ version of the workflow to which this instance of the task belongs
74
+
75
+ """
76
+
77
+ @property
78
+ def fits_parsing_class(self):
79
+ """FITS access class to be used with this task."""
80
+ return CryonirspRampFitsAccess
81
+
82
+ @property
83
+ def constant_buds(self) -> list[S]:
84
+ """Add CryoNIRSP specific constants to common constants."""
85
+ return [
86
+ UniqueBud(
87
+ constant_name=CryonirspBudName.camera_readout_mode.value,
88
+ metadata_key="camera_readout_mode",
89
+ ),
90
+ # Time Obs is the unique identifier for each ramp in the data set
91
+ CryonirspTimeObsBud(),
92
+ # This is used to determine which set of linearity correction tables to use.
93
+ UniqueBud(constant_name=CryonirspBudName.arm_id.value, metadata_key="arm_id"),
94
+ # Need wavelength to do filter compensation
95
+ ObserveWavelengthBud(),
96
+ # Need the optical density filter name for early failure detection
97
+ OpticalDensityFiltersPickyBud(),
98
+ # Need IP start time to support parameter access
99
+ ObsIpStartTimeBud(),
100
+ # Get the ROI 1 size and origin
101
+ UniqueBud(
102
+ constant_name=CryonirspBudName.roi_1_origin_x.value, metadata_key="roi_1_origin_x"
103
+ ),
104
+ UniqueBud(
105
+ constant_name=CryonirspBudName.roi_1_origin_y.value, metadata_key="roi_1_origin_y"
106
+ ),
107
+ UniqueBud(
108
+ constant_name=CryonirspBudName.roi_1_size_x.value, metadata_key="roi_1_size_x"
109
+ ),
110
+ UniqueBud(
111
+ constant_name=CryonirspBudName.roi_1_size_y.value, metadata_key="roi_1_size_y"
112
+ ),
113
+ ]
114
+
115
+ @property
116
+ def tag_flowers(self) -> list[S]:
117
+ """Add CryoNIRSP specific tags to common tags."""
118
+ return [
119
+ SingleValueSingleKeyFlower(
120
+ tag_stem_name=CryonirspStemName.curr_frame_in_ramp.value,
121
+ metadata_key="curr_frame_in_ramp",
122
+ ),
123
+ # time_obs is a unique identifier for all raw frames in a single ramp
124
+ SingleValueSingleKeyFlower(
125
+ tag_stem_name=CryonirspStemName.time_obs.value,
126
+ metadata_key="time_obs",
127
+ ),
128
+ ]
129
+
130
+ @property
131
+ def tags_for_input_frames(self) -> list[Tag]:
132
+ """Tags for the input data to parse."""
133
+ return [Tag.input(), Tag.frame()]
134
+
135
+
136
+ class ParseL0CryonirspLinearizedData(ParseDataBase, InputDatasetMixin):
137
+ """
138
+ Parse linearity corrected CryoNIRSP input data to add common and Cryonirsp specific constants.
139
+
140
+ Parameters
141
+ ----------
142
+ recipe_run_id : int
143
+ id of the recipe run used to identify the workflow run this task is part of
144
+ workflow_name : str
145
+ name of the workflow to which this instance of the task belongs
146
+ workflow_version : str
147
+ version of the workflow to which this instance of the task belongs
148
+
149
+ """
150
+
151
+ def __init__(
152
+ self,
153
+ recipe_run_id: int,
154
+ workflow_name: str,
155
+ workflow_version: str,
156
+ ):
157
+ super().__init__(
158
+ recipe_run_id=recipe_run_id,
159
+ workflow_name=workflow_name,
160
+ workflow_version=workflow_version,
161
+ )
162
+ self.parameters = CryonirspParsingParameters(self.input_dataset_parameters)
163
+
164
+ @property
165
+ def fits_parsing_class(self):
166
+ """FITS access class to be used in this task."""
167
+ return CryonirspL0FitsAccess
168
+
169
+ @property
170
+ def tags_for_input_frames(self) -> list[Tag]:
171
+ """Tags for the linearity corrected input frames."""
172
+ return [CryonirspTag.linearized(), CryonirspTag.frame()]
173
+
174
+ @property
175
+ def constant_buds(self) -> list[S]:
176
+ """Add CryoNIRSP specific constants to common constants."""
177
+ return default_constant_bud_factory() + [
178
+ NumMapScansBud(),
179
+ NumberOfScanStepsBud(),
180
+ NumberOfMeasurementsBud(),
181
+ CryonirspSolarGainStartTimeBud(),
182
+ NumCSStepBud(self.parameters.max_cs_step_time_sec),
183
+ CryonirspTaskExposureConditionsBud(
184
+ stem_name=CryonirspBudName.dark_frame_exposure_conditions_list.value,
185
+ ip_task_type=TaskName.dark.value,
186
+ ),
187
+ CryonirspTaskExposureConditionsBud(
188
+ stem_name=CryonirspBudName.lamp_gain_exposure_conditions_list.value,
189
+ ip_task_type=TaskName.lamp_gain.value,
190
+ ),
191
+ CryonirspTaskExposureConditionsBud(
192
+ stem_name=CryonirspBudName.solar_gain_exposure_conditions_list.value,
193
+ ip_task_type=TaskName.solar_gain.value,
194
+ ),
195
+ CryonirspTaskExposureConditionsBud(
196
+ stem_name=CryonirspBudName.observe_exposure_conditions_list.value,
197
+ ip_task_type=TaskName.observe.value,
198
+ ),
199
+ CryonirspTaskExposureConditionsBud(
200
+ stem_name=CryonirspBudName.polcal_exposure_conditions_list.value,
201
+ ip_task_type=TaskName.polcal.value,
202
+ ),
203
+ CryonirspNonDarkAndNonPolcalTaskExposureConditionsBud(),
204
+ CryonirspPickyDarkExposureConditionsBud(),
205
+ UniqueBud(constant_name=CryonirspBudName.axis_1_type.value, metadata_key="axis_1_type"),
206
+ UniqueBud(constant_name=CryonirspBudName.axis_2_type.value, metadata_key="axis_2_type"),
207
+ UniqueBud(constant_name=CryonirspBudName.axis_3_type.value, metadata_key="axis_3_type"),
208
+ PolarimetricCheckingUniqueBud(
209
+ constant_name=CryonirspBudName.num_modstates.value,
210
+ metadata_key="number_of_modulator_states",
211
+ ),
212
+ PolarimetricCheckingUniqueBud(
213
+ constant_name=CryonirspBudName.modulator_spin_mode.value,
214
+ metadata_key="modulator_spin_mode",
215
+ ),
216
+ ]
217
+
218
+ @property
219
+ def tag_flowers(self) -> list[S]:
220
+ """Add CryoNIRSP specific tags to common tags."""
221
+ return default_tag_flower_factory() + [
222
+ TaskTypeFlower(header_task_parsing_func=parse_header_ip_task_with_gains),
223
+ PolcalTaskFlower(),
224
+ MapScanFlower(),
225
+ ModstateNumberFlower(),
226
+ CSStepFlower(max_cs_step_time_sec=self.parameters.max_cs_step_time_sec),
227
+ ScanStepNumberFlower(),
228
+ MeasurementNumberFlower(),
229
+ SingleValueSingleKeyFlower(
230
+ tag_stem_name=CryonirspStemName.exposure_conditions.value,
231
+ metadata_key="exposure_conditions",
232
+ ),
233
+ ]
234
+
235
+
236
+ class ParseL0CryonirspSPLinearizedData(ParseL0CryonirspLinearizedData):
237
+ """Parse linearity corrected CryoNIRSP-SP input data with SP arm specific constants."""
238
+
239
+ @property
240
+ def constant_buds(self) -> list[S]:
241
+ """Add CryoNIRSP-SP specific constants to common constants."""
242
+ return super().constant_buds + [
243
+ TaskNearFloatBud(
244
+ constant_name=CryonirspBudName.grating_position_deg.value,
245
+ metadata_key="grating_position_deg",
246
+ ip_task_type=TaskName.solar_gain.value,
247
+ task_type_parsing_function=parse_header_ip_task_with_gains,
248
+ tolerance=0.01,
249
+ ),
250
+ TaskNearFloatBud(
251
+ constant_name=CryonirspBudName.grating_littrow_angle_deg.value,
252
+ metadata_key="grating_littrow_angle_deg",
253
+ ip_task_type=TaskName.solar_gain.value,
254
+ task_type_parsing_function=parse_header_ip_task_with_gains,
255
+ tolerance=0.01,
256
+ ),
257
+ TaskUniqueBud(
258
+ constant_name=CryonirspBudName.grating_constant.value,
259
+ metadata_key="grating_constant",
260
+ ip_task_type=TaskName.solar_gain.value,
261
+ task_type_parsing_function=parse_header_ip_task_with_gains,
262
+ ),
263
+ TaskUniqueBud(
264
+ constant_name=CryonirspBudName.wave_min.value,
265
+ metadata_key="wave_min",
266
+ ip_task_type=TaskName.solar_gain.value,
267
+ task_type_parsing_function=parse_header_ip_task_with_gains,
268
+ ),
269
+ TaskUniqueBud(
270
+ constant_name=CryonirspBudName.wave_max.value,
271
+ metadata_key="wave_max",
272
+ ip_task_type=TaskName.solar_gain.value,
273
+ task_type_parsing_function=parse_header_ip_task_with_gains,
274
+ ),
275
+ ]
276
+
277
+
278
+ class ParseL0CryonirspCILinearizedData(ParseL0CryonirspLinearizedData):
279
+ """Parse linearity corrected CryoNIRSP-CI input data with CI arm specific constants."""
280
+
281
+ pass