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,475 @@
|
|
|
1
|
+
"""Cryo SP solar gain task."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
import scipy.ndimage as spnd
|
|
4
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
5
|
+
from dkist_processing_math.arithmetic import divide_arrays_by_array
|
|
6
|
+
from dkist_processing_math.arithmetic import subtract_array_from_arrays
|
|
7
|
+
from dkist_processing_math.statistics import average_numpy_arrays
|
|
8
|
+
from dkist_service_configuration.logging import logger
|
|
9
|
+
from scipy import signal
|
|
10
|
+
|
|
11
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
12
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
13
|
+
from dkist_processing_cryonirsp.models.task_name import CryonirspTaskName
|
|
14
|
+
from dkist_processing_cryonirsp.tasks.cryonirsp_base import CryonirspTaskBase
|
|
15
|
+
|
|
16
|
+
__all__ = ["SPSolarGainCalibration"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SPSolarGainCalibration(CryonirspTaskBase):
|
|
20
|
+
"""Task class for generating Solar Gain images for each beam.
|
|
21
|
+
|
|
22
|
+
NB: This class does not extend GainCalibrationBase, because it is highly customized
|
|
23
|
+
and incorporates several correction steps as well as solar spectrum removal.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
recipe_run_id : int
|
|
28
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
29
|
+
workflow_name : str
|
|
30
|
+
name of the workflow to which this instance of the task belongs
|
|
31
|
+
workflow_version : str
|
|
32
|
+
version of the workflow to which this instance of the task belongs
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
record_provenance = True
|
|
37
|
+
|
|
38
|
+
def run(self):
|
|
39
|
+
"""
|
|
40
|
+
For each beam.
|
|
41
|
+
|
|
42
|
+
- Do dark, lamp, and geometric corrections
|
|
43
|
+
- Compute the characteristic spectra
|
|
44
|
+
- Re-apply the spectral curvature to the characteristic spectra
|
|
45
|
+
- Re-apply angle and state offset distortions to the characteristic spectra
|
|
46
|
+
- Remove the distorted characteristic solar spectra from the original spectra
|
|
47
|
+
- Write master solar gain
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
None
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
target_exposure_conditions = self.constants.solar_gain_exposure_conditions_list
|
|
55
|
+
|
|
56
|
+
with self.apm_step(f"Computing SP gain calibrations for {target_exposure_conditions=}"):
|
|
57
|
+
for exposure_conditions in target_exposure_conditions:
|
|
58
|
+
for beam in range(1, self.constants.num_beams + 1):
|
|
59
|
+
with self.apm_processing_step(
|
|
60
|
+
f"Perform initial corrections for {beam = } and {exposure_conditions = }"
|
|
61
|
+
):
|
|
62
|
+
spectral_corrected_solar_array = self.do_initial_corrections(
|
|
63
|
+
beam=beam, exposure_conditions=exposure_conditions
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
with self.apm_processing_step(
|
|
67
|
+
f"Compute the characteristic spectrum for {beam = } and {exposure_conditions = }"
|
|
68
|
+
):
|
|
69
|
+
char_spectrum = self.compute_char_spectrum(
|
|
70
|
+
array=spectral_corrected_solar_array, beam=beam
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
with self.apm_processing_step(
|
|
74
|
+
f"Re-apply the spectral and geometric distortions for {beam = } and {exposure_conditions = }"
|
|
75
|
+
):
|
|
76
|
+
distorted_char_spectrum = self.distort_char_spectrum(char_spectrum)
|
|
77
|
+
|
|
78
|
+
with self.apm_processing_step(
|
|
79
|
+
f"Remove the solar spectrum for {beam = } and {exposure_conditions = }"
|
|
80
|
+
):
|
|
81
|
+
# This is the final gain image, as we do not normalize
|
|
82
|
+
final_gain = self.remove_solar_signal(
|
|
83
|
+
char_solar_spectra=distorted_char_spectrum,
|
|
84
|
+
beam=beam,
|
|
85
|
+
exposure_conditions=exposure_conditions,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if self.parameters.fringe_correction_on:
|
|
89
|
+
with self.apm_processing_step(
|
|
90
|
+
f"Computing final solar gain based on fringe-corrected flux-scaled lamp gain for {beam = } and {exposure_conditions = }"
|
|
91
|
+
):
|
|
92
|
+
# Compute a solar gain based on a fringe-corrected lamp gain
|
|
93
|
+
final_gain = self.compute_fringe_corrected_gain(
|
|
94
|
+
beam, exposure_conditions
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
with self.apm_writing_step(
|
|
98
|
+
f"Writing the final solar gain array for {beam = } and {exposure_conditions = }"
|
|
99
|
+
):
|
|
100
|
+
self.write_solar_gain_calibration(
|
|
101
|
+
gain_array=final_gain,
|
|
102
|
+
beam=beam,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
with self.apm_processing_step("Computing and logging quality metrics"):
|
|
106
|
+
no_of_raw_solar_frames: int = self.scratch.count_all(
|
|
107
|
+
tags=[
|
|
108
|
+
CryonirspTag.linearized(),
|
|
109
|
+
CryonirspTag.frame(),
|
|
110
|
+
CryonirspTag.task_solar_gain(),
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
self.quality_store_task_type_counts(
|
|
114
|
+
task_type=TaskName.solar_gain.value, total_frames=no_of_raw_solar_frames
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def do_initial_corrections(
|
|
118
|
+
self, beam: int, exposure_conditions: ExposureConditions
|
|
119
|
+
) -> np.ndarray:
|
|
120
|
+
"""
|
|
121
|
+
Perform dark, bad pixel, and lamp corrections on the input solar gain data.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
beam
|
|
126
|
+
The beam number
|
|
127
|
+
|
|
128
|
+
exposure_conditions
|
|
129
|
+
The exposure conditions
|
|
130
|
+
|
|
131
|
+
Returns
|
|
132
|
+
-------
|
|
133
|
+
A solar array with basic and geometric corrections
|
|
134
|
+
"""
|
|
135
|
+
# Do the basic dark and bad pixel corrections
|
|
136
|
+
basic_corrected_solar_array = self.do_dark_and_bad_pixel_corrections(
|
|
137
|
+
beam, exposure_conditions
|
|
138
|
+
)
|
|
139
|
+
# Save as intermediate result for final gain computation
|
|
140
|
+
self.intermediate_frame_write_arrays(
|
|
141
|
+
arrays=basic_corrected_solar_array,
|
|
142
|
+
beam=beam,
|
|
143
|
+
task="SC_DARK_BP_CORRECTED_ONLY",
|
|
144
|
+
)
|
|
145
|
+
# Gain correct using the lamp gain. This removes internal optical effects.
|
|
146
|
+
lamp_array = self.intermediate_frame_load_lamp_gain_array(beam=beam)
|
|
147
|
+
lamp_corrected_solar_array = next(
|
|
148
|
+
divide_arrays_by_array(basic_corrected_solar_array, lamp_array)
|
|
149
|
+
)
|
|
150
|
+
# Do the rotation and spectral corrections
|
|
151
|
+
spectral_corrected_solar_array = self.do_geometric_corrections(
|
|
152
|
+
lamp_corrected_solar_array, beam
|
|
153
|
+
)
|
|
154
|
+
# Save as an intermediate result for science users
|
|
155
|
+
self.intermediate_frame_write_arrays(
|
|
156
|
+
arrays=spectral_corrected_solar_array,
|
|
157
|
+
beam=beam,
|
|
158
|
+
task=CryonirspTaskName.spectral_corrected_solar_array.value,
|
|
159
|
+
)
|
|
160
|
+
return spectral_corrected_solar_array
|
|
161
|
+
|
|
162
|
+
def do_dark_and_bad_pixel_corrections(
|
|
163
|
+
self, beam: int, exposure_conditions: ExposureConditions
|
|
164
|
+
) -> np.ndarray:
|
|
165
|
+
"""
|
|
166
|
+
Perform dark and bad pixel corrections on the input solar gain data.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
beam
|
|
171
|
+
The beam number
|
|
172
|
+
|
|
173
|
+
exposure_conditions
|
|
174
|
+
The exposure conditions
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
A solar array with dark and bad pixel corrections
|
|
179
|
+
"""
|
|
180
|
+
# Load the necessary files
|
|
181
|
+
dark_array = self.intermediate_frame_load_dark_array(
|
|
182
|
+
beam=beam, exposure_conditions=exposure_conditions
|
|
183
|
+
)
|
|
184
|
+
# Compute the avg solar array
|
|
185
|
+
linearized_solar_arrays = self.linearized_frame_gain_array_generator(
|
|
186
|
+
beam=beam, exposure_conditions=exposure_conditions, gain_type=TaskName.solar_gain.value
|
|
187
|
+
)
|
|
188
|
+
avg_solar_array = average_numpy_arrays(linearized_solar_arrays)
|
|
189
|
+
# Dark correct it
|
|
190
|
+
dark_corrected_solar_array = next(subtract_array_from_arrays(avg_solar_array, dark_array))
|
|
191
|
+
# Correct for bad pixels
|
|
192
|
+
bad_pixel_map = self.intermediate_frame_load_bad_pixel_map(beam=beam)
|
|
193
|
+
bad_pixel_corrected_solar_array = self.corrections_correct_bad_pixels(
|
|
194
|
+
dark_corrected_solar_array, bad_pixel_map
|
|
195
|
+
)
|
|
196
|
+
return bad_pixel_corrected_solar_array
|
|
197
|
+
|
|
198
|
+
def do_geometric_corrections(self, lamp_corrected_array: np.ndarray, beam: int) -> np.ndarray:
|
|
199
|
+
"""
|
|
200
|
+
Perform geometric corrections on the input solar gain data.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
lamp_corrected_array
|
|
205
|
+
A solar array that has had dark, bad pixel and lamp gain corrections
|
|
206
|
+
|
|
207
|
+
beam
|
|
208
|
+
The beam number
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
An array that has geometric and spectral corrections
|
|
213
|
+
"""
|
|
214
|
+
# Get the parameters and save them to self for use later on...
|
|
215
|
+
self.angle = self.intermediate_frame_load_angle(beam=beam)
|
|
216
|
+
self.state_offset = self.intermediate_frame_load_state_offset(beam=beam)
|
|
217
|
+
self.spec_shift = self.intermediate_frame_load_spec_shift(beam=beam)
|
|
218
|
+
# Correct for rotation and state offset. This does not correct for spectral curvature!
|
|
219
|
+
geo_corrected_solar_array = next(
|
|
220
|
+
self.corrections_correct_geometry(lamp_corrected_array, self.state_offset, self.angle)
|
|
221
|
+
)
|
|
222
|
+
# Remove the spectral curvature
|
|
223
|
+
spectral_corrected_solar_array = next(
|
|
224
|
+
self.corrections_remove_spec_shifts(geo_corrected_solar_array, self.spec_shift)
|
|
225
|
+
)
|
|
226
|
+
return spectral_corrected_solar_array
|
|
227
|
+
|
|
228
|
+
def compute_char_spectrum(self, array: np.ndarray, beam: int) -> np.ndarray:
|
|
229
|
+
"""
|
|
230
|
+
Estimate the characteristic solar spectrum from the corrected solar gain data.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
array
|
|
235
|
+
A corrected solar array image
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
A 2D array with the estimate of the characteristic spectrum
|
|
240
|
+
"""
|
|
241
|
+
# Normalize data row by row
|
|
242
|
+
pct = self.parameters.solar_characteristic_spatial_normalization_percentile
|
|
243
|
+
array_row_norm = array / np.nanpercentile(array, pct, axis=1)[:, None]
|
|
244
|
+
# Compute characteristic spectrum
|
|
245
|
+
char_spec_1d = np.nanmedian(array_row_norm, axis=0)
|
|
246
|
+
# Expand the 1D median along the columns (along the slit)
|
|
247
|
+
median_char_spec_2d = np.tile(char_spec_1d, (array_row_norm.shape[0], 1))
|
|
248
|
+
self.intermediate_frame_write_arrays(
|
|
249
|
+
arrays=char_spec_1d, task_tag=CryonirspTag.task_characteristic_spectra(), beam=beam
|
|
250
|
+
)
|
|
251
|
+
return median_char_spec_2d
|
|
252
|
+
|
|
253
|
+
def distort_char_spectrum(self, char_spec: np.ndarray) -> np.ndarray:
|
|
254
|
+
"""
|
|
255
|
+
Re-apply the geometric distortions, that were previously removed, to the characteristic spectrum.
|
|
256
|
+
|
|
257
|
+
Parameters
|
|
258
|
+
----------
|
|
259
|
+
char_spec
|
|
260
|
+
The characteristic spectrum
|
|
261
|
+
|
|
262
|
+
Returns
|
|
263
|
+
-------
|
|
264
|
+
The characteristic spectrum with spectral curvature distortion applied
|
|
265
|
+
"""
|
|
266
|
+
# Re-distort the characteristic spectrum in the reverse order from the earlier correction
|
|
267
|
+
# 1. Add spectral curvature back
|
|
268
|
+
reshifted_spectrum = next(
|
|
269
|
+
self.corrections_remove_spec_shifts(arrays=char_spec, spec_shift=-self.spec_shift)
|
|
270
|
+
)
|
|
271
|
+
# 2. Add state offset and angular rotation back
|
|
272
|
+
distorted_spectrum = next(
|
|
273
|
+
self.corrections_distort_geometry(
|
|
274
|
+
reshifted_spectrum,
|
|
275
|
+
-self.state_offset,
|
|
276
|
+
-self.angle,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
return distorted_spectrum
|
|
280
|
+
|
|
281
|
+
def geo_corrected_data(self, beam: int, exposure_conditions: ExposureConditions) -> np.ndarray:
|
|
282
|
+
"""
|
|
283
|
+
Read the intermediate dark and bad-pixel corrected solar data saved previously.
|
|
284
|
+
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
beam
|
|
288
|
+
The beam number
|
|
289
|
+
|
|
290
|
+
exposure_conditions
|
|
291
|
+
The exposure conditions
|
|
292
|
+
|
|
293
|
+
Returns
|
|
294
|
+
-------
|
|
295
|
+
A dark and bad pixel corrected solar array
|
|
296
|
+
"""
|
|
297
|
+
array_generator = self.intermediate_frame_load_intermediate_arrays(
|
|
298
|
+
tags=[
|
|
299
|
+
CryonirspTag.task("SC_DARK_BP_CORRECTED_ONLY"),
|
|
300
|
+
CryonirspTag.beam(beam),
|
|
301
|
+
]
|
|
302
|
+
)
|
|
303
|
+
return next(array_generator)
|
|
304
|
+
|
|
305
|
+
def remove_solar_signal(
|
|
306
|
+
self,
|
|
307
|
+
char_solar_spectra: np.ndarray,
|
|
308
|
+
beam: int,
|
|
309
|
+
exposure_conditions: ExposureConditions,
|
|
310
|
+
) -> np.ndarray:
|
|
311
|
+
"""
|
|
312
|
+
Remove the (distorted) characteristic solar spectra from the input solar data.
|
|
313
|
+
|
|
314
|
+
Parameters
|
|
315
|
+
----------
|
|
316
|
+
char_solar_spectra
|
|
317
|
+
The characteristic spectrum
|
|
318
|
+
|
|
319
|
+
beam
|
|
320
|
+
The beam number
|
|
321
|
+
|
|
322
|
+
exposure_conditions
|
|
323
|
+
The exposure conditions
|
|
324
|
+
|
|
325
|
+
Returns
|
|
326
|
+
-------
|
|
327
|
+
A geometric and spectrally corrected array with the solar signal removed
|
|
328
|
+
"""
|
|
329
|
+
logger.info(
|
|
330
|
+
f"Removing characteristic solar spectra from {beam=} and {exposure_conditions =}"
|
|
331
|
+
)
|
|
332
|
+
input_gain = self.geo_corrected_data(beam=beam, exposure_conditions=exposure_conditions)
|
|
333
|
+
array_with_solar_signal_removed = input_gain / char_solar_spectra
|
|
334
|
+
return array_with_solar_signal_removed
|
|
335
|
+
|
|
336
|
+
def write_solar_gain_calibration(self, gain_array: np.ndarray, beam: int) -> None:
|
|
337
|
+
"""
|
|
338
|
+
Write the final gain array as a file.
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
gain_array
|
|
343
|
+
The final gain array
|
|
344
|
+
|
|
345
|
+
beam
|
|
346
|
+
The beam number
|
|
347
|
+
|
|
348
|
+
Returns
|
|
349
|
+
-------
|
|
350
|
+
None
|
|
351
|
+
"""
|
|
352
|
+
logger.info(f"Writing final SolarGain for {beam=}")
|
|
353
|
+
self.intermediate_frame_write_arrays(
|
|
354
|
+
arrays=gain_array,
|
|
355
|
+
beam=beam,
|
|
356
|
+
task_tag=CryonirspTag.task_solar_gain(),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
def compute_fringe_corrected_gain(self, beam: int, exposure_conditions: float) -> np.ndarray:
|
|
360
|
+
"""
|
|
361
|
+
Compute a solar gain based on a scaled and fringe-removed lamp gain.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
beam
|
|
366
|
+
The beam number
|
|
367
|
+
|
|
368
|
+
exposure_conditions
|
|
369
|
+
The exposure conditions
|
|
370
|
+
|
|
371
|
+
Returns
|
|
372
|
+
-------
|
|
373
|
+
A lamp gain array that has been scaled to the average solar flux and has been fringe-corrected
|
|
374
|
+
"""
|
|
375
|
+
apm_str = f"{beam = } and {exposure_conditions = }"
|
|
376
|
+
|
|
377
|
+
with self.apm_processing_step(f"Perform initial corrections for {apm_str}"):
|
|
378
|
+
corrected_solar_array = self.do_dark_and_bad_pixel_corrections(
|
|
379
|
+
beam=beam, exposure_conditions=exposure_conditions
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
with self.apm_processing_step(f"Compute the flux-scaled lamp gain for {apm_str}"):
|
|
383
|
+
scaled_lamp_array = self.compute_flux_scaled_lamp_gain(corrected_solar_array, beam)
|
|
384
|
+
|
|
385
|
+
with self.apm_processing_step(f"Apply spectral filtering for {apm_str}"):
|
|
386
|
+
filtered_lamp_array = self.apply_spectral_and_spatial_filtering(scaled_lamp_array)
|
|
387
|
+
|
|
388
|
+
with self.apm_processing_step(f"Isolate and remove fringes for {apm_str}"):
|
|
389
|
+
final_gain_array = self.isolate_and_remove_fringes(
|
|
390
|
+
filtered_lamp_array, scaled_lamp_array
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
return final_gain_array
|
|
394
|
+
|
|
395
|
+
def compute_flux_scaled_lamp_gain(
|
|
396
|
+
self, corrected_solar_array: np.ndarray, beam: int
|
|
397
|
+
) -> np.ndarray:
|
|
398
|
+
"""
|
|
399
|
+
Scale the lamp gain image to match the flux of the average corrected solar array.
|
|
400
|
+
|
|
401
|
+
The average corrected solar array is gain corrected using the lamp gain image.
|
|
402
|
+
The flux ratio of the average corrected solar array relative to the lamp gain is computed
|
|
403
|
+
as the median of the lamp-gain-corrected solar array along the spectral axis.
|
|
404
|
+
The lamp gain is then scaled on a column by column basis by the flux ratio to yield
|
|
405
|
+
a gain image that is similar the average corrected solar array.
|
|
406
|
+
|
|
407
|
+
Parameters
|
|
408
|
+
----------
|
|
409
|
+
corrected_solar_array
|
|
410
|
+
The dark and bad pixel corrected average solar array
|
|
411
|
+
|
|
412
|
+
beam
|
|
413
|
+
The beam number
|
|
414
|
+
|
|
415
|
+
Returns
|
|
416
|
+
-------
|
|
417
|
+
The scaled lamp array
|
|
418
|
+
"""
|
|
419
|
+
lamp_array = self.intermediate_frame_load_lamp_gain_array(beam=beam)
|
|
420
|
+
lamp_corrected_solar_array = next(divide_arrays_by_array(corrected_solar_array, lamp_array))
|
|
421
|
+
flux_ratio = np.nanmedian(lamp_corrected_solar_array, axis=1)
|
|
422
|
+
scaled_lamp_array = lamp_array * flux_ratio[:, None]
|
|
423
|
+
return scaled_lamp_array
|
|
424
|
+
|
|
425
|
+
def apply_spectral_and_spatial_filtering(self, scaled_lamp_array: np.ndarray) -> np.ndarray:
|
|
426
|
+
"""
|
|
427
|
+
Apply spectral and spatial filtering to the scaled lamp array.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
scaled_lamp_array
|
|
432
|
+
The input scaled lamp array
|
|
433
|
+
|
|
434
|
+
Returns
|
|
435
|
+
-------
|
|
436
|
+
The filtered lamp array
|
|
437
|
+
"""
|
|
438
|
+
spectral_filter_size = self.parameters.fringe_correction_spectral_filter_size
|
|
439
|
+
spatial_filter_size = self.parameters.fringe_correction_spatial_filter_size
|
|
440
|
+
spectral_filtered_lamp_array = spnd.gaussian_filter(scaled_lamp_array, spectral_filter_size)
|
|
441
|
+
partial_filtered_array = scaled_lamp_array / spectral_filtered_lamp_array
|
|
442
|
+
spatial_filtered_lamp_gain = spnd.gaussian_filter(
|
|
443
|
+
partial_filtered_array, spatial_filter_size
|
|
444
|
+
)
|
|
445
|
+
return spatial_filtered_lamp_gain
|
|
446
|
+
|
|
447
|
+
def isolate_and_remove_fringes(
|
|
448
|
+
self, filtered_lamp_array: np.ndarray, scaled_lamp_array: np.ndarray
|
|
449
|
+
) -> np.ndarray:
|
|
450
|
+
"""
|
|
451
|
+
Use a low pass filter to estimate the fringes and then remove them from the scaled lamp array.
|
|
452
|
+
|
|
453
|
+
Parameters
|
|
454
|
+
----------
|
|
455
|
+
filtered_lamp_array
|
|
456
|
+
The filtered lamp gain array
|
|
457
|
+
|
|
458
|
+
scaled_lamp_array
|
|
459
|
+
The scaled lamp gain array
|
|
460
|
+
|
|
461
|
+
Returns
|
|
462
|
+
-------
|
|
463
|
+
The scaled lamp array with fringes removed.
|
|
464
|
+
"""
|
|
465
|
+
# The fringe cutoff is specified as a period so we invert it to get the lowpass cutoff frequency
|
|
466
|
+
cutoff_freq = 1.0 / self.parameters.fringe_correction_lowpass_cutoff_period
|
|
467
|
+
# Compute the Butterworth lowpass filter coefficients
|
|
468
|
+
numerator, denominator = signal.butter(2, cutoff_freq, btype="lowpass", fs=1)
|
|
469
|
+
# Apply the lowpass Butterworth filter and use Gustafsson's method to better preserve the array edges
|
|
470
|
+
low_pass_filtered_array = signal.filtfilt(
|
|
471
|
+
numerator, denominator, filtered_lamp_array, axis=1, method="gust"
|
|
472
|
+
)
|
|
473
|
+
fringe_estimate = filtered_lamp_array / low_pass_filtered_array
|
|
474
|
+
fringe_removed_array = scaled_lamp_array / fringe_estimate
|
|
475
|
+
return fringe_removed_array
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Tasks for transferring scratch data to custom location for post-pipeline analysis."""
|
|
2
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
3
|
+
from dkist_processing_common.tasks.mixin.globus import GlobusTransferItem
|
|
4
|
+
from dkist_processing_common.tasks.trial_output_data import TransferTrialDataBase
|
|
5
|
+
|
|
6
|
+
__all__ = ["TransferCryoTrialData"]
|
|
7
|
+
|
|
8
|
+
from dkist_processing_cryonirsp.models.task_name import CryonirspTaskName
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TransferCryoTrialData(TransferTrialDataBase):
|
|
12
|
+
"""Transfer DEBUG, Intermediate, and/or output data to the trial location."""
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def intermediate_task_names(self) -> list[str]:
|
|
16
|
+
"""Grab all the Calibration products used to calibrate science data."""
|
|
17
|
+
return [
|
|
18
|
+
TaskName.dark.value,
|
|
19
|
+
TaskName.lamp_gain.value,
|
|
20
|
+
TaskName.geometric_angle.value,
|
|
21
|
+
TaskName.geometric_offsets.value,
|
|
22
|
+
TaskName.geometric_spectral_shifts.value,
|
|
23
|
+
TaskName.solar_gain.value,
|
|
24
|
+
TaskName.demodulation_matrices.value,
|
|
25
|
+
CryonirspTaskName.spectral_corrected_solar_array.value,
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
def build_transfer_list(self) -> list[GlobusTransferItem]:
|
|
29
|
+
"""
|
|
30
|
+
Build a list containing all files we want to transfer to the trial environment.
|
|
31
|
+
|
|
32
|
+
The classes of/specific files to transfer are defined in the switches that look at the recipe run configuration.
|
|
33
|
+
"""
|
|
34
|
+
transfer_list = []
|
|
35
|
+
|
|
36
|
+
if self.debug_frame_switch:
|
|
37
|
+
transfer_list += self.build_debug_frame_transfer_list()
|
|
38
|
+
|
|
39
|
+
if self.intermediate_frame_switch:
|
|
40
|
+
transfer_list += self.build_intermediate_frame_transfer_list()
|
|
41
|
+
|
|
42
|
+
if self.output_frame_switch:
|
|
43
|
+
transfer_list += self.build_output_frame_transfer_list()
|
|
44
|
+
transfer_list += self.build_output_movie_transfer_list()
|
|
45
|
+
|
|
46
|
+
if self.specific_frame_tag_lists:
|
|
47
|
+
transfer_list += self.build_transfer_list_from_tag_lists(self.specific_frame_tag_lists)
|
|
48
|
+
|
|
49
|
+
if self.output_dataset_inventory_switch:
|
|
50
|
+
transfer_list += self.build_output_dataset_inventory_transfer_list()
|
|
51
|
+
|
|
52
|
+
if self.output_asdf_switch:
|
|
53
|
+
transfer_list += self.build_output_asdf_transfer_list()
|
|
54
|
+
|
|
55
|
+
if self.output_quality_report_switch:
|
|
56
|
+
transfer_list += self.build_output_quality_report_transfer_list()
|
|
57
|
+
|
|
58
|
+
if self.output_quality_data_switch:
|
|
59
|
+
transfer_list += self.build_output_quality_data_transfer_list()
|
|
60
|
+
|
|
61
|
+
return transfer_list
|