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,271 @@
1
+ """Cryonirsp quality metrics task."""
2
+ from dataclasses import dataclass
3
+ from dataclasses import field
4
+ from typing import Generator
5
+ from typing import Iterable
6
+
7
+ import numpy as np
8
+ from astropy.time import Time
9
+ from dkist_processing_common.codecs.fits import fits_access_decoder
10
+ from dkist_processing_common.parsers.quality import L1QualityFitsAccess
11
+ from dkist_processing_common.tasks import QualityL0Metrics
12
+ from dkist_processing_common.tasks.mixin.quality import QualityMixin
13
+ from dkist_service_configuration.logging import logger
14
+
15
+ from dkist_processing_cryonirsp.models.constants import CryonirspConstants
16
+ from dkist_processing_cryonirsp.models.tags import CryonirspTag
17
+ from dkist_processing_cryonirsp.tasks.cryonirsp_base import CryonirspTaskBase
18
+
19
+ __all__ = ["CryonirspL0QualityMetrics", "CryonirspL1QualityMetrics"]
20
+
21
+
22
+ @dataclass
23
+ class _QualityDataPoint:
24
+ """Class for storage of a single Cryonirsp quality data point in a time series."""
25
+
26
+ datetime: str | int # isot | mjd
27
+ value: float
28
+
29
+
30
+ @dataclass
31
+ class _QualityData:
32
+ """Class for storage of Cryonirsp time series quality data."""
33
+
34
+ data_points: list[_QualityDataPoint] = field(default_factory=list)
35
+
36
+ @property
37
+ def datetimes(self) -> list[str | int]:
38
+ """Parse datetimes from list of data points."""
39
+ return [dp.datetime for dp in self.data_points]
40
+
41
+ @property
42
+ def values(self) -> list[float]:
43
+ """Parse values from list of data points."""
44
+ return [dp.value for dp in self.data_points]
45
+
46
+ def __len__(self):
47
+ return len(self.data_points)
48
+
49
+
50
+ class CryonirspL0QualityMetrics(QualityL0Metrics):
51
+ """
52
+ Task class for collection of Cryonirsp L0 specific quality metrics.
53
+
54
+ Parameters
55
+ ----------
56
+ recipe_run_id : int
57
+ id of the recipe run used to identify the workflow run this task is part of
58
+ workflow_name : str
59
+ name of the workflow to which this instance of the task belongs
60
+ workflow_version : str
61
+ version of the workflow to which this instance of the task belongs
62
+
63
+ """
64
+
65
+ @property
66
+ def constants_model_class(self):
67
+ """Class for Cryonirsp constants."""
68
+ return CryonirspConstants
69
+
70
+ @property
71
+ def raw_frame_tag(self) -> str:
72
+ """
73
+ Define tag corresponding to L0 data.
74
+
75
+ For Cryo it's LINEARIZED.
76
+ """
77
+ return CryonirspTag.linearized()
78
+
79
+ @property
80
+ def modstate_list(self) -> Iterable[int] | None:
81
+ """
82
+ Define the list of modstates over which to compute L0 quality metrics.
83
+
84
+ If the dataset is non-polarimetric then we just compute all metrics over all modstates at once.
85
+ """
86
+ if self.constants.correct_for_polarization:
87
+ return range(1, self.constants.num_modstates + 1)
88
+
89
+ return None
90
+
91
+
92
+ class CryonirspL1QualityMetrics(CryonirspTaskBase, QualityMixin):
93
+ """
94
+ Task class for collection of Cryonirsp L1 specific quality metrics.
95
+
96
+ Parameters
97
+ ----------
98
+ recipe_run_id : int
99
+ id of the recipe run used to identify the workflow run this task is part of
100
+ workflow_name : str
101
+ name of the workflow to which this instance of the task belongs
102
+ workflow_version : str
103
+ version of the workflow to which this instance of the task belongs
104
+
105
+ """
106
+
107
+ def run(self) -> None:
108
+ """Calculate sensitivity and noise quality metrics."""
109
+ if self.constants.correct_for_polarization:
110
+ with self.apm_processing_step(
111
+ "Calculating L1 Sensitivity metrics for all stokes states"
112
+ ):
113
+ self.compute_full_stokes_sensitivity()
114
+ with self.apm_task_step("Calculating L1 Cryonirsp noise metrics for all stokes states"):
115
+ self.compute_full_stokes_noise()
116
+ else:
117
+ with self.apm_processing_step("Calculating L1 Sensitivity metrics for intensity only"):
118
+ self.compute_intensity_only_sensitivity()
119
+ with self.apm_task_step("Calculating L1 Cryonirsp noise metrics for intensity only"):
120
+ self.compute_intensity_only_noise()
121
+
122
+ def compute_full_stokes_sensitivity(self):
123
+ """Compute the sensitivities of each map scan for each stokes state."""
124
+ for stokes_state in self.constants.stokes_params:
125
+ with self.apm_processing_step(f"Calculating sensitivity for stokes = {stokes_state}"):
126
+ quality_data = self.calculate_sensitivity_for_stokes_state(
127
+ stokes_state=stokes_state
128
+ )
129
+ with self.apm_writing_step(f"Writing sensitivity data for stokes = {stokes_state}"):
130
+ self.quality_store_sensitivity(
131
+ stokes=stokes_state,
132
+ datetimes=quality_data.datetimes,
133
+ values=quality_data.values,
134
+ )
135
+
136
+ def compute_intensity_only_sensitivity(self):
137
+ """Compute the sensitivities of each map scan for the intensity stokes state only."""
138
+ with self.apm_processing_step(f"Calculating sensitivity for intensity only"):
139
+ quality_data = self.calculate_sensitivity_for_stokes_state(stokes_state="I")
140
+ with self.apm_writing_step("Writing sensitivity data for intensity only"):
141
+ self.quality_store_sensitivity(
142
+ stokes="I", datetimes=quality_data.datetimes, values=quality_data.values
143
+ )
144
+
145
+ def compute_full_stokes_noise(self):
146
+ """Compute noise in data broken down by each stokes state."""
147
+ for stokes in self.constants.stokes_params:
148
+ with self.apm_processing_step(f"Compile noise values for {stokes=}"):
149
+ noise_data = self.compile_noise_data(stokes=stokes)
150
+ with self.apm_writing_step(f"Write noise values for {stokes=}"):
151
+ self.quality_store_noise(
152
+ datetimes=noise_data.datetimes, values=noise_data.values, stokes=stokes
153
+ )
154
+
155
+ def compute_intensity_only_noise(self):
156
+ """Compute noise in data for the intensity stokes state only."""
157
+ stokes = "I"
158
+ with self.apm_processing_step(f"Compile noise values for {stokes=}"):
159
+ noise_data = self.compile_noise_data(stokes=stokes)
160
+ with self.apm_writing_step(f"Write noise values for {stokes=}"):
161
+ self.quality_store_noise(
162
+ datetimes=noise_data.datetimes, values=noise_data.values, stokes=stokes
163
+ )
164
+
165
+ def calculate_sensitivity_for_stokes_state(self, stokes_state: str) -> _QualityData:
166
+ """Calculate the sensitivities of each map scan for a given stokes state."""
167
+ stokes_sensitivities = _QualityData()
168
+ for map_scan in range(1, self.constants.num_map_scans + 1):
169
+ map_scan_sensitivity_data_point = self.calculate_sensitivity_for_map_scan(
170
+ map_scan=map_scan,
171
+ meas_num=1,
172
+ stokes_state=stokes_state,
173
+ )
174
+ stokes_sensitivities.data_points.append(map_scan_sensitivity_data_point)
175
+ logger.info(
176
+ f"Calculated {len(stokes_sensitivities)} stokes state sensitivities for {stokes_state=}"
177
+ )
178
+ return stokes_sensitivities
179
+
180
+ def calculate_sensitivity_for_map_scan(
181
+ self, map_scan: int, meas_num: int, stokes_state: str
182
+ ) -> _QualityDataPoint:
183
+ """Calculate the sensitivity as the standard deviation of a frame divided by median intensity frame averaged over the scan steps for a single map scan."""
184
+ scan_step_sensitivities = self.calculate_sensitivities_per_scan_step(
185
+ map_scan=map_scan, meas_num=meas_num, stokes_state=stokes_state
186
+ )
187
+ map_scan_sensitivity = np.mean(scan_step_sensitivities.values)
188
+ map_scan_average_date = Time(np.mean(scan_step_sensitivities.datetimes), format="mjd").isot
189
+ result = _QualityDataPoint(value=map_scan_sensitivity, datetime=map_scan_average_date)
190
+ logger.info(
191
+ f"Calculated map scan sensitivity for {map_scan=}, {meas_num=}, {stokes_state=}"
192
+ f" as {result} "
193
+ )
194
+ return result
195
+
196
+ def calculate_sensitivities_per_scan_step(
197
+ self, map_scan: int, meas_num: int, stokes_state: str
198
+ ) -> _QualityData:
199
+ """Calculate the sensitivities for each scan step in a map scan."""
200
+ scan_step_sensitivities = _QualityData()
201
+ for step in range(1, self.constants.num_scan_steps + 1):
202
+ avg_stokes_i_data_point = self.get_intensity_frame_average(
203
+ map_scan=map_scan, step=step, meas_num=1
204
+ )
205
+ frame = next(
206
+ self.read(
207
+ tags=[
208
+ CryonirspTag.calibrated(),
209
+ CryonirspTag.frame(),
210
+ CryonirspTag.scan_step(step),
211
+ CryonirspTag.map_scan(map_scan),
212
+ CryonirspTag.stokes(stokes_state),
213
+ CryonirspTag.meas_num(meas_num),
214
+ ],
215
+ decoder=fits_access_decoder,
216
+ fits_access_class=L1QualityFitsAccess,
217
+ )
218
+ )
219
+ scan_step_sensitivity = np.nanstd(frame.data) / avg_stokes_i_data_point.value
220
+ scan_step_sensitivity_data_point = _QualityDataPoint(
221
+ value=scan_step_sensitivity, datetime=avg_stokes_i_data_point.datetime
222
+ )
223
+ scan_step_sensitivities.data_points.append(scan_step_sensitivity_data_point)
224
+ logger.info(
225
+ f"Calculated {len(scan_step_sensitivities)} scan step sensitivities for {map_scan=}, "
226
+ f"{meas_num=}, and {stokes_state=}"
227
+ )
228
+ return scan_step_sensitivities
229
+
230
+ def get_intensity_frame_average(
231
+ self, map_scan: int, step: int, meas_num: int
232
+ ) -> _QualityDataPoint:
233
+ """Calculate the average of an intensity frame."""
234
+ frame: L1QualityFitsAccess = next(
235
+ self.read(
236
+ tags=[
237
+ CryonirspTag.calibrated(),
238
+ CryonirspTag.frame(),
239
+ CryonirspTag.scan_step(step),
240
+ CryonirspTag.map_scan(map_scan),
241
+ CryonirspTag.stokes("I"),
242
+ CryonirspTag.meas_num(meas_num),
243
+ ],
244
+ decoder=fits_access_decoder,
245
+ fits_access_class=L1QualityFitsAccess,
246
+ )
247
+ )
248
+ median = np.nanmedian(frame.data)
249
+ time_obs_mjd = Time(frame.time_obs).mjd
250
+ mean = np.nanmean(frame.data)
251
+ average = median or mean
252
+ result = _QualityDataPoint(value=average, datetime=time_obs_mjd)
253
+ logger.info(f"Calculated intensity frame average as {result}")
254
+ return result
255
+
256
+ def compile_noise_data(self, stokes: str) -> _QualityData:
257
+ """Compile lists of noise values and their observation times."""
258
+ tags = [CryonirspTag.calibrated(), CryonirspTag.frame(), CryonirspTag.stokes(stokes)]
259
+ frames: Generator[L1QualityFitsAccess, None, None] = self.read(
260
+ tags=tags,
261
+ decoder=fits_access_decoder,
262
+ fits_access_class=L1QualityFitsAccess,
263
+ )
264
+ result = _QualityData()
265
+ logger.info(f"Compiling noise data for {tags = }")
266
+ for frame in frames:
267
+ data_point = _QualityDataPoint(
268
+ value=self.avg_noise(frame.data), datetime=frame.time_obs
269
+ )
270
+ result.data_points.append(data_point)
271
+ return result