dkist-processing-visp 4.0.0__py3-none-any.whl → 5.0.0__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.
- dkist_processing_visp/models/constants.py +50 -9
- dkist_processing_visp/models/fits_access.py +5 -1
- dkist_processing_visp/models/metric_code.py +10 -0
- dkist_processing_visp/models/parameters.py +128 -19
- dkist_processing_visp/parsers/spectrograph_configuration.py +75 -0
- dkist_processing_visp/parsers/visp_l0_fits_access.py +6 -0
- dkist_processing_visp/tasks/geometric.py +115 -7
- dkist_processing_visp/tasks/l1_output_data.py +202 -0
- dkist_processing_visp/tasks/lamp.py +50 -91
- dkist_processing_visp/tasks/parse.py +19 -0
- dkist_processing_visp/tasks/science.py +14 -14
- dkist_processing_visp/tasks/solar.py +894 -451
- dkist_processing_visp/tasks/visp_base.py +1 -0
- dkist_processing_visp/tests/conftest.py +98 -35
- dkist_processing_visp/tests/header_models.py +71 -20
- dkist_processing_visp/tests/local_trial_workflows/local_trial_helpers.py +25 -1
- dkist_processing_visp/tests/test_assemble_quality.py +89 -4
- dkist_processing_visp/tests/test_geometric.py +40 -0
- dkist_processing_visp/tests/test_instrument_polarization.py +2 -1
- dkist_processing_visp/tests/test_lamp.py +17 -22
- dkist_processing_visp/tests/test_parameters.py +120 -18
- dkist_processing_visp/tests/test_parse.py +73 -1
- dkist_processing_visp/tests/test_science.py +5 -6
- dkist_processing_visp/tests/test_solar.py +319 -102
- dkist_processing_visp/tests/test_visp_constants.py +35 -6
- {dkist_processing_visp-4.0.0.dist-info → dkist_processing_visp-5.0.0.dist-info}/METADATA +40 -37
- {dkist_processing_visp-4.0.0.dist-info → dkist_processing_visp-5.0.0.dist-info}/RECORD +31 -30
- docs/conf.py +4 -1
- docs/gain_correction.rst +50 -42
- dkist_processing_visp/tasks/mixin/line_zones.py +0 -116
- {dkist_processing_visp-4.0.0.dist-info → dkist_processing_visp-5.0.0.dist-info}/WHEEL +0 -0
- {dkist_processing_visp-4.0.0.dist-info → dkist_processing_visp-5.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,14 +1,216 @@
|
|
|
1
1
|
"""Subclass of AssembleQualityData that causes the correct polcal metrics to build."""
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
4
|
+
from dkist_processing_common.codecs.asdf import asdf_decoder
|
|
5
|
+
from dkist_processing_common.models.quality import Plot2D
|
|
6
|
+
from dkist_processing_common.models.quality import ReportMetric
|
|
7
|
+
from dkist_processing_common.models.quality import VerticalMultiPanePlot2D
|
|
3
8
|
from dkist_processing_common.tasks import AssembleQualityData
|
|
4
9
|
|
|
5
10
|
__all__ = ["VispAssembleQualityData"]
|
|
6
11
|
|
|
12
|
+
from dkist_processing_visp.models.constants import VispConstants
|
|
13
|
+
from dkist_processing_visp.models.metric_code import VispMetricCode
|
|
14
|
+
from dkist_processing_visp.models.tags import VispTag
|
|
15
|
+
|
|
7
16
|
|
|
8
17
|
class VispAssembleQualityData(AssembleQualityData):
|
|
9
18
|
"""Subclass just so that the polcal_label_list can be populated."""
|
|
10
19
|
|
|
20
|
+
constants: VispConstants
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def constants_model_class(self):
|
|
24
|
+
"""Get ViSP pipeline constants."""
|
|
25
|
+
return VispConstants
|
|
26
|
+
|
|
11
27
|
@property
|
|
12
28
|
def polcal_label_list(self) -> list[str]:
|
|
13
29
|
"""Return labels for beams 1 and 2."""
|
|
14
30
|
return ["Beam 1", "Beam 2"]
|
|
31
|
+
|
|
32
|
+
def quality_assemble_data(self, polcal_label_list: list[str] | None = None) -> list[dict]:
|
|
33
|
+
"""
|
|
34
|
+
Assemble the full quality report and insert ViSP-specific metrics.
|
|
35
|
+
|
|
36
|
+
We try to place the new metrics right before default polcal ones, if possible.
|
|
37
|
+
"""
|
|
38
|
+
vignette_metrics = []
|
|
39
|
+
for beam in range(1, self.constants.num_beams + 1):
|
|
40
|
+
vignette_metrics.append(self.build_first_vignette_metric(beam=beam))
|
|
41
|
+
vignette_metrics.append(self.build_final_vignette_metric(beam=beam))
|
|
42
|
+
|
|
43
|
+
report = super().quality_assemble_data(polcal_label_list=polcal_label_list)
|
|
44
|
+
|
|
45
|
+
# Look for the first "PolCal" metric
|
|
46
|
+
first_polcal_metric_index = 0
|
|
47
|
+
try:
|
|
48
|
+
while not report[first_polcal_metric_index]["name"].lower().startswith("polcal"):
|
|
49
|
+
first_polcal_metric_index += 1
|
|
50
|
+
except:
|
|
51
|
+
# Wasn't found for whatever reason. No big deal, just put the new metrics at the front of the list
|
|
52
|
+
first_polcal_metric_index = 0
|
|
53
|
+
|
|
54
|
+
final_report = (
|
|
55
|
+
report[:first_polcal_metric_index]
|
|
56
|
+
+ vignette_metrics
|
|
57
|
+
+ report[first_polcal_metric_index:]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return final_report
|
|
61
|
+
|
|
62
|
+
def build_first_vignette_metric(self, beam: int) -> dict:
|
|
63
|
+
"""Build a ReportMetric showing the initial atlas-with-continuum fit and residuals."""
|
|
64
|
+
data = next(
|
|
65
|
+
self.read(
|
|
66
|
+
tags=[VispTag.quality(VispMetricCode.solar_first_vignette), VispTag.beam(beam)],
|
|
67
|
+
decoder=asdf_decoder,
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
wave_vec = data["output_wave_vec"].tolist()
|
|
72
|
+
input_spectrum = data["input_spectrum"].tolist()
|
|
73
|
+
best_fit_atlas = data["best_fit_atlas"].tolist()
|
|
74
|
+
continuum = data["best_fit_continuum"].tolist()
|
|
75
|
+
residuals = data["residuals"].tolist()
|
|
76
|
+
|
|
77
|
+
fit_series = {
|
|
78
|
+
"Raw input spectrum": [wave_vec, input_spectrum],
|
|
79
|
+
"Best fit atlas": [wave_vec, best_fit_atlas],
|
|
80
|
+
"Best fit continuum": [wave_vec, continuum],
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fit_plot_kwargs = {
|
|
84
|
+
"Raw input spectrum": {
|
|
85
|
+
"ls": "-",
|
|
86
|
+
"ms": 0,
|
|
87
|
+
"color": "#FAA61C",
|
|
88
|
+
"zorder": 2.0,
|
|
89
|
+
"lw": 4,
|
|
90
|
+
"alpha": 0.6,
|
|
91
|
+
},
|
|
92
|
+
"Best fit atlas": {"color": "k", "ls": "-", "ms": 0, "zorder": 2.1},
|
|
93
|
+
"Best fit continuum": {"ls": "-", "ms": 0, "color": "g", "zorder": 2.2},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fit_plot = Plot2D(
|
|
97
|
+
xlabel="Wavelength [nm]",
|
|
98
|
+
ylabel="Signal",
|
|
99
|
+
series_data=fit_series,
|
|
100
|
+
plot_kwargs=fit_plot_kwargs,
|
|
101
|
+
sort_series=False,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
residuals_series = {"Residuals": [wave_vec, residuals]}
|
|
105
|
+
residuals_plot_kwargs = {"Residuals": {"ls": "-", "color": "k", "ms": 0}}
|
|
106
|
+
|
|
107
|
+
y_min = np.nanpercentile(residuals, 2)
|
|
108
|
+
y_max = np.nanpercentile(residuals, 98)
|
|
109
|
+
y_range = y_max - y_min
|
|
110
|
+
y_min -= 0.1 * y_range
|
|
111
|
+
y_max += 0.1 * y_range
|
|
112
|
+
residuals_plot = Plot2D(
|
|
113
|
+
xlabel="Wavelength [nm]",
|
|
114
|
+
ylabel=r"$\frac{\mathrm{Obs - Atlas}}{\mathrm{Obs}}$",
|
|
115
|
+
series_data=residuals_series,
|
|
116
|
+
plot_kwargs=residuals_plot_kwargs,
|
|
117
|
+
ylim=(y_min, y_max),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
plot_list = [fit_plot, residuals_plot]
|
|
121
|
+
height_ratios = [1.5, 1.0]
|
|
122
|
+
|
|
123
|
+
full_plot = VerticalMultiPanePlot2D(
|
|
124
|
+
top_to_bottom_plot_list=plot_list,
|
|
125
|
+
match_x_axes=True,
|
|
126
|
+
no_gap=True,
|
|
127
|
+
top_to_bottom_height_ratios=height_ratios,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
metric = ReportMetric(
|
|
131
|
+
name=f"Initial Vignette Estimation - Beam {beam}",
|
|
132
|
+
description="These plots show the solar atlas fit used to estimate the initial, 1D spectral vignette "
|
|
133
|
+
"present in solar gain frames. The vignette signature is taken to be the fit continuum shown.",
|
|
134
|
+
metric_code=VispMetricCode.solar_first_vignette,
|
|
135
|
+
facet=self._format_facet(f"Beam {beam}"),
|
|
136
|
+
multi_plot_data=full_plot,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
return metric.model_dump()
|
|
140
|
+
|
|
141
|
+
def build_final_vignette_metric(self, beam: int) -> dict:
|
|
142
|
+
"""Build a ReportMetric showing the quality of the vignette correction on solar gain data."""
|
|
143
|
+
data = next(
|
|
144
|
+
self.read(
|
|
145
|
+
tags=[VispTag.quality(VispMetricCode.solar_final_vignette), VispTag.beam(beam)],
|
|
146
|
+
decoder=asdf_decoder,
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
wave_vec = data["output_wave_vec"].tolist()
|
|
151
|
+
median_spec = data["median_spec"].tolist()
|
|
152
|
+
low_deviation = data["low_deviation"]
|
|
153
|
+
high_deviation = data["high_deviation"]
|
|
154
|
+
diff = (high_deviation - low_deviation).tolist()
|
|
155
|
+
low_deviation = low_deviation.tolist()
|
|
156
|
+
high_deviation = high_deviation.tolist()
|
|
157
|
+
|
|
158
|
+
bounds_series = {
|
|
159
|
+
"Median solar signal": [wave_vec, median_spec],
|
|
160
|
+
"5th percentile bounds": [wave_vec, low_deviation],
|
|
161
|
+
"95th percentile bounds": [wave_vec, high_deviation],
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
bounds_plot_kwargs = {
|
|
165
|
+
"Median solar signal": {"ls": "-", "color": "k", "alpha": 0.8, "ms": 0, "zorder": 2.2},
|
|
166
|
+
"5th percentile bounds": {"color": "#1E317A", "ls": "-", "ms": 0, "zorder": 2.0},
|
|
167
|
+
"95th percentile bounds": {"ls": "-", "color": "#FAA61C", "ms": 0, "zorder": 2.1},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
bounds_plot = Plot2D(
|
|
171
|
+
xlabel="Wavelength [nm]",
|
|
172
|
+
ylabel="Signal",
|
|
173
|
+
series_data=bounds_series,
|
|
174
|
+
plot_kwargs=bounds_plot_kwargs,
|
|
175
|
+
sort_series=False,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
residuals_series = {"Residuals": [wave_vec, diff]}
|
|
179
|
+
residuals_plot_kwargs = {"Residuals": {"ls": "-", "color": "k", "ms": 0}}
|
|
180
|
+
|
|
181
|
+
y_min = np.nanpercentile(diff, 5)
|
|
182
|
+
y_max = np.nanpercentile(diff, 95)
|
|
183
|
+
y_range = y_max - y_min
|
|
184
|
+
y_min -= 0.1 * y_range
|
|
185
|
+
y_max += 0.1 * y_range
|
|
186
|
+
residuals_plot = Plot2D(
|
|
187
|
+
xlabel="Wavelength [nm]",
|
|
188
|
+
ylabel="95th - 5th percentile",
|
|
189
|
+
series_data=residuals_series,
|
|
190
|
+
plot_kwargs=residuals_plot_kwargs,
|
|
191
|
+
ylim=(y_min, y_max),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
plot_list = [bounds_plot, residuals_plot]
|
|
195
|
+
height_ratios = [1.5, 1.0]
|
|
196
|
+
|
|
197
|
+
full_plot = VerticalMultiPanePlot2D(
|
|
198
|
+
top_to_bottom_plot_list=plot_list,
|
|
199
|
+
match_x_axes=True,
|
|
200
|
+
no_gap=True,
|
|
201
|
+
top_to_bottom_height_ratios=height_ratios,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
metric = ReportMetric(
|
|
205
|
+
name=f"Final Vignette Estimation - Beam {beam}",
|
|
206
|
+
description="These plots show how well the full, 2D vignette signal was removed from solar gain frames. "
|
|
207
|
+
"The median solar signal shows a full spatial median of the vignette corrected solar gain; "
|
|
208
|
+
"this should be very close to the true solar spectrum incident on the DKIST optics. "
|
|
209
|
+
"The 5th and 9th percentile ranges show how stable this spectrum is along the spatial dimension "
|
|
210
|
+
"after removing the vignette signal.",
|
|
211
|
+
metric_code=VispMetricCode.solar_final_vignette,
|
|
212
|
+
facet=self._format_facet(f"Beam {beam}"),
|
|
213
|
+
multi_plot_data=full_plot,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return metric.model_dump()
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""ViSP lamp calibration task. See :doc:`this page </gain_correction>` for more information."""
|
|
2
2
|
|
|
3
|
-
import numpy as np
|
|
4
3
|
from dkist_processing_common.codecs.fits import fits_access_decoder
|
|
5
4
|
from dkist_processing_common.codecs.fits import fits_array_decoder
|
|
6
5
|
from dkist_processing_common.codecs.fits import fits_array_encoder
|
|
@@ -30,13 +29,14 @@ class LampCalibration(
|
|
|
30
29
|
|
|
31
30
|
Parameters
|
|
32
31
|
----------
|
|
33
|
-
recipe_run_id
|
|
32
|
+
recipe_run_id
|
|
34
33
|
id of the recipe run used to identify the workflow run this task is part of
|
|
35
|
-
|
|
34
|
+
|
|
35
|
+
workflow_name
|
|
36
36
|
name of the workflow to which this instance of the task belongs
|
|
37
|
-
workflow_version : str
|
|
38
|
-
version of the workflow to which this instance of the task belongs
|
|
39
37
|
|
|
38
|
+
workflow_version
|
|
39
|
+
version of the workflow to which this instance of the task belongs
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
42
|
record_provenance = True
|
|
@@ -45,9 +45,11 @@ class LampCalibration(
|
|
|
45
45
|
"""
|
|
46
46
|
For each beam.
|
|
47
47
|
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
48
|
+
- Normalize all input arrays by the number of frames per FPA
|
|
49
|
+
- Subtract the average dark frame corresponding to the matching readout exposure time
|
|
50
|
+
- Average all different readout exposure time arrays (if applicable)
|
|
51
|
+
- Interpolate over the hairlines
|
|
52
|
+
- Write final lamp gain to disk
|
|
51
53
|
- Record quality metrics
|
|
52
54
|
|
|
53
55
|
Returns
|
|
@@ -58,9 +60,11 @@ class LampCalibration(
|
|
|
58
60
|
with self.telemetry_span(
|
|
59
61
|
f"Generate lamp gains for {self.constants.num_beams} beams and {len(self.constants.lamp_readout_exp_times)} exposure times"
|
|
60
62
|
):
|
|
61
|
-
for
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
for beam in range(1, self.constants.num_beams + 1):
|
|
64
|
+
all_exp_time_arrays = []
|
|
65
|
+
for readout_exp_time in self.constants.lamp_readout_exp_times:
|
|
66
|
+
apm_str = f"{beam = } and {readout_exp_time = }"
|
|
67
|
+
logger.info(f"Load dark for beam {apm_str}")
|
|
64
68
|
dark_array = next(
|
|
65
69
|
self.read(
|
|
66
70
|
tags=VispTag.intermediate_frame_dark(
|
|
@@ -70,19 +74,44 @@ class LampCalibration(
|
|
|
70
74
|
)
|
|
71
75
|
)
|
|
72
76
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
with self.telemetry_span(f"Computing gain for {apm_str}"):
|
|
78
|
+
tags = [
|
|
79
|
+
VispTag.input(),
|
|
80
|
+
VispTag.frame(),
|
|
81
|
+
VispTag.task_lamp_gain(),
|
|
82
|
+
VispTag.readout_exp_time(readout_exp_time),
|
|
83
|
+
]
|
|
84
|
+
input_lamp_gain_objs = self.read(
|
|
85
|
+
tags=tags,
|
|
86
|
+
decoder=fits_access_decoder,
|
|
87
|
+
fits_access_class=VispL0FitsAccess,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
readout_normalized_arrays = (
|
|
91
|
+
self.beam_access_get_beam(o.data, beam=beam) / o.num_raw_frames_per_fpa
|
|
92
|
+
for o in input_lamp_gain_objs
|
|
78
93
|
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
readout_exp_time=readout_exp_time,
|
|
94
|
+
averaged_gain_data = average_numpy_arrays(readout_normalized_arrays)
|
|
95
|
+
|
|
96
|
+
dark_corrected_gain_data = next(
|
|
97
|
+
subtract_array_from_arrays(averaged_gain_data, dark_array)
|
|
84
98
|
)
|
|
85
99
|
|
|
100
|
+
all_exp_time_arrays.append(dark_corrected_gain_data)
|
|
101
|
+
|
|
102
|
+
avg_gain_array = average_numpy_arrays(all_exp_time_arrays)
|
|
103
|
+
filtered_gain_data = self.corrections_mask_hairlines(avg_gain_array)
|
|
104
|
+
|
|
105
|
+
with self.telemetry_span(f"Writing gain array for {apm_str}"):
|
|
106
|
+
self.write(
|
|
107
|
+
data=filtered_gain_data,
|
|
108
|
+
tags=[
|
|
109
|
+
VispTag.intermediate_frame(beam=beam),
|
|
110
|
+
VispTag.task_lamp_gain(),
|
|
111
|
+
],
|
|
112
|
+
encoder=fits_array_encoder,
|
|
113
|
+
)
|
|
114
|
+
|
|
86
115
|
with self.telemetry_span("Computing and logging quality metrics"):
|
|
87
116
|
no_of_raw_lamp_frames: int = self.scratch.count_all(
|
|
88
117
|
tags=[
|
|
@@ -95,73 +124,3 @@ class LampCalibration(
|
|
|
95
124
|
self.quality_store_task_type_counts(
|
|
96
125
|
task_type=TaskName.lamp_gain.value, total_frames=no_of_raw_lamp_frames
|
|
97
126
|
)
|
|
98
|
-
|
|
99
|
-
def compute_and_write_master_lamp_gain_for_modstate(
|
|
100
|
-
self,
|
|
101
|
-
modstate: int,
|
|
102
|
-
dark_array: np.ndarray,
|
|
103
|
-
beam: int,
|
|
104
|
-
readout_exp_time: float,
|
|
105
|
-
) -> None:
|
|
106
|
-
"""
|
|
107
|
-
Compute and write master lamp gain for a given modstate and beam.
|
|
108
|
-
|
|
109
|
-
Generally the algorithm is:
|
|
110
|
-
1. Average input gain arrays
|
|
111
|
-
2. Subtract average dark to get the dark corrected gain data
|
|
112
|
-
3. Normalize each beam to unity mean
|
|
113
|
-
4. Write to disk
|
|
114
|
-
|
|
115
|
-
Parameters
|
|
116
|
-
----------
|
|
117
|
-
modstate : int
|
|
118
|
-
The modulator state to calculate the master lamp gain for
|
|
119
|
-
|
|
120
|
-
dark_array : np.ndarray
|
|
121
|
-
The master dark to be subtracted from each lamp gain file
|
|
122
|
-
|
|
123
|
-
beam : int
|
|
124
|
-
The number of the beam
|
|
125
|
-
|
|
126
|
-
readout_exp_time : float
|
|
127
|
-
Exposure time of single readout
|
|
128
|
-
|
|
129
|
-
Returns
|
|
130
|
-
-------
|
|
131
|
-
None
|
|
132
|
-
"""
|
|
133
|
-
apm_str = f"{beam = }, {modstate = }, and {readout_exp_time = }"
|
|
134
|
-
# Get the input lamp gain arrays
|
|
135
|
-
tags = [
|
|
136
|
-
VispTag.input(),
|
|
137
|
-
VispTag.frame(),
|
|
138
|
-
VispTag.task_lamp_gain(),
|
|
139
|
-
VispTag.modstate(modstate),
|
|
140
|
-
VispTag.readout_exp_time(readout_exp_time),
|
|
141
|
-
]
|
|
142
|
-
input_lamp_gain_objs = self.read(
|
|
143
|
-
tags=tags, decoder=fits_access_decoder, fits_access_class=VispL0FitsAccess
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
with self.telemetry_span(f"Computing gain for {apm_str}"):
|
|
147
|
-
|
|
148
|
-
readout_normalized_arrays = (
|
|
149
|
-
self.beam_access_get_beam(o.data, beam=beam) / o.num_raw_frames_per_fpa
|
|
150
|
-
for o in input_lamp_gain_objs
|
|
151
|
-
)
|
|
152
|
-
averaged_gain_data = average_numpy_arrays(readout_normalized_arrays)
|
|
153
|
-
|
|
154
|
-
dark_corrected_gain_data = next(
|
|
155
|
-
subtract_array_from_arrays(averaged_gain_data, dark_array)
|
|
156
|
-
)
|
|
157
|
-
filtered_gain_data = self.corrections_mask_hairlines(dark_corrected_gain_data)
|
|
158
|
-
|
|
159
|
-
with self.telemetry_span(f"Writing gain array for {apm_str}"):
|
|
160
|
-
self.write(
|
|
161
|
-
data=filtered_gain_data,
|
|
162
|
-
tags=[
|
|
163
|
-
VispTag.intermediate_frame(beam=beam, modstate=modstate),
|
|
164
|
-
VispTag.task_lamp_gain(),
|
|
165
|
-
],
|
|
166
|
-
encoder=fits_array_encoder,
|
|
167
|
-
)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import TypeVar
|
|
4
4
|
|
|
5
|
+
from dkist_processing_common.models.fits_access import MetadataKey
|
|
5
6
|
from dkist_processing_common.models.flower_pot import Stem
|
|
6
7
|
from dkist_processing_common.models.task_name import TaskName
|
|
7
8
|
from dkist_processing_common.parsers.cs_step import CSStepFlower
|
|
@@ -15,6 +16,7 @@ from dkist_processing_common.parsers.time import ObsIpStartTimeBud
|
|
|
15
16
|
from dkist_processing_common.parsers.time import ReadoutExpTimeFlower
|
|
16
17
|
from dkist_processing_common.parsers.time import TaskExposureTimesBud
|
|
17
18
|
from dkist_processing_common.parsers.time import TaskReadoutExpTimesBud
|
|
19
|
+
from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
|
|
18
20
|
from dkist_processing_common.parsers.unique_bud import UniqueBud
|
|
19
21
|
from dkist_processing_common.parsers.wavelength import ObserveWavelengthBud
|
|
20
22
|
from dkist_processing_common.tasks import ParseL0InputDataBase
|
|
@@ -29,6 +31,8 @@ from dkist_processing_visp.parsers.modulator_states import NumberModulatorStates
|
|
|
29
31
|
from dkist_processing_visp.parsers.polarimeter_mode import PolarimeterModeBud
|
|
30
32
|
from dkist_processing_visp.parsers.raster_step import RasterScanStepFlower
|
|
31
33
|
from dkist_processing_visp.parsers.raster_step import TotalRasterStepsBud
|
|
34
|
+
from dkist_processing_visp.parsers.spectrograph_configuration import IncidentLightAngleBud
|
|
35
|
+
from dkist_processing_visp.parsers.spectrograph_configuration import ReflectedLightAngleBud
|
|
32
36
|
from dkist_processing_visp.parsers.time import DarkReadoutExpTimePickyBud
|
|
33
37
|
from dkist_processing_visp.parsers.time import NonDarkNonPolcalTaskReadoutExpTimesBud
|
|
34
38
|
from dkist_processing_visp.parsers.visp_l0_fits_access import VispL0FitsAccess
|
|
@@ -74,6 +78,7 @@ class ParseL0VispInputData(ParseL0InputDataBase):
|
|
|
74
78
|
def constant_buds(self) -> list[S]:
|
|
75
79
|
"""Add ViSP specific constants to common constants."""
|
|
76
80
|
return super().constant_buds + [
|
|
81
|
+
UniqueBud(constant_name=VispBudName.arm_id.value, metadata_key=VispMetadataKey.arm_id),
|
|
77
82
|
NumMapScansBud(),
|
|
78
83
|
TotalRasterStepsBud(),
|
|
79
84
|
NumCSStepBud(self.parameters.max_cs_step_time_sec),
|
|
@@ -84,6 +89,20 @@ class ParseL0VispInputData(ParseL0InputDataBase):
|
|
|
84
89
|
RetarderNameBud(),
|
|
85
90
|
NonDarkNonPolcalTaskReadoutExpTimesBud(),
|
|
86
91
|
DarkReadoutExpTimePickyBud(),
|
|
92
|
+
IncidentLightAngleBud(),
|
|
93
|
+
ReflectedLightAngleBud(),
|
|
94
|
+
TaskUniqueBud(
|
|
95
|
+
constant_name=VispBudName.grating_constant_inverse_mm.value,
|
|
96
|
+
metadata_key=VispMetadataKey.grating_constant_inverse_mm,
|
|
97
|
+
ip_task_types=[TaskName.observe.value, TaskName.solar_gain.value],
|
|
98
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
99
|
+
),
|
|
100
|
+
TaskUniqueBud(
|
|
101
|
+
constant_name=VispBudName.solar_gain_ip_start_time.value,
|
|
102
|
+
metadata_key=MetadataKey.ip_start_time,
|
|
103
|
+
ip_task_types=TaskName.solar_gain,
|
|
104
|
+
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
105
|
+
),
|
|
87
106
|
TaskExposureTimesBud(
|
|
88
107
|
stem_name=VispBudName.lamp_exposure_times.value,
|
|
89
108
|
ip_task_types=TaskName.lamp_gain.value,
|
|
@@ -170,7 +170,7 @@ class ScienceCalibration(
|
|
|
170
170
|
"""
|
|
171
171
|
dark_dict = defaultdict(dict)
|
|
172
172
|
background_dict = dict()
|
|
173
|
-
solar_dict =
|
|
173
|
+
solar_dict = dict()
|
|
174
174
|
angle_dict = dict()
|
|
175
175
|
state_offset_dict = defaultdict(dict)
|
|
176
176
|
spec_shift_dict = dict()
|
|
@@ -225,6 +225,18 @@ class ScienceCalibration(
|
|
|
225
225
|
)
|
|
226
226
|
)
|
|
227
227
|
|
|
228
|
+
# Solar
|
|
229
|
+
#######
|
|
230
|
+
solar_dict[VispTag.beam(beam)] = next(
|
|
231
|
+
self.read(
|
|
232
|
+
tags=[
|
|
233
|
+
VispTag.intermediate_frame(beam=beam),
|
|
234
|
+
VispTag.task_solar_gain(),
|
|
235
|
+
],
|
|
236
|
+
decoder=fits_array_decoder,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
228
240
|
# Demod
|
|
229
241
|
#######
|
|
230
242
|
if self.constants.correct_for_polarization:
|
|
@@ -239,18 +251,6 @@ class ScienceCalibration(
|
|
|
239
251
|
)
|
|
240
252
|
|
|
241
253
|
for modstate in range(1, self.constants.num_modstates + 1):
|
|
242
|
-
# Solar
|
|
243
|
-
#######
|
|
244
|
-
solar_dict[VispTag.beam(beam)][VispTag.modstate(modstate)] = next(
|
|
245
|
-
self.read(
|
|
246
|
-
tags=[
|
|
247
|
-
VispTag.intermediate_frame(beam=beam, modstate=modstate),
|
|
248
|
-
VispTag.task_solar_gain(),
|
|
249
|
-
],
|
|
250
|
-
decoder=fits_array_decoder,
|
|
251
|
-
)
|
|
252
|
-
)
|
|
253
|
-
|
|
254
254
|
# State Offset
|
|
255
255
|
##############
|
|
256
256
|
state_offset_dict[VispTag.beam(beam)][VispTag.modstate(modstate)] = next(
|
|
@@ -586,7 +586,7 @@ class ScienceCalibration(
|
|
|
586
586
|
VispTag.readout_exp_time(readout_exp_time)
|
|
587
587
|
]
|
|
588
588
|
background_array = calibrations.background[VispTag.beam(beam)]
|
|
589
|
-
solar_gain_array = calibrations.solar_gain[VispTag.beam(beam)]
|
|
589
|
+
solar_gain_array = calibrations.solar_gain[VispTag.beam(beam)]
|
|
590
590
|
angle = calibrations.angle[VispTag.beam(beam)]
|
|
591
591
|
spec_shift = calibrations.spec_shift[VispTag.beam(beam)]
|
|
592
592
|
state_offset = calibrations.state_offset[VispTag.beam(beam)][VispTag.modstate(modstate)]
|