dkist-processing-cryonirsp 1.3.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dkist-processing-cryonirsp might be problematic. Click here for more details.
- changelog/.gitempty +0 -0
- dkist_processing_cryonirsp/__init__.py +11 -0
- dkist_processing_cryonirsp/config.py +12 -0
- dkist_processing_cryonirsp/models/__init__.py +1 -0
- dkist_processing_cryonirsp/models/constants.py +248 -0
- dkist_processing_cryonirsp/models/exposure_conditions.py +26 -0
- dkist_processing_cryonirsp/models/parameters.py +296 -0
- dkist_processing_cryonirsp/models/tags.py +168 -0
- dkist_processing_cryonirsp/models/task_name.py +14 -0
- dkist_processing_cryonirsp/parsers/__init__.py +1 -0
- dkist_processing_cryonirsp/parsers/cryonirsp_l0_fits_access.py +111 -0
- dkist_processing_cryonirsp/parsers/cryonirsp_l1_fits_access.py +30 -0
- dkist_processing_cryonirsp/parsers/exposure_conditions.py +163 -0
- dkist_processing_cryonirsp/parsers/map_repeats.py +40 -0
- dkist_processing_cryonirsp/parsers/measurements.py +55 -0
- dkist_processing_cryonirsp/parsers/modstates.py +31 -0
- dkist_processing_cryonirsp/parsers/optical_density_filters.py +40 -0
- dkist_processing_cryonirsp/parsers/polarimetric_check.py +120 -0
- dkist_processing_cryonirsp/parsers/scan_step.py +412 -0
- dkist_processing_cryonirsp/parsers/time.py +80 -0
- dkist_processing_cryonirsp/parsers/wavelength.py +26 -0
- dkist_processing_cryonirsp/tasks/__init__.py +19 -0
- dkist_processing_cryonirsp/tasks/assemble_movie.py +202 -0
- dkist_processing_cryonirsp/tasks/bad_pixel_map.py +96 -0
- dkist_processing_cryonirsp/tasks/beam_boundaries_base.py +279 -0
- dkist_processing_cryonirsp/tasks/ci_beam_boundaries.py +55 -0
- dkist_processing_cryonirsp/tasks/ci_science.py +169 -0
- dkist_processing_cryonirsp/tasks/cryonirsp_base.py +67 -0
- dkist_processing_cryonirsp/tasks/dark.py +98 -0
- dkist_processing_cryonirsp/tasks/gain.py +251 -0
- dkist_processing_cryonirsp/tasks/instrument_polarization.py +447 -0
- dkist_processing_cryonirsp/tasks/l1_output_data.py +44 -0
- dkist_processing_cryonirsp/tasks/linearity_correction.py +582 -0
- dkist_processing_cryonirsp/tasks/make_movie_frames.py +302 -0
- dkist_processing_cryonirsp/tasks/mixin/__init__.py +1 -0
- dkist_processing_cryonirsp/tasks/mixin/beam_access.py +52 -0
- dkist_processing_cryonirsp/tasks/mixin/corrections.py +177 -0
- dkist_processing_cryonirsp/tasks/mixin/intermediate_frame.py +193 -0
- dkist_processing_cryonirsp/tasks/mixin/linearized_frame.py +309 -0
- dkist_processing_cryonirsp/tasks/mixin/shift_measurements.py +297 -0
- dkist_processing_cryonirsp/tasks/parse.py +281 -0
- dkist_processing_cryonirsp/tasks/quality_metrics.py +271 -0
- dkist_processing_cryonirsp/tasks/science_base.py +511 -0
- dkist_processing_cryonirsp/tasks/sp_beam_boundaries.py +270 -0
- dkist_processing_cryonirsp/tasks/sp_dispersion_axis_correction.py +484 -0
- dkist_processing_cryonirsp/tasks/sp_geometric.py +585 -0
- dkist_processing_cryonirsp/tasks/sp_science.py +299 -0
- dkist_processing_cryonirsp/tasks/sp_solar_gain.py +475 -0
- dkist_processing_cryonirsp/tasks/trial_output_data.py +61 -0
- dkist_processing_cryonirsp/tasks/write_l1.py +1033 -0
- dkist_processing_cryonirsp/tests/__init__.py +1 -0
- dkist_processing_cryonirsp/tests/conftest.py +456 -0
- dkist_processing_cryonirsp/tests/header_models.py +592 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/__init__.py +0 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/l0_cals_only.py +541 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/l0_to_l1.py +615 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/linearize_only.py +96 -0
- dkist_processing_cryonirsp/tests/local_trial_workflows/local_trial_helpers.py +592 -0
- dkist_processing_cryonirsp/tests/test_assemble_movie.py +144 -0
- dkist_processing_cryonirsp/tests/test_assemble_qualilty.py +517 -0
- dkist_processing_cryonirsp/tests/test_bad_pixel_maps.py +115 -0
- dkist_processing_cryonirsp/tests/test_ci_beam_boundaries.py +106 -0
- dkist_processing_cryonirsp/tests/test_ci_science.py +355 -0
- dkist_processing_cryonirsp/tests/test_corrections.py +126 -0
- dkist_processing_cryonirsp/tests/test_cryo_base.py +202 -0
- dkist_processing_cryonirsp/tests/test_cryo_constants.py +76 -0
- dkist_processing_cryonirsp/tests/test_dark.py +287 -0
- dkist_processing_cryonirsp/tests/test_gain.py +278 -0
- dkist_processing_cryonirsp/tests/test_instrument_polarization.py +531 -0
- dkist_processing_cryonirsp/tests/test_linearity_correction.py +245 -0
- dkist_processing_cryonirsp/tests/test_make_movie_frames.py +111 -0
- dkist_processing_cryonirsp/tests/test_parameters.py +266 -0
- dkist_processing_cryonirsp/tests/test_parse.py +1439 -0
- dkist_processing_cryonirsp/tests/test_quality.py +203 -0
- dkist_processing_cryonirsp/tests/test_sp_beam_boundaries.py +112 -0
- dkist_processing_cryonirsp/tests/test_sp_dispersion_axis_correction.py +155 -0
- dkist_processing_cryonirsp/tests/test_sp_geometric.py +319 -0
- dkist_processing_cryonirsp/tests/test_sp_make_movie_frames.py +121 -0
- dkist_processing_cryonirsp/tests/test_sp_science.py +483 -0
- dkist_processing_cryonirsp/tests/test_sp_solar.py +198 -0
- dkist_processing_cryonirsp/tests/test_trial_create_quality_report.py +79 -0
- dkist_processing_cryonirsp/tests/test_trial_output_data.py +251 -0
- dkist_processing_cryonirsp/tests/test_workflows.py +9 -0
- dkist_processing_cryonirsp/tests/test_write_l1.py +436 -0
- dkist_processing_cryonirsp/workflows/__init__.py +2 -0
- dkist_processing_cryonirsp/workflows/ci_l0_processing.py +77 -0
- dkist_processing_cryonirsp/workflows/sp_l0_processing.py +84 -0
- dkist_processing_cryonirsp/workflows/trial_workflows.py +190 -0
- dkist_processing_cryonirsp-1.3.4.dist-info/METADATA +194 -0
- dkist_processing_cryonirsp-1.3.4.dist-info/RECORD +111 -0
- dkist_processing_cryonirsp-1.3.4.dist-info/WHEEL +5 -0
- dkist_processing_cryonirsp-1.3.4.dist-info/top_level.txt +4 -0
- docs/Makefile +134 -0
- docs/bad_pixel_calibration.rst +47 -0
- docs/beam_angle_calculation.rst +53 -0
- docs/beam_boundary_computation.rst +88 -0
- docs/changelog.rst +7 -0
- docs/ci_science_calibration.rst +33 -0
- docs/conf.py +52 -0
- docs/index.rst +21 -0
- docs/l0_to_l1_cryonirsp_ci-full-trial.rst +10 -0
- docs/l0_to_l1_cryonirsp_ci.rst +10 -0
- docs/l0_to_l1_cryonirsp_sp-full-trial.rst +10 -0
- docs/l0_to_l1_cryonirsp_sp.rst +10 -0
- docs/linearization.rst +43 -0
- docs/make.bat +170 -0
- docs/requirements.txt +1 -0
- docs/requirements_table.rst +8 -0
- docs/scientific_changelog.rst +10 -0
- docs/sp_science_calibration.rst +59 -0
- licenses/LICENSE.rst +11 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""Cryo instrument polarization task."""
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from astropy.io import fits
|
|
8
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
9
|
+
from dkist_processing_math.arithmetic import divide_arrays_by_array
|
|
10
|
+
from dkist_processing_math.arithmetic import subtract_array_from_arrays
|
|
11
|
+
from dkist_processing_math.statistics import average_numpy_arrays
|
|
12
|
+
from dkist_processing_math.transform.binning import resize_arrays
|
|
13
|
+
from dkist_processing_pac.fitter.polcal_fitter import PolcalFitter
|
|
14
|
+
from dkist_processing_pac.input_data.drawer import Drawer
|
|
15
|
+
from dkist_processing_pac.input_data.dresser import Dresser
|
|
16
|
+
from dkist_service_configuration.logging import logger
|
|
17
|
+
|
|
18
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
19
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
20
|
+
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
|
|
21
|
+
from dkist_processing_cryonirsp.tasks.cryonirsp_base import CryonirspTaskBase
|
|
22
|
+
|
|
23
|
+
__all__ = ["CIInstrumentPolarizationCalibration", "SPInstrumentPolarizationCalibration"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def generate_polcal_quality_label(arm: str, beam: int) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Make a quality label given an arm and beam.
|
|
29
|
+
|
|
30
|
+
Defined here so we don't have to remember what our labels are in the L1 output data task.
|
|
31
|
+
"""
|
|
32
|
+
return f"{arm} Beam {beam}"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class InstrumentPolarizationCalibrationBase(CryonirspTaskBase, ABC):
|
|
36
|
+
"""
|
|
37
|
+
Base task class for instrument polarization for a CryoNIRSP calibration run.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
recipe_run_id : int
|
|
42
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
43
|
+
workflow_name : str
|
|
44
|
+
name of the workflow to which this instance of the task belongs
|
|
45
|
+
workflow_version : str
|
|
46
|
+
version of the workflow to which this instance of the task belongs
|
|
47
|
+
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
record_provenance = True
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def record_polcal_quality_metrics(self, beam: int, polcal_fitter: PolcalFitter):
|
|
54
|
+
"""Abstract method to be implemented in subclass."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
def run(self) -> None:
|
|
58
|
+
"""
|
|
59
|
+
For each beam.
|
|
60
|
+
|
|
61
|
+
- Reduce calibration sequence steps
|
|
62
|
+
- Fit reduced data to PAC parameters
|
|
63
|
+
- Compute and save demodulation matrices
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
None
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
if not self.constants.correct_for_polarization:
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
target_exposure_conditions = self.constants.polcal_exposure_conditions_list
|
|
74
|
+
logger.info(f"{target_exposure_conditions = }")
|
|
75
|
+
|
|
76
|
+
self.generate_polcal_dark_calibration(target_exposure_conditions)
|
|
77
|
+
self.generate_polcal_gain_calibration(target_exposure_conditions)
|
|
78
|
+
|
|
79
|
+
logger.info(
|
|
80
|
+
f"Demodulation matrices will span FOV with shape {(self.parameters.polcal_num_spatial_bins, self.parameters.polcal_num_spatial_bins)}"
|
|
81
|
+
)
|
|
82
|
+
for beam in range(1, self.constants.num_beams + 1):
|
|
83
|
+
with self.apm_processing_step(f"Reducing CS steps for {beam = }"):
|
|
84
|
+
local_reduced_arrays, global_reduced_arrays = self.reduce_cs_steps(beam)
|
|
85
|
+
|
|
86
|
+
with self.apm_processing_step(f"Fit CU parameters for {beam = }"):
|
|
87
|
+
local_dresser = Dresser()
|
|
88
|
+
local_dresser.add_drawer(Drawer(local_reduced_arrays))
|
|
89
|
+
global_dresser = Dresser()
|
|
90
|
+
global_dresser.add_drawer(Drawer(global_reduced_arrays))
|
|
91
|
+
pac_fitter = PolcalFitter(
|
|
92
|
+
local_dresser=local_dresser,
|
|
93
|
+
global_dresser=global_dresser,
|
|
94
|
+
fit_mode=self.parameters.polcal_pac_fit_mode,
|
|
95
|
+
init_set=self.parameters.polcal_pac_init_set,
|
|
96
|
+
fit_TM=False,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
with self.apm_processing_step(f"Resampling demodulation matrices for {beam = }"):
|
|
100
|
+
demod_matrices = pac_fitter.demodulation_matrices
|
|
101
|
+
# Reshaping the demodulation matrix to get rid of unit length dimensions
|
|
102
|
+
logger.info(f"Resampling demodulation matrices for {beam = }")
|
|
103
|
+
demod_matrices = self.reshape_demod_matrices(demod_matrices)
|
|
104
|
+
logger.info(
|
|
105
|
+
f"Shape of resampled demodulation matrices for {beam = }: {demod_matrices.shape}"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
with self.apm_writing_step(f"Writing demodulation matrices for {beam = }"):
|
|
109
|
+
self.intermediate_frame_write_arrays(
|
|
110
|
+
demod_matrices,
|
|
111
|
+
beam=beam,
|
|
112
|
+
task_tag=CryonirspTag.task_demodulation_matrices(),
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
with self.apm_processing_step("Computing and recording polcal quality metrics"):
|
|
116
|
+
self.record_polcal_quality_metrics(beam, polcal_fitter=pac_fitter)
|
|
117
|
+
|
|
118
|
+
with self.apm_processing_step("Computing and logging quality metrics"):
|
|
119
|
+
no_of_raw_polcal_frames: int = self.scratch.count_all(
|
|
120
|
+
tags=[
|
|
121
|
+
CryonirspTag.linearized(),
|
|
122
|
+
CryonirspTag.frame(),
|
|
123
|
+
CryonirspTag.task_polcal(),
|
|
124
|
+
],
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
self.quality_store_task_type_counts(
|
|
128
|
+
task_type=TaskName.polcal.value, total_frames=no_of_raw_polcal_frames
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def reduce_cs_steps(
|
|
132
|
+
self, beam: int
|
|
133
|
+
) -> tuple[dict[int, list[CryonirspL0FitsAccess]], dict[int, list[CryonirspL0FitsAccess]]]:
|
|
134
|
+
"""
|
|
135
|
+
Reduce all of the data for the cal sequence steps for this beam.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
beam
|
|
140
|
+
The current beam being processed
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
Dict
|
|
145
|
+
A Dict of calibrated and binned arrays for all the cs steps for this beam
|
|
146
|
+
"""
|
|
147
|
+
local_reduced_array_dict = defaultdict(list)
|
|
148
|
+
global_reduced_array_dict = defaultdict(list)
|
|
149
|
+
|
|
150
|
+
for modstate in range(1, self.constants.num_modstates + 1):
|
|
151
|
+
for exposure_conditions in self.constants.polcal_exposure_conditions_list:
|
|
152
|
+
logger.info(f"Loading dark array for {exposure_conditions = } and {beam = }")
|
|
153
|
+
try:
|
|
154
|
+
dark_array = self.intermediate_frame_load_polcal_dark_array(
|
|
155
|
+
exposure_conditions=exposure_conditions,
|
|
156
|
+
beam=beam,
|
|
157
|
+
)
|
|
158
|
+
except StopIteration as e:
|
|
159
|
+
raise ValueError(
|
|
160
|
+
f"No matching dark array found for {exposure_conditions = }"
|
|
161
|
+
) from e
|
|
162
|
+
|
|
163
|
+
logger.info(f"Loading gain array for {exposure_conditions = } and {beam = }")
|
|
164
|
+
try:
|
|
165
|
+
gain_array = self.intermediate_frame_load_polcal_gain_array(
|
|
166
|
+
exposure_conditions=exposure_conditions, beam=beam
|
|
167
|
+
)
|
|
168
|
+
except StopIteration as e:
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"No matching gain array found for {exposure_conditions = }"
|
|
171
|
+
) from e
|
|
172
|
+
|
|
173
|
+
for cs_step in range(self.constants.num_cs_steps):
|
|
174
|
+
local_obj, global_obj = self.reduce_single_step(
|
|
175
|
+
beam,
|
|
176
|
+
dark_array,
|
|
177
|
+
gain_array,
|
|
178
|
+
modstate,
|
|
179
|
+
cs_step,
|
|
180
|
+
exposure_conditions,
|
|
181
|
+
)
|
|
182
|
+
local_reduced_array_dict[cs_step].append(local_obj)
|
|
183
|
+
global_reduced_array_dict[cs_step].append(global_obj)
|
|
184
|
+
|
|
185
|
+
return local_reduced_array_dict, global_reduced_array_dict
|
|
186
|
+
|
|
187
|
+
def reduce_single_step(
|
|
188
|
+
self,
|
|
189
|
+
beam: int,
|
|
190
|
+
dark_array: np.ndarray,
|
|
191
|
+
gain_array: np.ndarray,
|
|
192
|
+
modstate: int,
|
|
193
|
+
cs_step: int,
|
|
194
|
+
exposure_conditions: ExposureConditions,
|
|
195
|
+
) -> tuple[CryonirspL0FitsAccess, CryonirspL0FitsAccess]:
|
|
196
|
+
"""
|
|
197
|
+
Reduce a single calibration step for this beam, cs step and modulator state.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
beam : int
|
|
202
|
+
The current beam being processed
|
|
203
|
+
dark_array : np.ndarray
|
|
204
|
+
The dark array for the current beam
|
|
205
|
+
gain_array : np.ndarray
|
|
206
|
+
The gain array for the current beam
|
|
207
|
+
modstate : int
|
|
208
|
+
The current modulator state
|
|
209
|
+
cs_step : int
|
|
210
|
+
The current cal sequence step
|
|
211
|
+
exposure_conditions : ExposureConditions
|
|
212
|
+
The exposure conditions (exposure time, OD filter)
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
The final reduced result for this single step
|
|
217
|
+
"""
|
|
218
|
+
apm_str = f"{beam = }, {modstate = }, {cs_step = }, and {exposure_conditions = }"
|
|
219
|
+
logger.info(f"Reducing {apm_str}")
|
|
220
|
+
|
|
221
|
+
pol_cal_headers = (
|
|
222
|
+
obj.header
|
|
223
|
+
for obj in self.linearized_frame_polcal_fits_access_generator(
|
|
224
|
+
modstate=modstate,
|
|
225
|
+
cs_step=cs_step,
|
|
226
|
+
exposure_conditions=exposure_conditions,
|
|
227
|
+
beam=beam,
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
pol_cal_arrays = (
|
|
231
|
+
obj.data
|
|
232
|
+
for obj in self.linearized_frame_polcal_fits_access_generator(
|
|
233
|
+
modstate=modstate,
|
|
234
|
+
cs_step=cs_step,
|
|
235
|
+
exposure_conditions=exposure_conditions,
|
|
236
|
+
beam=beam,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
avg_inst_pol_cal_header = next(pol_cal_headers)
|
|
241
|
+
avg_inst_pol_cal_array = average_numpy_arrays(pol_cal_arrays)
|
|
242
|
+
|
|
243
|
+
with self.apm_processing_step(f"Apply basic corrections for {apm_str}"):
|
|
244
|
+
dark_corrected_array = subtract_array_from_arrays(avg_inst_pol_cal_array, dark_array)
|
|
245
|
+
gain_corrected_array = next(divide_arrays_by_array(dark_corrected_array, gain_array))
|
|
246
|
+
|
|
247
|
+
with self.apm_processing_step(f"Extract macro pixels from {apm_str}"):
|
|
248
|
+
self.set_original_beam_size(gain_corrected_array)
|
|
249
|
+
output_shape = (
|
|
250
|
+
self.parameters.polcal_num_spatial_bins,
|
|
251
|
+
self.parameters.polcal_num_spectral_bins,
|
|
252
|
+
)
|
|
253
|
+
local_binned_array = next(resize_arrays(gain_corrected_array, output_shape))
|
|
254
|
+
global_binned_array = next(resize_arrays(gain_corrected_array, (1, 1)))
|
|
255
|
+
|
|
256
|
+
with self.apm_processing_step(f"Create reduced CryonirspL0FitsAccess for {apm_str}"):
|
|
257
|
+
local_result = CryonirspL0FitsAccess(
|
|
258
|
+
fits.ImageHDU(local_binned_array[:, :], avg_inst_pol_cal_header),
|
|
259
|
+
auto_squeeze=False,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
global_result = CryonirspL0FitsAccess(
|
|
263
|
+
fits.ImageHDU(global_binned_array[None, :, :], avg_inst_pol_cal_header),
|
|
264
|
+
auto_squeeze=False,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return local_result, global_result
|
|
268
|
+
|
|
269
|
+
def reshape_demod_matrices(self, demod_matrices: np.ndarray) -> np.ndarray:
|
|
270
|
+
"""Upsample demodulation matrices to match the full beam size.
|
|
271
|
+
|
|
272
|
+
Given an input set of demodulation matrices with shape (X', Y', 4, M) resample the output to shape
|
|
273
|
+
(X, Y, 4, M), where X' and Y' are the binned size of the beam FOV, X and Y are the full beam shape, M is the
|
|
274
|
+
number of modulator states.
|
|
275
|
+
|
|
276
|
+
If only a single demodulation matrix was made then it is returned as a single array with shape (4, M).
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
demod_matrices
|
|
281
|
+
A set of demodulation matrices with shape (X', Y', 4, M)
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
If X' and Y' > 1 then upsampled matrices that are the full beam size (X, Y, 4, M).
|
|
286
|
+
If X' == Y' == 1 then a single matric for the whole FOV with shape (4, M)
|
|
287
|
+
"""
|
|
288
|
+
expected_dims = 4
|
|
289
|
+
if len(demod_matrices.shape) != expected_dims:
|
|
290
|
+
raise ValueError(
|
|
291
|
+
f"Expected demodulation matrices to have {expected_dims} dimensions. Got shape {demod_matrices.shape}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
data_shape = demod_matrices.shape[
|
|
295
|
+
:2
|
|
296
|
+
] # The non-demodulation matrix part of the larger array
|
|
297
|
+
demod_shape = demod_matrices.shape[-2:] # The shape of a single demodulation matrix
|
|
298
|
+
logger.info(f"Demodulation FOV sampling shape: {data_shape}")
|
|
299
|
+
logger.info(f"Demodulation matrix shape: {demod_shape}")
|
|
300
|
+
if data_shape == (1, 1):
|
|
301
|
+
# A single modulation matrix can be used directly, so just return it after removing extraneous dimensions
|
|
302
|
+
logger.info(f"Single demodulation matrix detected")
|
|
303
|
+
return demod_matrices[0, 0, :, :]
|
|
304
|
+
|
|
305
|
+
target_shape = self.single_beam_shape + demod_shape
|
|
306
|
+
logger.info(f"Target full-frame demodulation shape: {target_shape}")
|
|
307
|
+
return self.resize_polcal_array(demod_matrices, target_shape)
|
|
308
|
+
|
|
309
|
+
def set_original_beam_size(self, array: np.ndarray) -> None:
|
|
310
|
+
"""Record the shape of a single beam as a class property."""
|
|
311
|
+
self.single_beam_shape = array.shape
|
|
312
|
+
|
|
313
|
+
@staticmethod
|
|
314
|
+
def resize_polcal_array(array: np.ndarray, output_shape: tuple[int, ...]) -> np.ndarray:
|
|
315
|
+
return next(resize_arrays(array, output_shape))
|
|
316
|
+
|
|
317
|
+
def generate_polcal_dark_calibration(
|
|
318
|
+
self, target_exposure_conditions_list: [ExposureConditions]
|
|
319
|
+
):
|
|
320
|
+
"""Compute the polcal dark calibration."""
|
|
321
|
+
with self.apm_task_step(
|
|
322
|
+
f"Calculating dark frames for {len(target_exposure_conditions_list)} exp times"
|
|
323
|
+
):
|
|
324
|
+
for exposure_conditions in target_exposure_conditions_list:
|
|
325
|
+
for beam in range(1, self.constants.num_beams + 1):
|
|
326
|
+
with self.apm_processing_step(
|
|
327
|
+
f"Calculating polcal dark array(s) for {exposure_conditions = } and {beam = }"
|
|
328
|
+
):
|
|
329
|
+
linearized_dark_arrays = self.linearized_frame_polcal_dark_array_generator(
|
|
330
|
+
exposure_conditions=exposure_conditions,
|
|
331
|
+
beam=beam,
|
|
332
|
+
)
|
|
333
|
+
averaged_dark_array = average_numpy_arrays(linearized_dark_arrays)
|
|
334
|
+
with self.apm_writing_step(
|
|
335
|
+
f"Writing dark for {exposure_conditions = } and {beam = }"
|
|
336
|
+
):
|
|
337
|
+
self.intermediate_frame_write_arrays(
|
|
338
|
+
averaged_dark_array,
|
|
339
|
+
task_tag=CryonirspTag.task_polcal_dark(),
|
|
340
|
+
exposure_conditions=exposure_conditions,
|
|
341
|
+
beam=beam,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def generate_polcal_gain_calibration(self, exposure_conditions_list: [ExposureConditions]):
|
|
345
|
+
"""Compute the polcal gain calibration."""
|
|
346
|
+
with self.apm_task_step(
|
|
347
|
+
f"Generate gains for {len(exposure_conditions_list)} exposure conditions"
|
|
348
|
+
):
|
|
349
|
+
for exposure_conditions in exposure_conditions_list:
|
|
350
|
+
for beam in range(1, self.constants.num_beams + 1):
|
|
351
|
+
logger.info(
|
|
352
|
+
f"Load polcal dark array for {exposure_conditions = } and {beam = }"
|
|
353
|
+
)
|
|
354
|
+
try:
|
|
355
|
+
dark_array = self.intermediate_frame_load_polcal_dark_array(
|
|
356
|
+
exposure_conditions=exposure_conditions,
|
|
357
|
+
beam=beam,
|
|
358
|
+
)
|
|
359
|
+
except StopIteration as e:
|
|
360
|
+
raise ValueError(
|
|
361
|
+
f"No matching polcal dark found for {exposure_conditions = } s and {beam = }"
|
|
362
|
+
) from e
|
|
363
|
+
with self.apm_processing_step(
|
|
364
|
+
f"Calculating polcal gain array(s) for {exposure_conditions = } and {beam = }"
|
|
365
|
+
):
|
|
366
|
+
linearized_gain_arrays = self.linearized_frame_polcal_gain_array_generator(
|
|
367
|
+
exposure_conditions=exposure_conditions,
|
|
368
|
+
beam=beam,
|
|
369
|
+
)
|
|
370
|
+
averaged_gain_array = average_numpy_arrays(linearized_gain_arrays)
|
|
371
|
+
dark_corrected_gain_array = next(
|
|
372
|
+
subtract_array_from_arrays(averaged_gain_array, dark_array)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
bad_pixel_map = self.intermediate_frame_load_bad_pixel_map(beam=beam)
|
|
376
|
+
bad_pixel_corrected_array = self.corrections_correct_bad_pixels(
|
|
377
|
+
dark_corrected_gain_array, bad_pixel_map
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
normalized_gain_array = bad_pixel_corrected_array / np.mean(
|
|
381
|
+
bad_pixel_corrected_array
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
with self.apm_writing_step(
|
|
385
|
+
f"Writing gain array for exposure time {exposure_conditions} and {beam = }"
|
|
386
|
+
):
|
|
387
|
+
self.intermediate_frame_write_arrays(
|
|
388
|
+
normalized_gain_array,
|
|
389
|
+
task_tag=CryonirspTag.task_polcal_gain(),
|
|
390
|
+
exposure_conditions=exposure_conditions,
|
|
391
|
+
beam=beam,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class CIInstrumentPolarizationCalibration(InstrumentPolarizationCalibrationBase):
|
|
396
|
+
"""
|
|
397
|
+
Task class for instrument polarization for a CI CryoNIRSP calibration run.
|
|
398
|
+
|
|
399
|
+
Parameters
|
|
400
|
+
----------
|
|
401
|
+
recipe_run_id : int
|
|
402
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
403
|
+
workflow_name : str
|
|
404
|
+
name of the workflow to which this instance of the task belongs
|
|
405
|
+
workflow_version : str
|
|
406
|
+
version of the workflow to which this instance of the task belongs
|
|
407
|
+
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
def record_polcal_quality_metrics(self, beam: int, polcal_fitter: PolcalFitter):
|
|
411
|
+
"""Record various quality metrics from PolCal fits."""
|
|
412
|
+
self.quality_store_polcal_results(
|
|
413
|
+
polcal_fitter=polcal_fitter,
|
|
414
|
+
label=generate_polcal_quality_label(arm="CI", beam=beam),
|
|
415
|
+
bin_nums=[
|
|
416
|
+
self.parameters.polcal_num_spatial_bins,
|
|
417
|
+
self.parameters.polcal_num_spatial_bins,
|
|
418
|
+
],
|
|
419
|
+
bin_labels=["spatial", "spatial"],
|
|
420
|
+
skip_recording_constant_pars=False,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class SPInstrumentPolarizationCalibration(InstrumentPolarizationCalibrationBase):
|
|
425
|
+
"""Task class for instrument polarization for an SP CryoNIRSP calibration run."""
|
|
426
|
+
|
|
427
|
+
def record_polcal_quality_metrics(
|
|
428
|
+
self,
|
|
429
|
+
beam: int,
|
|
430
|
+
polcal_fitter: PolcalFitter,
|
|
431
|
+
) -> None:
|
|
432
|
+
"""Record various quality metrics from PolCal fits."""
|
|
433
|
+
self.quality_store_polcal_results(
|
|
434
|
+
polcal_fitter=polcal_fitter,
|
|
435
|
+
label=generate_polcal_quality_label(arm="SP", beam=beam),
|
|
436
|
+
bin_nums=[
|
|
437
|
+
self.parameters.polcal_num_spatial_bins,
|
|
438
|
+
self.parameters.polcal_num_spectral_bins,
|
|
439
|
+
],
|
|
440
|
+
bin_labels=["spatial", "spectral"],
|
|
441
|
+
## This is a bit of a hack and thus needs some explanation
|
|
442
|
+
# By using the ``skip_recording_constant_pars`` switch we DON'T record the "polcal constant parameters" metric
|
|
443
|
+
# for beam 2. This is because both beam 1 and beam 2 will have the same table. The way `*-common` is built
|
|
444
|
+
# it will look for all metrics for both beam 1 and beam 2 so if we did save that metric for beam 2 then the
|
|
445
|
+
# table would show up twice in the quality report. The following line avoids that.
|
|
446
|
+
skip_recording_constant_pars=beam != 1,
|
|
447
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Subclasses of AssembleQualityData that cause the correct polcal metrics to build."""
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from dkist_processing_common.models.constants import ConstantsBase
|
|
5
|
+
from dkist_processing_common.tasks import AssembleQualityData
|
|
6
|
+
|
|
7
|
+
__all__ = ["CIAssembleQualityData", "SPAssembleQualityData"]
|
|
8
|
+
|
|
9
|
+
from dkist_processing_cryonirsp.models.constants import CryonirspConstants
|
|
10
|
+
from dkist_processing_cryonirsp.tasks.instrument_polarization import generate_polcal_quality_label
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CIAssembleQualityData(AssembleQualityData):
|
|
14
|
+
"""Subclass just so that the polcal_label_list can be populated."""
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def constants_model_class(self) -> Type[CryonirspConstants]:
|
|
18
|
+
"""Grab the Cryo constants so we can have the number of beams."""
|
|
19
|
+
return CryonirspConstants
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def polcal_label_list(self) -> list[str]:
|
|
23
|
+
"""Return label(s) for Cryo CI."""
|
|
24
|
+
return [
|
|
25
|
+
generate_polcal_quality_label(arm="CI", beam=beam)
|
|
26
|
+
for beam in range(1, self.constants.num_beams + 1)
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SPAssembleQualityData(AssembleQualityData):
|
|
31
|
+
"""Subclass just so that the polcal_label_list can be populated."""
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def constants_model_class(self) -> Type[CryonirspConstants]:
|
|
35
|
+
"""Grab the Cryo constants so we can have the number of beams."""
|
|
36
|
+
return CryonirspConstants
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def polcal_label_list(self) -> list[str]:
|
|
40
|
+
"""Return labels for beams 1 and 2."""
|
|
41
|
+
return [
|
|
42
|
+
generate_polcal_quality_label(arm="SP", beam=beam)
|
|
43
|
+
for beam in range(1, self.constants.num_beams + 1)
|
|
44
|
+
]
|