dkist-processing-cryonirsp 1.4.20__py3-none-any.whl → 1.14.9rc1__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.
- changelog/232.misc.rst +1 -0
- dkist_processing_cryonirsp/__init__.py +1 -0
- dkist_processing_cryonirsp/codecs/fits.py +1 -0
- dkist_processing_cryonirsp/config.py +5 -1
- dkist_processing_cryonirsp/models/beam_boundaries.py +1 -0
- dkist_processing_cryonirsp/models/constants.py +31 -30
- dkist_processing_cryonirsp/models/exposure_conditions.py +6 -5
- dkist_processing_cryonirsp/models/fits_access.py +40 -0
- dkist_processing_cryonirsp/models/parameters.py +14 -26
- dkist_processing_cryonirsp/models/tags.py +1 -0
- dkist_processing_cryonirsp/models/task_name.py +1 -0
- dkist_processing_cryonirsp/parsers/check_for_gains.py +1 -0
- dkist_processing_cryonirsp/parsers/cryonirsp_l0_fits_access.py +40 -47
- dkist_processing_cryonirsp/parsers/cryonirsp_l1_fits_access.py +1 -0
- dkist_processing_cryonirsp/parsers/exposure_conditions.py +14 -13
- dkist_processing_cryonirsp/parsers/map_repeats.py +1 -0
- dkist_processing_cryonirsp/parsers/measurements.py +29 -16
- dkist_processing_cryonirsp/parsers/modstates.py +5 -1
- dkist_processing_cryonirsp/parsers/optical_density_filters.py +1 -0
- dkist_processing_cryonirsp/parsers/polarimetric_check.py +18 -7
- dkist_processing_cryonirsp/parsers/scan_step.py +12 -4
- dkist_processing_cryonirsp/parsers/time.py +7 -7
- dkist_processing_cryonirsp/parsers/wavelength.py +6 -1
- dkist_processing_cryonirsp/tasks/__init__.py +2 -1
- dkist_processing_cryonirsp/tasks/assemble_movie.py +1 -0
- dkist_processing_cryonirsp/tasks/bad_pixel_map.py +6 -5
- dkist_processing_cryonirsp/tasks/beam_boundaries_base.py +12 -11
- dkist_processing_cryonirsp/tasks/ci_beam_boundaries.py +1 -0
- dkist_processing_cryonirsp/tasks/ci_science.py +1 -0
- dkist_processing_cryonirsp/tasks/cryonirsp_base.py +2 -3
- dkist_processing_cryonirsp/tasks/dark.py +5 -4
- dkist_processing_cryonirsp/tasks/gain.py +7 -6
- dkist_processing_cryonirsp/tasks/instrument_polarization.py +17 -16
- dkist_processing_cryonirsp/tasks/l1_output_data.py +1 -0
- dkist_processing_cryonirsp/tasks/linearity_correction.py +1 -0
- dkist_processing_cryonirsp/tasks/make_movie_frames.py +3 -2
- dkist_processing_cryonirsp/tasks/mixin/corrections.py +1 -0
- dkist_processing_cryonirsp/tasks/mixin/shift_measurements.py +9 -2
- dkist_processing_cryonirsp/tasks/parse.py +70 -52
- dkist_processing_cryonirsp/tasks/quality_metrics.py +15 -14
- dkist_processing_cryonirsp/tasks/science_base.py +8 -6
- dkist_processing_cryonirsp/tasks/sp_beam_boundaries.py +2 -1
- dkist_processing_cryonirsp/tasks/sp_geometric.py +11 -10
- dkist_processing_cryonirsp/tasks/sp_science.py +1 -0
- dkist_processing_cryonirsp/tasks/sp_solar_gain.py +15 -12
- dkist_processing_cryonirsp/tasks/sp_wavelength_calibration.py +300 -0
- dkist_processing_cryonirsp/tasks/write_l1.py +59 -38
- dkist_processing_cryonirsp/tests/conftest.py +75 -53
- dkist_processing_cryonirsp/tests/header_models.py +62 -11
- dkist_processing_cryonirsp/tests/local_trial_workflows/l0_cals_only.py +26 -46
- dkist_processing_cryonirsp/tests/local_trial_workflows/l0_to_l1.py +26 -47
- dkist_processing_cryonirsp/tests/local_trial_workflows/linearize_only.py +3 -3
- dkist_processing_cryonirsp/tests/local_trial_workflows/local_trial_helpers.py +57 -26
- dkist_processing_cryonirsp/tests/test_assemble_movie.py +4 -5
- dkist_processing_cryonirsp/tests/test_assemble_qualilty.py +5 -1
- dkist_processing_cryonirsp/tests/test_bad_pixel_maps.py +4 -5
- dkist_processing_cryonirsp/tests/test_ci_beam_boundaries.py +4 -5
- dkist_processing_cryonirsp/tests/test_ci_science.py +4 -5
- dkist_processing_cryonirsp/tests/test_corrections.py +5 -6
- dkist_processing_cryonirsp/tests/test_cryo_base.py +4 -6
- dkist_processing_cryonirsp/tests/test_cryo_constants.py +7 -3
- dkist_processing_cryonirsp/tests/test_dark.py +7 -8
- dkist_processing_cryonirsp/tests/test_fits_access.py +44 -0
- dkist_processing_cryonirsp/tests/test_gain.py +7 -8
- dkist_processing_cryonirsp/tests/test_instrument_polarization.py +19 -10
- dkist_processing_cryonirsp/tests/test_linearity_correction.py +5 -4
- dkist_processing_cryonirsp/tests/test_make_movie_frames.py +2 -3
- dkist_processing_cryonirsp/tests/test_parameters.py +23 -28
- dkist_processing_cryonirsp/tests/test_parse.py +48 -12
- dkist_processing_cryonirsp/tests/test_quality.py +2 -3
- dkist_processing_cryonirsp/tests/test_sp_beam_boundaries.py +5 -5
- dkist_processing_cryonirsp/tests/test_sp_geometric.py +5 -6
- dkist_processing_cryonirsp/tests/test_sp_make_movie_frames.py +2 -3
- dkist_processing_cryonirsp/tests/test_sp_science.py +4 -5
- dkist_processing_cryonirsp/tests/test_sp_solar.py +6 -5
- dkist_processing_cryonirsp/tests/{test_sp_dispersion_axis_correction.py → test_sp_wavelength_calibration.py} +11 -29
- dkist_processing_cryonirsp/tests/test_trial_create_quality_report.py +1 -1
- dkist_processing_cryonirsp/tests/test_workflows.py +1 -0
- dkist_processing_cryonirsp/tests/test_write_l1.py +29 -31
- dkist_processing_cryonirsp/workflows/__init__.py +1 -0
- dkist_processing_cryonirsp/workflows/ci_l0_processing.py +9 -5
- dkist_processing_cryonirsp/workflows/sp_l0_processing.py +12 -8
- dkist_processing_cryonirsp/workflows/trial_workflows.py +12 -11
- dkist_processing_cryonirsp-1.14.9rc1.dist-info/METADATA +552 -0
- dkist_processing_cryonirsp-1.14.9rc1.dist-info/RECORD +115 -0
- {dkist_processing_cryonirsp-1.4.20.dist-info → dkist_processing_cryonirsp-1.14.9rc1.dist-info}/WHEEL +1 -1
- docs/ci_science_calibration.rst +10 -0
- docs/conf.py +1 -0
- docs/index.rst +1 -0
- docs/sp_science_calibration.rst +7 -0
- docs/wavelength_calibration.rst +62 -0
- dkist_processing_cryonirsp/tasks/sp_dispersion_axis_correction.py +0 -492
- dkist_processing_cryonirsp-1.4.20.dist-info/METADATA +0 -452
- dkist_processing_cryonirsp-1.4.20.dist-info/RECORD +0 -111
- {dkist_processing_cryonirsp-1.4.20.dist-info → dkist_processing_cryonirsp-1.14.9rc1.dist-info}/top_level.txt +0 -0
changelog/232.misc.rst
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Update dkist-processing-common to add a new constant for the dark number of frames per FPA.
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"""Configuration for the dkist-processing-cryonirsp package and the logging thereof."""
|
|
2
|
+
|
|
2
3
|
from dkist_processing_common.config import DKISTProcessingCommonConfiguration
|
|
4
|
+
from pydantic import Field
|
|
3
5
|
|
|
4
6
|
|
|
5
7
|
class DKISTProcessingCryoNIRSPConfigurations(DKISTProcessingCommonConfiguration):
|
|
6
8
|
"""Configurations custom to the dkist-processing-cryonirsp package."""
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
fts_atlas_data_dir: str | None = Field(
|
|
11
|
+
default=None, description="Common cached directory for a downloaded FTS Atlas."
|
|
12
|
+
)
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
dkist_processing_cryonirsp_configurations = DKISTProcessingCryoNIRSPConfigurations()
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"""CryoNIRSP additions to common constants."""
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
3
4
|
from enum import unique
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
import astropy.units as u
|
|
6
7
|
from dkist_processing_common.models.constants import ConstantsBase
|
|
7
8
|
|
|
8
9
|
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@unique
|
|
12
|
-
class CryonirspBudName(
|
|
13
|
+
class CryonirspBudName(StrEnum):
|
|
13
14
|
"""Names to be used for CryoNIRSP buds."""
|
|
14
15
|
|
|
15
16
|
arm_id = "ARM_ID"
|
|
@@ -18,8 +19,6 @@ class CryonirspBudName(Enum):
|
|
|
18
19
|
num_map_scans = "NUM_MAP_SCANS"
|
|
19
20
|
num_modstates = "NUM_MODSTATES"
|
|
20
21
|
wavelength = "WAVELENGTH"
|
|
21
|
-
wave_min = "WAVE_MIN"
|
|
22
|
-
wave_max = "WAVE_MAX"
|
|
23
22
|
grating_position_deg = "GRATING_POSITION_DEG"
|
|
24
23
|
grating_littrow_angle_deg = "GRATING_LITTROW_ANGLE_DEG"
|
|
25
24
|
grating_constant = "GRATING_CONSTANT"
|
|
@@ -50,10 +49,12 @@ class CryonirspBudName(Enum):
|
|
|
50
49
|
roi_1_size_x = "ROI_1_SIZE_X"
|
|
51
50
|
roi_1_size_y = "ROI_1_SIZE_Y"
|
|
52
51
|
optical_density_filter_picky_bud = "OPTICAL_DENSITY_FILTER_PICKY_BUD"
|
|
53
|
-
|
|
52
|
+
solar_gain_start_time = "SOLAR_GAIN_START_TIME"
|
|
54
53
|
gain_frame_type_list = "GAIN_FRAME_TYPE_LIST"
|
|
55
54
|
lamp_gain_frame_type_list = "LAMP_GAIN_FRAME_TYPE_LIST"
|
|
56
55
|
solar_gain_frame_type_list = "SOLAR_GAIN_FRAME_TYPE_LIST"
|
|
56
|
+
center_wavelength = "CENTER_WAVELENGTH"
|
|
57
|
+
slit_width = "SLIT_WIDTH"
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
class CryonirspConstants(ConstantsBase):
|
|
@@ -88,19 +89,9 @@ class CryonirspConstants(ConstantsBase):
|
|
|
88
89
|
return self._db_dict[CryonirspBudName.wavelength.value]
|
|
89
90
|
|
|
90
91
|
@property
|
|
91
|
-
def
|
|
92
|
-
"""
|
|
93
|
-
return self._db_dict[CryonirspBudName.
|
|
94
|
-
|
|
95
|
-
@property
|
|
96
|
-
def wave_max(self) -> float:
|
|
97
|
-
"""Wavelength maximum."""
|
|
98
|
-
return self._db_dict[CryonirspBudName.wave_max.value]
|
|
99
|
-
|
|
100
|
-
@property
|
|
101
|
-
def solar_gain_ip_start_time(self) -> str:
|
|
102
|
-
"""Solar gain IP start time."""
|
|
103
|
-
return self._db_dict[CryonirspBudName.solar_gain_ip_start_time.value]
|
|
92
|
+
def solar_gain_start_time(self) -> str:
|
|
93
|
+
"""Solar gain start time."""
|
|
94
|
+
return self._db_dict[CryonirspBudName.solar_gain_start_time.value]
|
|
104
95
|
|
|
105
96
|
@property
|
|
106
97
|
def grating_position_deg(self) -> float:
|
|
@@ -112,10 +103,20 @@ class CryonirspConstants(ConstantsBase):
|
|
|
112
103
|
"""Grating littrow angle (deg)."""
|
|
113
104
|
return self._db_dict[CryonirspBudName.grating_littrow_angle_deg.value]
|
|
114
105
|
|
|
106
|
+
@property
|
|
107
|
+
def center_wavelength(self) -> float:
|
|
108
|
+
"""Center wavelength of the selected filter (nm)."""
|
|
109
|
+
return self._db_dict[CryonirspBudName.center_wavelength.value]
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def slit_width(self) -> float:
|
|
113
|
+
"""Physical width of the selected slit (um)."""
|
|
114
|
+
return self._db_dict[CryonirspBudName.slit_width.value]
|
|
115
|
+
|
|
115
116
|
@property
|
|
116
117
|
def grating_constant(self) -> float:
|
|
117
118
|
"""Grating constant."""
|
|
118
|
-
return self._db_dict[CryonirspBudName.grating_constant.value]
|
|
119
|
+
return self._db_dict[CryonirspBudName.grating_constant.value] / u.mm
|
|
119
120
|
|
|
120
121
|
@property
|
|
121
122
|
def camera_readout_mode(self) -> str:
|
|
@@ -214,16 +215,6 @@ class CryonirspConstants(ConstantsBase):
|
|
|
214
215
|
conditions = [ExposureConditions(*item) for item in raw_conditions]
|
|
215
216
|
return conditions
|
|
216
217
|
|
|
217
|
-
@property
|
|
218
|
-
def num_modstates(self) -> int:
|
|
219
|
-
"""Find the number of modulation states."""
|
|
220
|
-
return self._db_dict[CryonirspBudName.num_modstates.value]
|
|
221
|
-
|
|
222
|
-
@property
|
|
223
|
-
def num_cs_steps(self) -> int:
|
|
224
|
-
"""Find the number of calibration sequence steps."""
|
|
225
|
-
return self._db_dict[BudName.num_cs_steps.value]
|
|
226
|
-
|
|
227
218
|
@property
|
|
228
219
|
def stokes_I_list(self) -> [str]:
|
|
229
220
|
"""List containing only the Stokes-I parameter."""
|
|
@@ -236,6 +227,16 @@ class CryonirspConstants(ConstantsBase):
|
|
|
236
227
|
CryonirspBudName.modulator_spin_mode.value
|
|
237
228
|
] in ["Continuous", "Stepped"]
|
|
238
229
|
|
|
230
|
+
@property
|
|
231
|
+
def pac_init_set(self):
|
|
232
|
+
"""Return the label for the initial set of parameter values used when fitting demodulation matrices."""
|
|
233
|
+
retarder_name = self.retarder_name
|
|
234
|
+
match retarder_name:
|
|
235
|
+
case "SiO2 OC":
|
|
236
|
+
return "OCCal_VIS"
|
|
237
|
+
case _:
|
|
238
|
+
raise ValueError(f"No init set known for {retarder_name = }")
|
|
239
|
+
|
|
239
240
|
@property
|
|
240
241
|
def axis_1_type(self) -> str:
|
|
241
242
|
"""Find the type of the first array axis."""
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"""Support classes for exposure conditions."""
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from enum import StrEnum
|
|
4
|
+
from typing import NamedTuple
|
|
4
5
|
|
|
5
6
|
# Number of digits used to round the exposure when creating the ExposureConditions tuple in fits_access
|
|
6
7
|
CRYO_EXP_TIME_ROUND_DIGITS: int = 3
|
|
7
8
|
|
|
8
|
-
"""Base class to hold a tuple of exposure time and filter name."""
|
|
9
|
-
ExposureConditionsBase = namedtuple("ExposureConditions", ["exposure_time", "filter_name"])
|
|
10
9
|
|
|
10
|
+
class ExposureConditions(NamedTuple):
|
|
11
|
+
"""Named tuple to hold exposure time and filter name."""
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
exposure_time: float
|
|
14
|
+
filter_name: str
|
|
14
15
|
|
|
15
16
|
def __str__(self):
|
|
16
17
|
return f"{self.exposure_time}_{self.filter_name}"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""CryoNIRSP control of FITS key names and values."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
from enum import unique
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@unique
|
|
8
|
+
class CryonirspMetadataKey(StrEnum):
|
|
9
|
+
"""Controlled list of names for FITS metadata header keys."""
|
|
10
|
+
|
|
11
|
+
camera_readout_mode = "CNCAMMD"
|
|
12
|
+
curr_frame_in_ramp = "CNCNDR"
|
|
13
|
+
num_frames_in_ramp = "CNNNDR"
|
|
14
|
+
arm_id = "CNARMID"
|
|
15
|
+
roi_1_origin_x = "HWROI1OX"
|
|
16
|
+
roi_1_origin_y = "HWROI1OY"
|
|
17
|
+
roi_1_size_x = "HWROI1SX"
|
|
18
|
+
roi_1_size_y = "HWROI1SY"
|
|
19
|
+
filter_name = "CNFILTNP"
|
|
20
|
+
number_of_modulator_states = "CNMODNST"
|
|
21
|
+
modulator_state = "CNMODCST"
|
|
22
|
+
scan_step = "CNCURSCN"
|
|
23
|
+
num_scan_steps = "CNNUMSCN"
|
|
24
|
+
num_cn1_scan_steps = "CNP1DNSP"
|
|
25
|
+
num_cn2_scan_steps = "CNP2DNSP"
|
|
26
|
+
cn2_step_size = "CNP2DSS"
|
|
27
|
+
cn1_scan_step = "CNP1DCUR"
|
|
28
|
+
meas_num = "CNCMEAS"
|
|
29
|
+
num_meas = "CNNMEAS"
|
|
30
|
+
sub_repeat_num = "CNCSREP"
|
|
31
|
+
num_sub_repeats = "CNSUBREP"
|
|
32
|
+
modulator_spin_mode = "CNSPINMD"
|
|
33
|
+
axis_1_type = "CTYPE1"
|
|
34
|
+
axis_2_type = "CTYPE2"
|
|
35
|
+
axis_3_type = "CTYPE3"
|
|
36
|
+
grating_position_deg = "CNGRTPOS"
|
|
37
|
+
grating_littrow_angle_deg = "CNGRTLAT"
|
|
38
|
+
grating_constant = "CNGRTCON"
|
|
39
|
+
center_wavelength = "CNCENWAV"
|
|
40
|
+
slit_width = "CNSLITW"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""CryoNIRSP calibration pipeline parameters."""
|
|
2
|
+
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
from functools import cached_property
|
|
4
5
|
|
|
@@ -7,6 +8,7 @@ import numpy as np
|
|
|
7
8
|
from dkist_processing_common.models.parameters import ParameterArmIdMixin
|
|
8
9
|
from dkist_processing_common.models.parameters import ParameterBase
|
|
9
10
|
from dkist_processing_common.models.parameters import ParameterWavelengthMixin
|
|
11
|
+
from solar_wavelength_calibration import DownloadConfig
|
|
10
12
|
|
|
11
13
|
from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
|
|
12
14
|
|
|
@@ -82,11 +84,6 @@ class CryonirspParameters(ParameterBase, ParameterWavelengthMixin, ParameterArmI
|
|
|
82
84
|
"""Name of set of fitting flags to use during PAC Calibration Unit parameter fits."""
|
|
83
85
|
return self._find_most_recent_past_value("cryonirsp_polcal_pac_fit_mode")
|
|
84
86
|
|
|
85
|
-
@property
|
|
86
|
-
def polcal_pac_init_set(self):
|
|
87
|
-
"""Name of set of initial values for Calibration Unit parameter fit."""
|
|
88
|
-
return self._find_most_recent_past_value("cryonirsp_polcal_pac_init_set")
|
|
89
|
-
|
|
90
87
|
@property
|
|
91
88
|
def beam_boundaries_smoothing_disk_size(self) -> int:
|
|
92
89
|
"""Return the size of the smoothing disk (in pixels) to be used in the beam boundaries computation."""
|
|
@@ -141,8 +138,8 @@ class CryonirspParameters(ParameterBase, ParameterWavelengthMixin, ParameterArmI
|
|
|
141
138
|
@cached_property
|
|
142
139
|
def linearization_thresholds(self) -> np.ndarray:
|
|
143
140
|
"""Name of parameter associated with the linearization thresholds."""
|
|
144
|
-
|
|
145
|
-
value = self._load_param_value_from_numpy_save(
|
|
141
|
+
param_obj = self._find_parameter_for_arm("cryonirsp_linearization_thresholds")
|
|
142
|
+
value = self._load_param_value_from_numpy_save(param_obj=param_obj)
|
|
146
143
|
# float64 data can blow up the memory required for linearization - convert to float32
|
|
147
144
|
if np.issubdtype(value.dtype, np.float64):
|
|
148
145
|
value = value.astype(np.float32, casting="same_kind")
|
|
@@ -272,24 +269,15 @@ class CryonirspParameters(ParameterBase, ParameterWavelengthMixin, ParameterArmI
|
|
|
272
269
|
"""Return the CryoNIRSP pixel pitch."""
|
|
273
270
|
return self._find_most_recent_past_value("cryonirsp_pixel_pitch_micron") * u.micron
|
|
274
271
|
|
|
275
|
-
@
|
|
276
|
-
def
|
|
277
|
-
"""Solar
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
- wavelength in nanometers
|
|
281
|
-
- transmission at given wavelength
|
|
282
|
-
"""
|
|
283
|
-
param_dict = self._find_most_recent_past_value("cryonirsp_solar_atlas")
|
|
284
|
-
return self._load_param_value_from_numpy_save(param_dict)
|
|
272
|
+
@property
|
|
273
|
+
def wavecal_atlas_download_config(self) -> DownloadConfig:
|
|
274
|
+
"""Define the `~solar_wavelength_calibration.DownloadConfig` used to grab the Solar atlas used for wavelength calibration."""
|
|
275
|
+
config_dict = self._find_most_recent_past_value("cryonirsp_wavecal_atlas_download_config")
|
|
276
|
+
return DownloadConfig.model_validate(config_dict)
|
|
285
277
|
|
|
286
278
|
@cached_property
|
|
287
|
-
def
|
|
288
|
-
"""
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
- transmission at given wavelength
|
|
293
|
-
"""
|
|
294
|
-
param_dict = self._find_most_recent_past_value("cryonirsp_telluric_atlas")
|
|
295
|
-
return self._load_param_value_from_numpy_save(param_dict)
|
|
279
|
+
def wavecal_fraction_of_unweighted_edge_pixels(self) -> int:
|
|
280
|
+
"""Return the fraction of edge pixels to weight to zero during the wavelength calibration."""
|
|
281
|
+
return self._find_most_recent_past_value(
|
|
282
|
+
"cryonirsp_wavecal_fraction_of_unweighted_edge_pixels"
|
|
283
|
+
)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""CryoNIRSP FITS access for L0 data."""
|
|
2
|
+
|
|
2
3
|
import numpy as np
|
|
3
4
|
from astropy.io import fits
|
|
4
5
|
from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
|
|
5
6
|
|
|
6
7
|
from dkist_processing_cryonirsp.models.exposure_conditions import CRYO_EXP_TIME_ROUND_DIGITS
|
|
7
8
|
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
9
|
+
from dkist_processing_cryonirsp.models.fits_access import CryonirspMetadataKey
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class CryonirspRampFitsAccess(L0FitsAccess):
|
|
@@ -33,16 +35,15 @@ class CryonirspRampFitsAccess(L0FitsAccess):
|
|
|
33
35
|
):
|
|
34
36
|
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
35
37
|
|
|
36
|
-
self.camera_readout_mode = self.header[
|
|
37
|
-
self.curr_frame_in_ramp: int = self.header[
|
|
38
|
-
self.num_frames_in_ramp: int = self.header[
|
|
39
|
-
self.arm_id: str = self.header[
|
|
40
|
-
self.filter_name = self.header[
|
|
41
|
-
self.roi_1_origin_x = self.header[
|
|
42
|
-
self.roi_1_origin_y = self.header[
|
|
43
|
-
self.roi_1_size_x = self.header[
|
|
44
|
-
self.roi_1_size_y = self.header[
|
|
45
|
-
self.obs_ip_start_time = self.header["DKIST011"]
|
|
38
|
+
self.camera_readout_mode = self.header[CryonirspMetadataKey.camera_readout_mode]
|
|
39
|
+
self.curr_frame_in_ramp: int = self.header[CryonirspMetadataKey.curr_frame_in_ramp]
|
|
40
|
+
self.num_frames_in_ramp: int = self.header[CryonirspMetadataKey.num_frames_in_ramp]
|
|
41
|
+
self.arm_id: str = self.header[CryonirspMetadataKey.arm_id]
|
|
42
|
+
self.filter_name = self.header[CryonirspMetadataKey.filter_name].upper()
|
|
43
|
+
self.roi_1_origin_x = self.header[CryonirspMetadataKey.roi_1_origin_x]
|
|
44
|
+
self.roi_1_origin_y = self.header[CryonirspMetadataKey.roi_1_origin_y]
|
|
45
|
+
self.roi_1_size_x = self.header[CryonirspMetadataKey.roi_1_size_x]
|
|
46
|
+
self.roi_1_size_y = self.header[CryonirspMetadataKey.roi_1_size_y]
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class CryonirspL0FitsAccess(L0FitsAccess):
|
|
@@ -71,46 +72,38 @@ class CryonirspL0FitsAccess(L0FitsAccess):
|
|
|
71
72
|
):
|
|
72
73
|
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
73
74
|
|
|
74
|
-
self.arm_id: str = self.header[
|
|
75
|
-
self.number_of_modulator_states: int = self.header[
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
self.
|
|
79
|
-
self.
|
|
80
|
-
self.
|
|
81
|
-
self.
|
|
82
|
-
self.
|
|
83
|
-
self.
|
|
84
|
-
self.
|
|
85
|
-
self.
|
|
86
|
-
self.
|
|
87
|
-
self.
|
|
88
|
-
self.
|
|
89
|
-
self.
|
|
90
|
-
self.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
self.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
98
|
-
self.grating_position_deg: float = self.header["CNGRTPOS"]
|
|
99
|
-
self.grating_littrow_angle_deg: float = self.header["CNGRTLAT"]
|
|
100
|
-
# grating_constant is in the L0 header in (mm)^(-1) when needed in (m)^(-1) hence multiply by 1000.
|
|
101
|
-
self.grating_constant: float = self.header["CNGRTCON"] * 1000
|
|
102
|
-
self.obs_ip_start_time = self.header["DKIST011"]
|
|
75
|
+
self.arm_id: str = self.header[CryonirspMetadataKey.arm_id]
|
|
76
|
+
self.number_of_modulator_states: int = self.header[
|
|
77
|
+
CryonirspMetadataKey.number_of_modulator_states
|
|
78
|
+
]
|
|
79
|
+
self.modulator_state: int = self.header[CryonirspMetadataKey.modulator_state]
|
|
80
|
+
self.scan_step: int = self.header[CryonirspMetadataKey.scan_step]
|
|
81
|
+
self.num_scan_steps: int = self.header[CryonirspMetadataKey.num_scan_steps]
|
|
82
|
+
self.num_cn1_scan_steps: int = self.header[CryonirspMetadataKey.num_cn1_scan_steps]
|
|
83
|
+
self.num_cn2_scan_steps: int = self.header[CryonirspMetadataKey.num_cn2_scan_steps]
|
|
84
|
+
self.cn2_step_size: float = self.header[CryonirspMetadataKey.cn2_step_size]
|
|
85
|
+
self.meas_num: int = self.header[CryonirspMetadataKey.meas_num]
|
|
86
|
+
self.num_meas: int = self.header[CryonirspMetadataKey.num_meas]
|
|
87
|
+
self.sub_repeat_num = self.header[CryonirspMetadataKey.sub_repeat_num]
|
|
88
|
+
self.num_sub_repeats: int = self.header[CryonirspMetadataKey.num_sub_repeats]
|
|
89
|
+
self.modulator_spin_mode: str = self.header[CryonirspMetadataKey.modulator_spin_mode]
|
|
90
|
+
self.axis_1_type: str = self.header[CryonirspMetadataKey.axis_1_type]
|
|
91
|
+
self.axis_2_type: str = self.header[CryonirspMetadataKey.axis_2_type]
|
|
92
|
+
self.axis_3_type: str = self.header[CryonirspMetadataKey.axis_3_type]
|
|
93
|
+
self.grating_position_deg: float = self.header[CryonirspMetadataKey.grating_position_deg]
|
|
94
|
+
self.grating_littrow_angle_deg: float = self.header[
|
|
95
|
+
CryonirspMetadataKey.grating_littrow_angle_deg
|
|
96
|
+
]
|
|
97
|
+
self.grating_constant: float = self.header[CryonirspMetadataKey.grating_constant]
|
|
98
|
+
self.filter_name = self.header[CryonirspMetadataKey.filter_name.value].upper()
|
|
103
99
|
# The ExposureConditions are a combination of the exposure time and the OD filter name:
|
|
104
100
|
self.exposure_conditions = ExposureConditions(
|
|
105
|
-
round(self.fpa_exposure_time_ms, CRYO_EXP_TIME_ROUND_DIGITS),
|
|
106
|
-
self.header["CNFILTNP"].upper(),
|
|
101
|
+
round(self.fpa_exposure_time_ms, CRYO_EXP_TIME_ROUND_DIGITS), self.filter_name
|
|
107
102
|
)
|
|
108
|
-
self.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
"""Convert the inner loop step number from float to int."""
|
|
113
|
-
return int(self.header["CNP1DCUR"])
|
|
103
|
+
self.center_wavelength = self.header[CryonirspMetadataKey.center_wavelength]
|
|
104
|
+
self.slit_width = self.header[CryonirspMetadataKey.slit_width]
|
|
105
|
+
# Convert the inner loop step number from float to int:
|
|
106
|
+
self.cn1_scan_step = int(self.header[CryonirspMetadataKey.cn1_scan_step])
|
|
114
107
|
|
|
115
108
|
|
|
116
109
|
class CryonirspLinearizedFitsAccess(CryonirspL0FitsAccess):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Buds to parse the combination of exposure time and filter name."""
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from typing import Hashable
|
|
4
|
+
from typing import NamedTuple
|
|
4
5
|
from typing import Type
|
|
5
6
|
|
|
6
7
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
@@ -28,7 +29,6 @@ class CryonirspTaskExposureConditionsBud(Stem):
|
|
|
28
29
|
|
|
29
30
|
def __init__(self, stem_name: str, ip_task_type: str):
|
|
30
31
|
super().__init__(stem_name=stem_name)
|
|
31
|
-
self.metadata_key = "exposure_conditions"
|
|
32
32
|
self.ip_task_type = ip_task_type
|
|
33
33
|
|
|
34
34
|
def setter(self, fits_obj: CryonirspL0FitsAccess):
|
|
@@ -42,7 +42,7 @@ class CryonirspTaskExposureConditionsBud(Stem):
|
|
|
42
42
|
"""
|
|
43
43
|
ip_task_type = parse_header_ip_task_with_gains(fits_obj)
|
|
44
44
|
if ip_task_type.lower() == self.ip_task_type.lower():
|
|
45
|
-
return
|
|
45
|
+
return fits_obj.exposure_conditions
|
|
46
46
|
return SpilledDirt
|
|
47
47
|
|
|
48
48
|
def getter(self, key: Hashable) -> tuple[ExposureConditions, ...]:
|
|
@@ -56,7 +56,6 @@ class CryonirspConditionalTaskExposureConditionsBudBase(Stem):
|
|
|
56
56
|
|
|
57
57
|
def __init__(self, stem_name: str, task_types_to_ignore: list[str]):
|
|
58
58
|
super().__init__(stem_name=stem_name)
|
|
59
|
-
self.metadata_key = "exposure_conditions"
|
|
60
59
|
self.task_types_to_ignore = task_types_to_ignore
|
|
61
60
|
|
|
62
61
|
def setter(self, fits_obj: CryonirspL0FitsAccess) -> ExposureConditions | Type[SpilledDirt]:
|
|
@@ -73,7 +72,7 @@ class CryonirspConditionalTaskExposureConditionsBudBase(Stem):
|
|
|
73
72
|
"""
|
|
74
73
|
task_type = parse_header_ip_task_with_gains(fits_obj=fits_obj)
|
|
75
74
|
if task_type.upper() not in self.task_types_to_ignore:
|
|
76
|
-
return
|
|
75
|
+
return fits_obj.exposure_conditions
|
|
77
76
|
return SpilledDirt
|
|
78
77
|
|
|
79
78
|
def getter(self, key: Hashable) -> tuple[ExposureConditions, ...]:
|
|
@@ -103,7 +102,6 @@ class CryonirspSPConditionalTaskExposureConditionsBud(
|
|
|
103
102
|
stem_name=CryonirspBudName.sp_non_dark_and_non_polcal_task_exposure_conditions_list.value,
|
|
104
103
|
task_types_to_ignore=[TaskName.dark.value, TaskName.polcal.value],
|
|
105
104
|
)
|
|
106
|
-
self.metadata_key = "exposure_conditions"
|
|
107
105
|
|
|
108
106
|
|
|
109
107
|
class CryonirspCIConditionalTaskExposureConditionsBud(
|
|
@@ -120,19 +118,22 @@ class CryonirspCIConditionalTaskExposureConditionsBud(
|
|
|
120
118
|
TaskName.lamp_gain.value,
|
|
121
119
|
],
|
|
122
120
|
)
|
|
123
|
-
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class DarkTaskTestAndExposureConditionsContainer(NamedTuple):
|
|
124
|
+
"""Named tuple to hold whether the task is dark along with the associated exposure conditions."""
|
|
125
|
+
|
|
126
|
+
is_dark: bool
|
|
127
|
+
exposure_conditions: ExposureConditions
|
|
124
128
|
|
|
125
129
|
|
|
126
130
|
class CryonirspPickyDarkExposureConditionsBudBase(Stem):
|
|
127
131
|
"""Parse exposure conditions tuples to ensure existence of dark frames with the required exposure conditions."""
|
|
128
132
|
|
|
129
|
-
DarkTaskTestAndExposureConditions =
|
|
130
|
-
"DarkTaskTestAndExposureConditions", ["is_dark", "exposure_conditions"]
|
|
131
|
-
)
|
|
133
|
+
DarkTaskTestAndExposureConditions = DarkTaskTestAndExposureConditionsContainer
|
|
132
134
|
|
|
133
135
|
def __init__(self, stem_name: str, task_types_to_ignore: list[str]):
|
|
134
136
|
super().__init__(stem_name=stem_name)
|
|
135
|
-
self.metadata_key = "exposure_conditions"
|
|
136
137
|
self.task_types_to_ignore = task_types_to_ignore
|
|
137
138
|
|
|
138
139
|
def setter(self, fits_obj: CryonirspL0FitsAccess) -> tuple | Type[SpilledDirt]:
|
|
@@ -150,11 +151,11 @@ class CryonirspPickyDarkExposureConditionsBudBase(Stem):
|
|
|
150
151
|
task_type = parse_header_ip_task_with_gains(fits_obj=fits_obj)
|
|
151
152
|
if task_type.upper() == TaskName.dark.value:
|
|
152
153
|
return self.DarkTaskTestAndExposureConditions(
|
|
153
|
-
is_dark=True, exposure_conditions=
|
|
154
|
+
is_dark=True, exposure_conditions=fits_obj.exposure_conditions
|
|
154
155
|
)
|
|
155
156
|
if task_type.upper() not in self.task_types_to_ignore:
|
|
156
157
|
return self.DarkTaskTestAndExposureConditions(
|
|
157
|
-
is_dark=False, exposure_conditions=
|
|
158
|
+
is_dark=False, exposure_conditions=fits_obj.exposure_conditions
|
|
158
159
|
)
|
|
159
160
|
# Ignored task types fall through
|
|
160
161
|
return SpilledDirt
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Stems for organizing files into separate dsps repeats."""
|
|
2
|
+
|
|
2
3
|
from dkist_processing_cryonirsp.models.constants import CryonirspBudName
|
|
3
4
|
from dkist_processing_cryonirsp.models.tags import CryonirspStemName
|
|
4
5
|
from dkist_processing_cryonirsp.parsers.scan_step import MapScanStepStemBase
|
|
@@ -1,45 +1,58 @@
|
|
|
1
1
|
"""Copies of UniqueBud and SingleValueSingleKeyFlower from common that only activate if the frames are "observe" task."""
|
|
2
|
+
|
|
3
|
+
from typing import Hashable
|
|
2
4
|
from typing import Type
|
|
3
5
|
|
|
4
6
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
5
7
|
from dkist_processing_common.parsers.single_value_single_key_flower import (
|
|
6
8
|
SingleValueSingleKeyFlower,
|
|
7
9
|
)
|
|
8
|
-
from dkist_processing_common.parsers.unique_bud import UniqueBud
|
|
9
10
|
|
|
10
11
|
from dkist_processing_cryonirsp.models.constants import CryonirspBudName
|
|
12
|
+
from dkist_processing_cryonirsp.models.fits_access import CryonirspMetadataKey
|
|
11
13
|
from dkist_processing_cryonirsp.models.tags import CryonirspStemName
|
|
12
14
|
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
|
|
15
|
+
from dkist_processing_cryonirsp.parsers.scan_step import NumberOfScanStepsBase
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
class NumberOfMeasurementsBud(
|
|
18
|
+
class NumberOfMeasurementsBud(NumberOfScanStepsBase):
|
|
16
19
|
"""Bud for finding the total number of measurements per scan step."""
|
|
17
20
|
|
|
18
21
|
def __init__(self):
|
|
19
|
-
|
|
20
|
-
super().__init__(
|
|
21
|
-
constant_name=CryonirspBudName.num_meas.value, metadata_key=self.metadata_key
|
|
22
|
-
)
|
|
22
|
+
super().__init__(stem_name=CryonirspBudName.num_meas.value)
|
|
23
23
|
|
|
24
|
-
def
|
|
24
|
+
def getter(self, key: Hashable) -> Hashable:
|
|
25
25
|
"""
|
|
26
|
-
|
|
26
|
+
Search all scan steps to find the maximum number of measurements in a step.
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
This maximum number should be the same for all measurements, with the possible exception of the last one in
|
|
29
|
+
an abort.
|
|
30
|
+
|
|
31
|
+
Abort possibilities:
|
|
32
|
+
* if a measurement is missing in the last scan step of a multi-scan, multi-map observation, it will be handled
|
|
33
|
+
as an incomplete scan step and truncated by the scan step abort handler.
|
|
34
|
+
* if a measurement is missing in a single-map, single-scan observation then the number of measurements will be
|
|
35
|
+
given by the last measurement value that had as many frames as the maximum number of frames of any
|
|
36
|
+
measurement.
|
|
37
|
+
This is a formality for intensity mode observations but is an important check for polarimetric data as the
|
|
38
|
+
abort may have happened in the middle of the modulator state sequence.
|
|
32
39
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
measurements_in_scan_steps = []
|
|
41
|
+
for meas_dict in self.scan_step_dict.values():
|
|
42
|
+
measurements_in_scan_steps.append(len(meas_dict))
|
|
43
|
+
return max(
|
|
44
|
+
measurements_in_scan_steps
|
|
45
|
+
) # if there are incomplete measurements, they should be at the end and will be truncated by an incomplete scan step
|
|
36
46
|
|
|
37
47
|
|
|
38
48
|
class MeasurementNumberFlower(SingleValueSingleKeyFlower):
|
|
39
49
|
"""Flower for a measurement number."""
|
|
40
50
|
|
|
41
51
|
def __init__(self):
|
|
42
|
-
super().__init__(
|
|
52
|
+
super().__init__(
|
|
53
|
+
tag_stem_name=CryonirspStemName.meas_num.value,
|
|
54
|
+
metadata_key=CryonirspMetadataKey.meas_num,
|
|
55
|
+
)
|
|
43
56
|
|
|
44
57
|
def setter(self, fits_obj: CryonirspL0FitsAccess) -> Type[SpilledDirt] | int:
|
|
45
58
|
"""
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Copy of SingleValueSingleKeyFlower from common that only activates if the frames are "observe" task."""
|
|
2
|
+
|
|
2
3
|
from typing import Type
|
|
3
4
|
|
|
4
5
|
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
@@ -7,6 +8,7 @@ from dkist_processing_common.parsers.single_value_single_key_flower import (
|
|
|
7
8
|
SingleValueSingleKeyFlower,
|
|
8
9
|
)
|
|
9
10
|
|
|
11
|
+
from dkist_processing_cryonirsp.models.fits_access import CryonirspMetadataKey
|
|
10
12
|
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
|
|
11
13
|
|
|
12
14
|
|
|
@@ -14,7 +16,9 @@ class ModstateNumberFlower(SingleValueSingleKeyFlower):
|
|
|
14
16
|
"""Flower for a modstate number."""
|
|
15
17
|
|
|
16
18
|
def __init__(self):
|
|
17
|
-
super().__init__(
|
|
19
|
+
super().__init__(
|
|
20
|
+
tag_stem_name=StemName.modstate.value, metadata_key=CryonirspMetadataKey.modulator_state
|
|
21
|
+
)
|
|
18
22
|
|
|
19
23
|
def setter(self, fits_obj: CryonirspL0FitsAccess) -> Type[SpilledDirt] | int:
|
|
20
24
|
"""
|