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,169 @@
|
|
|
1
|
+
"""Cryonirsp CI science calibration task."""
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
|
|
4
|
+
from dkist_service_configuration.logging import logger
|
|
5
|
+
|
|
6
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
7
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
8
|
+
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
|
|
9
|
+
from dkist_processing_cryonirsp.tasks.science_base import CalibrationCollection
|
|
10
|
+
from dkist_processing_cryonirsp.tasks.science_base import ScienceCalibrationBase
|
|
11
|
+
|
|
12
|
+
__all__ = ["CIScienceCalibration"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CIScienceCalibration(ScienceCalibrationBase):
|
|
16
|
+
"""Task class for Cryonirsp CI science calibration of polarized and non-polarized data."""
|
|
17
|
+
|
|
18
|
+
CI_BEAM = 1
|
|
19
|
+
|
|
20
|
+
def calibrate_and_write_frames(self, calibrations: CalibrationCollection):
|
|
21
|
+
"""
|
|
22
|
+
Completely calibrate all science frames.
|
|
23
|
+
|
|
24
|
+
- Apply dark and gain corrections
|
|
25
|
+
- Demodulate if needed
|
|
26
|
+
- Apply telescope correction, if needed
|
|
27
|
+
- Write calibrated arrays
|
|
28
|
+
"""
|
|
29
|
+
for exposure_conditions in self.constants.observe_exposure_conditions_list:
|
|
30
|
+
for map_scan in range(1, self.constants.num_map_scans + 1):
|
|
31
|
+
for scan_step in range(1, self.constants.num_scan_steps + 1):
|
|
32
|
+
for meas_num in range(1, self.constants.num_meas + 1):
|
|
33
|
+
if self.constants.correct_for_polarization:
|
|
34
|
+
calibrated_object = self.calibrate_polarimetric_frames(
|
|
35
|
+
exposure_conditions=exposure_conditions,
|
|
36
|
+
map_scan=map_scan,
|
|
37
|
+
scan_step=scan_step,
|
|
38
|
+
meas_num=meas_num,
|
|
39
|
+
calibrations=calibrations,
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
calibrated_object = self.calibrate_intensity_only_frames(
|
|
43
|
+
exposure_conditions=exposure_conditions,
|
|
44
|
+
map_scan=map_scan,
|
|
45
|
+
scan_step=scan_step,
|
|
46
|
+
meas_num=meas_num,
|
|
47
|
+
calibrations=calibrations,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
logging_str = f"{exposure_conditions = }, {map_scan = }, {scan_step = } and {meas_num = }"
|
|
51
|
+
logger.info(f"Writing calibrated arrays for {logging_str}")
|
|
52
|
+
self.write_calibrated_object(
|
|
53
|
+
calibrated_object,
|
|
54
|
+
map_scan=map_scan,
|
|
55
|
+
scan_step=scan_step,
|
|
56
|
+
meas_num=meas_num,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def calibrate_polarimetric_frames(
|
|
60
|
+
self,
|
|
61
|
+
*,
|
|
62
|
+
exposure_conditions: ExposureConditions,
|
|
63
|
+
map_scan: int,
|
|
64
|
+
scan_step: int,
|
|
65
|
+
meas_num: int,
|
|
66
|
+
calibrations: CalibrationCollection,
|
|
67
|
+
) -> CryonirspL0FitsAccess:
|
|
68
|
+
"""
|
|
69
|
+
Completely calibrate polarimetric science frames.
|
|
70
|
+
|
|
71
|
+
- Apply dark and gain corrections
|
|
72
|
+
- Demodulate
|
|
73
|
+
- Apply telescope correction
|
|
74
|
+
"""
|
|
75
|
+
logging_str = f"{exposure_conditions = }, {map_scan = }, {scan_step = } and {meas_num = }"
|
|
76
|
+
logger.info(f"Processing polarimetric observe frames from {logging_str}")
|
|
77
|
+
|
|
78
|
+
intermediate_array, intermediate_header = self.correct_and_demodulate(
|
|
79
|
+
beam=self.CI_BEAM,
|
|
80
|
+
meas_num=meas_num,
|
|
81
|
+
scan_step=scan_step,
|
|
82
|
+
map_scan=map_scan,
|
|
83
|
+
exposure_conditions=exposure_conditions,
|
|
84
|
+
calibrations=calibrations,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
intermediate_object = self.wrap_array_and_header_in_fits_access(
|
|
88
|
+
intermediate_array, intermediate_header
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
logger.info(f"Correcting telescope polarization for {logging_str}")
|
|
92
|
+
calibrated_object = self.telescope_polarization_correction(intermediate_object)
|
|
93
|
+
|
|
94
|
+
return calibrated_object
|
|
95
|
+
|
|
96
|
+
def calibrate_intensity_only_frames(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
exposure_conditions: ExposureConditions,
|
|
100
|
+
map_scan: int,
|
|
101
|
+
scan_step: int,
|
|
102
|
+
meas_num: int,
|
|
103
|
+
calibrations: CalibrationCollection,
|
|
104
|
+
) -> CryonirspL0FitsAccess:
|
|
105
|
+
"""
|
|
106
|
+
Completely calibrate non-polarimetric science frames.
|
|
107
|
+
|
|
108
|
+
- Apply dark and gain corrections
|
|
109
|
+
"""
|
|
110
|
+
logging_str = f"{exposure_conditions = }, {map_scan = }, {scan_step = } and {meas_num = }"
|
|
111
|
+
logger.info(f"Processing Stokes-I observe frames from {logging_str}")
|
|
112
|
+
intermediate_array, intermediate_header = self.apply_basic_corrections(
|
|
113
|
+
beam=self.CI_BEAM,
|
|
114
|
+
modstate=1,
|
|
115
|
+
meas_num=meas_num,
|
|
116
|
+
scan_step=scan_step,
|
|
117
|
+
map_scan=map_scan,
|
|
118
|
+
exposure_conditions=exposure_conditions,
|
|
119
|
+
calibrations=calibrations,
|
|
120
|
+
)
|
|
121
|
+
intermediate_header = self.compute_date_keys(intermediate_header)
|
|
122
|
+
|
|
123
|
+
intermediate_array = self.add_stokes_dimension_to_intensity_only_array(intermediate_array)
|
|
124
|
+
|
|
125
|
+
calibrated_object = self.wrap_array_and_header_in_fits_access(
|
|
126
|
+
intermediate_array, intermediate_header
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return calibrated_object
|
|
130
|
+
|
|
131
|
+
def collect_calibration_objects(self) -> CalibrationCollection:
|
|
132
|
+
"""
|
|
133
|
+
Collect *all* calibration for all modstates, and exposure times.
|
|
134
|
+
|
|
135
|
+
Doing this once here prevents lots of reads as we reduce the science data.
|
|
136
|
+
"""
|
|
137
|
+
dark_dict = defaultdict(dict)
|
|
138
|
+
solar_dict = dict()
|
|
139
|
+
demod_dict = dict() if self.constants.correct_for_polarization else None
|
|
140
|
+
|
|
141
|
+
beam = self.CI_BEAM
|
|
142
|
+
# Load the dark arrays
|
|
143
|
+
for exposure_conditions in self.constants.observe_exposure_conditions_list:
|
|
144
|
+
dark_array = self.intermediate_frame_load_dark_array(
|
|
145
|
+
beam=beam, exposure_conditions=exposure_conditions
|
|
146
|
+
)
|
|
147
|
+
dark_dict[CryonirspTag.beam(beam)][
|
|
148
|
+
CryonirspTag.exposure_conditions(exposure_conditions)
|
|
149
|
+
] = dark_array
|
|
150
|
+
|
|
151
|
+
# Load the gain arrays
|
|
152
|
+
solar_dict[CryonirspTag.beam(beam)] = self.intermediate_frame_load_solar_gain_array(
|
|
153
|
+
beam=beam,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Load the demod matrices
|
|
157
|
+
if self.constants.correct_for_polarization:
|
|
158
|
+
demod_dict[CryonirspTag.beam(beam)] = self.intermediate_frame_load_demod_matrices(
|
|
159
|
+
beam=beam
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return CalibrationCollection(
|
|
163
|
+
dark=dark_dict,
|
|
164
|
+
angle=None,
|
|
165
|
+
spec_shift=None,
|
|
166
|
+
state_offset=None,
|
|
167
|
+
solar_gain=solar_dict,
|
|
168
|
+
demod_matrices=demod_dict,
|
|
169
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""CryoNIRSP base class."""
|
|
2
|
+
from abc import ABC
|
|
3
|
+
|
|
4
|
+
from dkist_processing_common.tasks import WorkflowTaskBase
|
|
5
|
+
from dkist_processing_common.tasks.mixin.input_dataset import InputDatasetMixin
|
|
6
|
+
from dkist_processing_common.tasks.mixin.quality import QualityMixin
|
|
7
|
+
|
|
8
|
+
from dkist_processing_cryonirsp.models.constants import CryonirspConstants
|
|
9
|
+
from dkist_processing_cryonirsp.models.parameters import CryonirspParameters
|
|
10
|
+
from dkist_processing_cryonirsp.tasks.mixin.beam_access import BeamAccessMixin
|
|
11
|
+
from dkist_processing_cryonirsp.tasks.mixin.corrections import CorrectionsMixin
|
|
12
|
+
from dkist_processing_cryonirsp.tasks.mixin.intermediate_frame import (
|
|
13
|
+
IntermediateFrameMixin,
|
|
14
|
+
)
|
|
15
|
+
from dkist_processing_cryonirsp.tasks.mixin.linearized_frame import (
|
|
16
|
+
LinearizedFrameMixin,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CryonirspTaskBase(
|
|
21
|
+
WorkflowTaskBase,
|
|
22
|
+
InputDatasetMixin,
|
|
23
|
+
BeamAccessMixin,
|
|
24
|
+
LinearizedFrameMixin,
|
|
25
|
+
IntermediateFrameMixin,
|
|
26
|
+
CorrectionsMixin,
|
|
27
|
+
QualityMixin,
|
|
28
|
+
ABC,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Task class for base CryoNIRSP tasks that occur after linearization.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
recipe_run_id : int
|
|
36
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
37
|
+
workflow_name : str
|
|
38
|
+
name of the workflow to which this instance of the task belongs
|
|
39
|
+
workflow_version : str
|
|
40
|
+
version of the workflow to which this instance of the task belongs
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# So tab completion shows all the Cryonirsp constants
|
|
44
|
+
constants: CryonirspConstants
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def constants_model_class(self):
|
|
48
|
+
"""Get CryoNIRSP pipeline constants."""
|
|
49
|
+
return CryonirspConstants
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
recipe_run_id: int,
|
|
54
|
+
workflow_name: str,
|
|
55
|
+
workflow_version: str,
|
|
56
|
+
):
|
|
57
|
+
super().__init__(
|
|
58
|
+
recipe_run_id=recipe_run_id,
|
|
59
|
+
workflow_name=workflow_name,
|
|
60
|
+
workflow_version=workflow_version,
|
|
61
|
+
)
|
|
62
|
+
self.parameters = CryonirspParameters(
|
|
63
|
+
self.input_dataset_parameters,
|
|
64
|
+
obs_ip_start_time=self.constants.obs_ip_start_time,
|
|
65
|
+
wavelength=self.constants.wavelength,
|
|
66
|
+
arm_id=self.constants.arm_id,
|
|
67
|
+
)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""CryoNIRSP dark calibration task."""
|
|
2
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
3
|
+
from dkist_processing_math.statistics import average_numpy_arrays
|
|
4
|
+
from dkist_service_configuration.logging import logger
|
|
5
|
+
|
|
6
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspTag
|
|
7
|
+
from dkist_processing_cryonirsp.tasks.cryonirsp_base import CryonirspTaskBase
|
|
8
|
+
|
|
9
|
+
__all__ = ["DarkCalibration"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DarkCalibration(CryonirspTaskBase):
|
|
13
|
+
"""Task class for calculation of the averaged dark frame for a CryoNIRSP calibration run.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
recipe_run_id : int
|
|
18
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
19
|
+
workflow_name : str
|
|
20
|
+
name of the workflow to which this instance of the task belongs
|
|
21
|
+
workflow_version : str
|
|
22
|
+
version of the workflow to which this instance of the task belongs
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
record_provenance = True
|
|
27
|
+
|
|
28
|
+
def run(self):
|
|
29
|
+
"""
|
|
30
|
+
Run method for the task.
|
|
31
|
+
|
|
32
|
+
- Gather input dark frames
|
|
33
|
+
- Calculate average dark
|
|
34
|
+
- Write average dark
|
|
35
|
+
- Record quality metrics
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
None
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
target_exposure_conditions = (
|
|
43
|
+
self.constants.non_dark_and_non_polcal_task_exposure_conditions_list
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
logger.info(f"{target_exposure_conditions = }")
|
|
47
|
+
with self.apm_task_step(
|
|
48
|
+
f"Calculating dark frames for {self.constants.num_beams} beams and {len(target_exposure_conditions)} exp times"
|
|
49
|
+
):
|
|
50
|
+
total_dark_frames_used = 0
|
|
51
|
+
for exposure_conditions in target_exposure_conditions:
|
|
52
|
+
for beam in range(1, self.constants.num_beams + 1):
|
|
53
|
+
logger.info(
|
|
54
|
+
f"Gathering input dark frames for {exposure_conditions = } and {beam = }"
|
|
55
|
+
)
|
|
56
|
+
dark_tags = [
|
|
57
|
+
CryonirspTag.linearized(),
|
|
58
|
+
CryonirspTag.frame(),
|
|
59
|
+
CryonirspTag.task_dark(),
|
|
60
|
+
CryonirspTag.exposure_conditions(exposure_conditions),
|
|
61
|
+
]
|
|
62
|
+
current_exp_dark_count = self.scratch.count_all(tags=dark_tags)
|
|
63
|
+
total_dark_frames_used += current_exp_dark_count
|
|
64
|
+
linearized_dark_arrays = self.linearized_frame_dark_array_generator(
|
|
65
|
+
exposure_conditions=exposure_conditions, beam=beam
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
with self.apm_processing_step(
|
|
69
|
+
f"Calculating dark for {exposure_conditions = } and {beam = }"
|
|
70
|
+
):
|
|
71
|
+
averaged_dark_array = average_numpy_arrays(linearized_dark_arrays)
|
|
72
|
+
|
|
73
|
+
with self.apm_writing_step(
|
|
74
|
+
f"Writing dark for {exposure_conditions = } {beam = }"
|
|
75
|
+
):
|
|
76
|
+
self.intermediate_frame_write_arrays(
|
|
77
|
+
averaged_dark_array,
|
|
78
|
+
beam=beam,
|
|
79
|
+
task_tag=CryonirspTag.task_dark(),
|
|
80
|
+
exposure_conditions=exposure_conditions,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
with self.apm_processing_step("Computing and logging quality metrics"):
|
|
84
|
+
no_of_raw_dark_frames: int = self.scratch.count_all(
|
|
85
|
+
tags=[
|
|
86
|
+
CryonirspTag.linearized(),
|
|
87
|
+
CryonirspTag.frame(),
|
|
88
|
+
CryonirspTag.task_dark(),
|
|
89
|
+
],
|
|
90
|
+
)
|
|
91
|
+
unused_count = int(
|
|
92
|
+
no_of_raw_dark_frames - (total_dark_frames_used // self.constants.num_beams)
|
|
93
|
+
)
|
|
94
|
+
self.quality_store_task_type_counts(
|
|
95
|
+
task_type=TaskName.dark.value,
|
|
96
|
+
total_frames=no_of_raw_dark_frames,
|
|
97
|
+
frames_not_used=unused_count,
|
|
98
|
+
)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Cryo gain task."""
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
7
|
+
from dkist_processing_math.arithmetic import subtract_array_from_arrays
|
|
8
|
+
from dkist_processing_math.statistics import average_numpy_arrays
|
|
9
|
+
from dkist_service_configuration.logging import logger
|
|
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.tasks.cryonirsp_base import CryonirspTaskBase
|
|
14
|
+
|
|
15
|
+
__all__ = ["LampGainCalibration", "CISolarGainCalibration"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GainCalibrationBase(CryonirspTaskBase):
|
|
19
|
+
"""
|
|
20
|
+
Base task class for calculation of average lamp or solar gains for CI and average lamp gains for SP.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
recipe_run_id : int
|
|
25
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
26
|
+
workflow_name : str
|
|
27
|
+
name of the workflow to which this instance of the task belongs
|
|
28
|
+
workflow_version : str
|
|
29
|
+
version of the workflow to which this instance of the task belongs
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def gain_type(self) -> str:
|
|
36
|
+
"""Return the gain type, SOLAR_GAIN or LAMP_GAIN."""
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def exposure_conditions(self) -> [ExposureConditions]:
|
|
42
|
+
"""Return the exposure conditions list."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def gain_array_generator(self) -> Callable:
|
|
48
|
+
"""Return the gain array generator to use based on the gain type."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def normalize_gain_switch(self) -> bool:
|
|
54
|
+
"""If True then the final gain image is normalized to have a mean of 1."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
record_provenance = True
|
|
58
|
+
|
|
59
|
+
def run(self):
|
|
60
|
+
"""
|
|
61
|
+
Execute the task.
|
|
62
|
+
|
|
63
|
+
For each exposure time and beam:
|
|
64
|
+
- Gather input gain and averaged dark arrays
|
|
65
|
+
- Calculate average array
|
|
66
|
+
- Normalize average array
|
|
67
|
+
- Write average gain array
|
|
68
|
+
- Record quality metrics
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
None
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
target_exposure_conditions = self.exposure_conditions
|
|
76
|
+
|
|
77
|
+
logger.info(f"{target_exposure_conditions = }")
|
|
78
|
+
with self.apm_task_step(
|
|
79
|
+
f"Generate {self.gain_type} for {len(target_exposure_conditions)} exposure times"
|
|
80
|
+
):
|
|
81
|
+
for exposure_conditions in target_exposure_conditions:
|
|
82
|
+
# NB: By using num_beams = 1 for CI, this method works for both CI and SP
|
|
83
|
+
for beam in range(1, self.constants.num_beams + 1):
|
|
84
|
+
apm_str = f"{beam = } and {exposure_conditions = }"
|
|
85
|
+
with self.apm_processing_step(f"Remove dark signal for {apm_str}"):
|
|
86
|
+
dark_array = self.intermediate_frame_load_dark_array(
|
|
87
|
+
beam=beam, exposure_conditions=exposure_conditions
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
avg_gain_array = self.compute_average_gain_array(
|
|
91
|
+
beam=beam, exposure_conditions=exposure_conditions
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
dark_corrected_gain_array = next(
|
|
95
|
+
subtract_array_from_arrays(avg_gain_array, dark_array)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
with self.apm_processing_step(f"Correct bad pixels for {apm_str}"):
|
|
99
|
+
bad_pixel_map = self.intermediate_frame_load_bad_pixel_map(beam=beam)
|
|
100
|
+
bad_pixel_corrected_array = self.corrections_correct_bad_pixels(
|
|
101
|
+
dark_corrected_gain_array, bad_pixel_map
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if self.normalize_gain_switch:
|
|
105
|
+
with self.apm_processing_step(f"Normalize final gain for {apm_str}"):
|
|
106
|
+
normalized_gain_array = self.normalize_gain(bad_pixel_corrected_array)
|
|
107
|
+
else:
|
|
108
|
+
normalized_gain_array = bad_pixel_corrected_array
|
|
109
|
+
|
|
110
|
+
with self.apm_writing_step(
|
|
111
|
+
f"Writing gain array for {beam = } and {exposure_conditions = }"
|
|
112
|
+
):
|
|
113
|
+
self.intermediate_frame_write_arrays(
|
|
114
|
+
normalized_gain_array,
|
|
115
|
+
beam=beam,
|
|
116
|
+
task=self.gain_type,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
with self.apm_processing_step("Computing and logging quality metrics"):
|
|
120
|
+
no_of_raw_gain_frames: int = self.scratch.count_all(
|
|
121
|
+
tags=[
|
|
122
|
+
CryonirspTag.linearized(),
|
|
123
|
+
CryonirspTag.frame(),
|
|
124
|
+
CryonirspTag.task(self.gain_type),
|
|
125
|
+
],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
self.quality_store_task_type_counts(
|
|
129
|
+
task_type=self.gain_type, total_frames=no_of_raw_gain_frames
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def compute_average_gain_array(
|
|
133
|
+
self,
|
|
134
|
+
beam: int,
|
|
135
|
+
exposure_conditions: ExposureConditions,
|
|
136
|
+
) -> np.ndarray:
|
|
137
|
+
"""
|
|
138
|
+
Compute average gain array for a given exposure conditions and beam.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
beam : int
|
|
143
|
+
The number of the beam
|
|
144
|
+
|
|
145
|
+
exposure_conditions : float
|
|
146
|
+
Exposure time
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
np.ndarray
|
|
151
|
+
"""
|
|
152
|
+
linearized_gain_arrays = self.gain_array_generator(
|
|
153
|
+
beam=beam, exposure_conditions=exposure_conditions
|
|
154
|
+
)
|
|
155
|
+
averaged_gain_data = average_numpy_arrays(linearized_gain_arrays)
|
|
156
|
+
return averaged_gain_data
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def normalize_gain(gain_array: np.ndarray) -> np.ndarray:
|
|
160
|
+
"""
|
|
161
|
+
Normalize gain to a mean of 1.
|
|
162
|
+
|
|
163
|
+
Find any residual pixels that are zero valued and set them to 1.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
gain_array : np.ndarray
|
|
168
|
+
Dark corrected gain array
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
np.ndarray
|
|
173
|
+
Normalized dark-corrected gain array
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
avg = np.nanmean(gain_array)
|
|
177
|
+
gain_array /= avg
|
|
178
|
+
|
|
179
|
+
return gain_array
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class LampGainCalibration(GainCalibrationBase):
|
|
183
|
+
"""
|
|
184
|
+
Task class for calculation of an average lamp gain frame for a CryoNIRSP CI or SP calibration run.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
recipe_run_id : int
|
|
189
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
190
|
+
workflow_name : str
|
|
191
|
+
name of the workflow to which this instance of the task belongs
|
|
192
|
+
workflow_version : str
|
|
193
|
+
version of the workflow to which this instance of the task belongs
|
|
194
|
+
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def gain_type(self) -> str:
|
|
199
|
+
"""Return the gain type, SOLAR_GAIN or LAMP_GAIN."""
|
|
200
|
+
return TaskName.lamp_gain.value
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def exposure_conditions(self) -> [ExposureConditions]:
|
|
204
|
+
"""Return the exposure conditions list."""
|
|
205
|
+
return self.constants.lamp_gain_exposure_conditions_list
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def gain_array_generator(self) -> Callable:
|
|
209
|
+
"""Return the gain array generator to use based on the gain type."""
|
|
210
|
+
return self.linearized_frame_lamp_gain_array_generator
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def normalize_gain_switch(self) -> True:
|
|
214
|
+
"""Lamp gains should be normalized."""
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class CISolarGainCalibration(GainCalibrationBase):
|
|
219
|
+
"""
|
|
220
|
+
Task class for calculation of an average solar gain frame for a CryoNIRSP CI calibration run.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
recipe_run_id : int
|
|
225
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
226
|
+
workflow_name : str
|
|
227
|
+
name of the workflow to which this instance of the task belongs
|
|
228
|
+
workflow_version : str
|
|
229
|
+
version of the workflow to which this instance of the task belongs
|
|
230
|
+
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def gain_type(self) -> str:
|
|
235
|
+
"""Return the gain type, SOLAR_GAIN or LAMP_GAIN."""
|
|
236
|
+
return TaskName.solar_gain.value
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def exposure_conditions(self) -> [ExposureConditions]:
|
|
240
|
+
"""Return the exposure conditions list."""
|
|
241
|
+
return self.constants.solar_gain_exposure_conditions_list
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def gain_array_generator(self) -> Callable:
|
|
245
|
+
"""Return the gain array generator to use based on the gain type."""
|
|
246
|
+
return self.linearized_frame_solar_gain_array_generator
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def normalize_gain_switch(self) -> False:
|
|
250
|
+
"""We don't want to normalize and Solar Gain images."""
|
|
251
|
+
return False
|