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,168 @@
|
|
|
1
|
+
"""CryoNIRSP tags."""
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
from dkist_processing_common.models.tags import StemName
|
|
5
|
+
from dkist_processing_common.models.tags import Tag
|
|
6
|
+
|
|
7
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
8
|
+
from dkist_processing_cryonirsp.models.task_name import CryonirspTaskName
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CryonirspStemName(str, Enum):
|
|
12
|
+
"""Controlled list of Tag Stems."""
|
|
13
|
+
|
|
14
|
+
linearized = "LINEARIZED"
|
|
15
|
+
beam = "BEAM"
|
|
16
|
+
scan_step = "SCAN_STEP"
|
|
17
|
+
curr_frame_in_ramp = "CURR_FRAME_IN_RAMP"
|
|
18
|
+
time_obs = "TIME_OBS"
|
|
19
|
+
meas_num = "MEAS_NUM"
|
|
20
|
+
map_scan = "MAP_SCAN"
|
|
21
|
+
exposure_conditions = "EXPOSURE_CONDITIONS"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CryonirspTag(Tag):
|
|
25
|
+
"""CryoNIRSP specific tag formatting."""
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def beam(cls, beam_num: int) -> str:
|
|
29
|
+
"""
|
|
30
|
+
Tags by beam number.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
beam_num
|
|
35
|
+
The beam number
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
The formatted tag string
|
|
40
|
+
"""
|
|
41
|
+
return cls.format_tag(CryonirspStemName.beam, beam_num)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def scan_step(cls, scan_step: int) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Tags by the current scan step number.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
scan_step
|
|
51
|
+
The current scan step number
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
The formatted tag string
|
|
56
|
+
"""
|
|
57
|
+
return cls.format_tag(CryonirspStemName.scan_step, scan_step)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def map_scan(cls, map_scan: int) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Tags by the current scan step number.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
map_scan
|
|
67
|
+
The current map_scan number
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
The formatted tag string
|
|
72
|
+
"""
|
|
73
|
+
return cls.format_tag(CryonirspStemName.map_scan, map_scan)
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def linearized(cls) -> str:
|
|
77
|
+
"""
|
|
78
|
+
Tags for linearized frames.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
The formatted tag string
|
|
83
|
+
"""
|
|
84
|
+
return cls.format_tag(CryonirspStemName.linearized)
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def curr_frame_in_ramp(cls, curr_frame_in_ramp: int) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Tags based on the current frame number in the ramp.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
curr_frame_in_ramp
|
|
94
|
+
The current frame number for this ramp
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
The formatted tag string
|
|
99
|
+
"""
|
|
100
|
+
return cls.format_tag(CryonirspStemName.curr_frame_in_ramp, curr_frame_in_ramp)
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def time_obs(cls, time_obs: str) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Tags by the observe date.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
time_obs
|
|
110
|
+
The observe time
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
The formatted tag string
|
|
115
|
+
"""
|
|
116
|
+
return cls.format_tag(CryonirspStemName.time_obs, time_obs)
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def meas_num(cls, meas_num: int) -> str:
|
|
120
|
+
"""
|
|
121
|
+
Tags by the measurement number.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
meas_num
|
|
126
|
+
The current measurement number
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
The formatted tag string
|
|
131
|
+
"""
|
|
132
|
+
return cls.format_tag(CryonirspStemName.meas_num, meas_num)
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def exposure_conditions(cls, exposure_conditions: ExposureConditions) -> str:
|
|
136
|
+
"""
|
|
137
|
+
Tags by the measurement number.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
exposure_conditions
|
|
142
|
+
A tuple of (exposure time, filter name)
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
The formatted tag string
|
|
147
|
+
"""
|
|
148
|
+
return cls.format_tag(CryonirspStemName.exposure_conditions, exposure_conditions)
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def task_beam_boundaries(cls) -> str:
|
|
152
|
+
"""Tags intermediate beam boundary calibration objects."""
|
|
153
|
+
return cls.format_tag(StemName.task, CryonirspTaskName.beam_boundaries.value)
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def task_bad_pixel_map(cls) -> str:
|
|
157
|
+
"""Tags intermediate bad pixel map objects."""
|
|
158
|
+
return cls.format_tag(StemName.task, CryonirspTaskName.bad_pixel_map.value)
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def task_spectral_fit(cls) -> str:
|
|
162
|
+
"""Tags spectral fit solution."""
|
|
163
|
+
return cls.format_tag(StemName.task, CryonirspTaskName.spectral_fit.value)
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def task_characteristic_spectra(cls) -> str:
|
|
167
|
+
"""Tags 1D intermediate characteristic spectra."""
|
|
168
|
+
return cls.format_tag(StemName.task, CryonirspTaskName.solar_char_spec.value)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""List of intermediate task names."""
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CryonirspTaskName(str, Enum):
|
|
6
|
+
"""Controlled list of CryoNirsp task tag names."""
|
|
7
|
+
|
|
8
|
+
beam_boundaries = "BEAM_BOUNDARIES"
|
|
9
|
+
bad_pixel_map = "BAD_PIXEL_MAP"
|
|
10
|
+
polcal_dark = "POLCAL_DARK"
|
|
11
|
+
polcal_gain = "POLCAL_GAIN"
|
|
12
|
+
spectral_corrected_solar_array = "SPECTRAL_CORRECTED_SOLAR_ARRAY"
|
|
13
|
+
spectral_fit = "SPECTRAL_FIT"
|
|
14
|
+
solar_char_spec = "SOLAR_CHAR_SPEC"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Init."""
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""CryoNIRSP FITS access for L0 data."""
|
|
2
|
+
from astropy.io import fits
|
|
3
|
+
from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
|
|
4
|
+
|
|
5
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import CRYO_EXP_TIME_ROUND_DIGITS
|
|
6
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CryonirspRampFitsAccess(L0FitsAccess):
|
|
10
|
+
"""
|
|
11
|
+
Class to provide easy access to L0 headers for non-linearized (raw) CryoNIRSP data.
|
|
12
|
+
|
|
13
|
+
i.e. instead of <CryonirspL0FitsAccess>.header['key'] this class lets us use <CryonirspL0FitsAccess>.key instead
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
hdu :
|
|
18
|
+
Fits L0 header object
|
|
19
|
+
|
|
20
|
+
name : str
|
|
21
|
+
The name of the file that was loaded into this FitsAccess object
|
|
22
|
+
|
|
23
|
+
auto_squeeze : bool
|
|
24
|
+
When set to True, dimensions of length 1 will be removed from the array
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
hdu: fits.ImageHDU | fits.PrimaryHDU | fits.CompImageHDU,
|
|
30
|
+
name: str | None = None,
|
|
31
|
+
auto_squeeze: bool = True,
|
|
32
|
+
):
|
|
33
|
+
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
34
|
+
|
|
35
|
+
self.camera_readout_mode = self.header["CNCAMMD"]
|
|
36
|
+
self.curr_frame_in_ramp: int = self.header["CNCNDR"]
|
|
37
|
+
self.num_frames_in_ramp: int = self.header["CNNNDR"]
|
|
38
|
+
self.arm_id: str = self.header["CNARMID"]
|
|
39
|
+
self.filter_name = self.header["CNFILTNP"].upper()
|
|
40
|
+
self.roi_1_origin_x = self.header["HWROI1OX"]
|
|
41
|
+
self.roi_1_origin_y = self.header["HWROI1OY"]
|
|
42
|
+
self.roi_1_size_x = self.header["HWROI1SX"]
|
|
43
|
+
self.roi_1_size_y = self.header["HWROI1SY"]
|
|
44
|
+
self.obs_ip_start_time = self.header["DKIST011"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CryonirspL0FitsAccess(L0FitsAccess):
|
|
48
|
+
"""
|
|
49
|
+
Class to provide easy access to L0 headers for linearized (ready for processing) CryoNIRSP data.
|
|
50
|
+
|
|
51
|
+
i.e. instead of <CryonirspL0FitsAccess>.header['key'] this class lets us use <CryonirspL0FitsAccess>.key instead
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
hdu :
|
|
56
|
+
Fits L0 header object
|
|
57
|
+
|
|
58
|
+
name : str
|
|
59
|
+
The name of the file that was loaded into this FitsAccess object
|
|
60
|
+
|
|
61
|
+
auto_squeeze : bool
|
|
62
|
+
When set to True, dimensions of length 1 will be removed from the array
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
hdu: fits.ImageHDU | fits.PrimaryHDU | fits.CompImageHDU,
|
|
68
|
+
name: str | None = None,
|
|
69
|
+
auto_squeeze: bool = True,
|
|
70
|
+
):
|
|
71
|
+
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
72
|
+
|
|
73
|
+
self.arm_id: str = self.header["CNARMID"]
|
|
74
|
+
self.number_of_modulator_states: int = self.header["CNMODNST"]
|
|
75
|
+
self.modulator_state: int = self.header["CNMODCST"]
|
|
76
|
+
self.scan_step: int = self.header["CNCURSCN"]
|
|
77
|
+
self.num_scan_steps: int = self.header["CNNUMSCN"]
|
|
78
|
+
self.num_cn1_scan_steps: int = self.header["CNP1DNSP"]
|
|
79
|
+
self.num_cn2_scan_steps: int = self.header["CNP2DNSP"]
|
|
80
|
+
self.cn2_step_size: float = self.header["CNP2DSS"]
|
|
81
|
+
self.meas_num: int = self.header["CNCMEAS"]
|
|
82
|
+
self.num_meas: int = self.header["CNNMEAS"]
|
|
83
|
+
self.sub_repeat_num = self.header["CNCSREP"]
|
|
84
|
+
self.num_sub_repeats: int = self.header["CNSUBREP"]
|
|
85
|
+
self.modulator_spin_mode: str = self.header["CNSPINMD"]
|
|
86
|
+
self.axis_1_type: str = self.header["CTYPE1"]
|
|
87
|
+
self.axis_2_type: str = self.header["CTYPE2"]
|
|
88
|
+
self.axis_3_type: str = self.header["CTYPE3"]
|
|
89
|
+
self.wave_min: float = round(
|
|
90
|
+
self.header["CRVAL1"] - (self.header["CRPIX1"] * self.header["CDELT1"]), 1
|
|
91
|
+
)
|
|
92
|
+
self.wave_max: float = round(
|
|
93
|
+
self.header["CRVAL1"]
|
|
94
|
+
+ ((self.header["NAXIS1"] - self.header["CRPIX1"]) * self.header["CDELT1"]),
|
|
95
|
+
1,
|
|
96
|
+
)
|
|
97
|
+
self.grating_position_deg: float = self.header["CNGRTPOS"]
|
|
98
|
+
self.grating_littrow_angle_deg: float = self.header["CNGRTLAT"]
|
|
99
|
+
self.grating_constant: float = self.header["CNGRTCON"]
|
|
100
|
+
self.obs_ip_start_time = self.header["DKIST011"]
|
|
101
|
+
# The ExposureConditions are a combination of the exposure time and the OD filter name:
|
|
102
|
+
self.exposure_conditions = ExposureConditions(
|
|
103
|
+
round(self.fpa_exposure_time_ms, CRYO_EXP_TIME_ROUND_DIGITS),
|
|
104
|
+
self.header["CNFILTNP"].upper(),
|
|
105
|
+
)
|
|
106
|
+
self.solar_gain_ip_start_time = self.header["DATE-OBS"]
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def cn1_scan_step(self):
|
|
110
|
+
"""Convert the inner loop step number from float to int."""
|
|
111
|
+
return int(self.header["CNP1DCUR"])
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""CryoNIRSP FITS access for L1 data."""
|
|
2
|
+
from astropy.io import fits
|
|
3
|
+
from dkist_processing_common.parsers.l1_fits_access import L1FitsAccess
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CryonirspL1FitsAccess(L1FitsAccess):
|
|
7
|
+
"""
|
|
8
|
+
Class to provide easy access to L1 headers.
|
|
9
|
+
|
|
10
|
+
i.e. instead of <CryonirspL1FitsAccess>.header['key'] this class lets us use <CryonirspL1FitsAccess>.key instead
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
hdu :
|
|
15
|
+
Fits L1 header object
|
|
16
|
+
|
|
17
|
+
name : str
|
|
18
|
+
The name of the file that was loaded into this FitsAccess object
|
|
19
|
+
|
|
20
|
+
auto_squeeze : bool
|
|
21
|
+
When set to True, dimensions of length 1 will be removed from the array
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
hdu: fits.ImageHDU | fits.PrimaryHDU | fits.CompImageHDU,
|
|
27
|
+
name: str | None = None,
|
|
28
|
+
auto_squeeze: bool | None = True,
|
|
29
|
+
):
|
|
30
|
+
super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Buds to parse the combination of exposure time and filter name."""
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
from typing import Hashable
|
|
4
|
+
from typing import Type
|
|
5
|
+
|
|
6
|
+
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
7
|
+
from dkist_processing_common.models.flower_pot import Stem
|
|
8
|
+
from dkist_processing_common.models.flower_pot import Thorn
|
|
9
|
+
from dkist_processing_common.models.task_name import TaskName
|
|
10
|
+
from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
|
|
11
|
+
|
|
12
|
+
from dkist_processing_cryonirsp.models.constants import CryonirspBudName
|
|
13
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import ExposureConditions
|
|
14
|
+
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
|
|
15
|
+
|
|
16
|
+
_DARK_AND_POLCAL_TASK_TYPES = (TaskName.dark.value, TaskName.polcal.value)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CryonirspTaskExposureConditionsBud(Stem):
|
|
20
|
+
"""
|
|
21
|
+
Bud to allow custom parsing of exposure conditions based on ip task type.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
stem_name : str
|
|
26
|
+
The name of the stem of the tag
|
|
27
|
+
ip_task_type : str
|
|
28
|
+
Instrument program task type
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, stem_name: str, ip_task_type: str):
|
|
32
|
+
super().__init__(stem_name=stem_name)
|
|
33
|
+
self.metadata_key = "exposure_conditions"
|
|
34
|
+
self.ip_task_type = ip_task_type
|
|
35
|
+
|
|
36
|
+
def setter(self, fits_obj: CryonirspL0FitsAccess):
|
|
37
|
+
"""
|
|
38
|
+
Set the value of the bud.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
fits_obj:
|
|
43
|
+
A single FitsAccess object
|
|
44
|
+
"""
|
|
45
|
+
ip_task_type = parse_header_ip_task_with_gains(fits_obj)
|
|
46
|
+
if ip_task_type.lower() == self.ip_task_type.lower():
|
|
47
|
+
return getattr(fits_obj, self.metadata_key)
|
|
48
|
+
return SpilledDirt
|
|
49
|
+
|
|
50
|
+
def getter(self, key: Hashable) -> tuple[ExposureConditions, ...]:
|
|
51
|
+
"""Return a list of the ExposureConditions for this ip task type."""
|
|
52
|
+
exposure_conditions_tuple = tuple(sorted(set(self.key_to_petal_dict.values())))
|
|
53
|
+
return exposure_conditions_tuple
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CryonirspNonDarkAndNonPolcalTaskExposureConditionsBud(Stem):
|
|
57
|
+
"""For ip task types that are neither DARK nor POLCAL, produce a list of exposure conditions tuples."""
|
|
58
|
+
|
|
59
|
+
def __init__(self):
|
|
60
|
+
super().__init__(
|
|
61
|
+
stem_name=CryonirspBudName.non_dark_and_non_polcal_task_exposure_conditions_list.value
|
|
62
|
+
)
|
|
63
|
+
self.metadata_key = "exposure_conditions"
|
|
64
|
+
|
|
65
|
+
def setter(self, fits_obj: CryonirspL0FitsAccess) -> ExposureConditions | Type[SpilledDirt]:
|
|
66
|
+
"""
|
|
67
|
+
Set the task exposure conditions tuple for this fits object.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
fits_obj
|
|
72
|
+
The input fits object
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
The exposure time associated with this fits object
|
|
76
|
+
"""
|
|
77
|
+
if fits_obj.ip_task_type.upper() not in _DARK_AND_POLCAL_TASK_TYPES:
|
|
78
|
+
return getattr(fits_obj, self.metadata_key)
|
|
79
|
+
return SpilledDirt
|
|
80
|
+
|
|
81
|
+
def getter(self, key: Hashable) -> tuple[ExposureConditions, ...]:
|
|
82
|
+
"""
|
|
83
|
+
Get the list of exposure conditions tuples.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
key
|
|
88
|
+
The input key
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
A tuple of (exposure time, filter name) tuples
|
|
93
|
+
"""
|
|
94
|
+
exposure_conditions_tuple = tuple(sorted(set(self.key_to_petal_dict.values())))
|
|
95
|
+
return exposure_conditions_tuple
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class CryonirspPickyDarkExposureConditionsBud(Stem):
|
|
99
|
+
"""Parse exposure conditions tuples to ensure existence of dark frames with the required exposure conditions."""
|
|
100
|
+
|
|
101
|
+
DarkTaskTestAndExposureConditions = namedtuple(
|
|
102
|
+
"DarkTaskTestAndExposureConditions", ["is_dark", "exposure_conditions"]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def __init__(self):
|
|
106
|
+
super().__init__(stem_name=CryonirspBudName.picky_dark_exposure_conditions_list.value)
|
|
107
|
+
self.metadata_key = "exposure_conditions"
|
|
108
|
+
|
|
109
|
+
def setter(self, fits_obj: CryonirspL0FitsAccess) -> tuple | Type[SpilledDirt]:
|
|
110
|
+
"""
|
|
111
|
+
Set the task exposure conditions tuple and whether it is a DARK task for this fits object.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
fits_obj
|
|
116
|
+
The input fits object
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
A tuple of a boolean indicating if the task is a dark task, and the exposure conditions for this fits object
|
|
120
|
+
"""
|
|
121
|
+
if fits_obj.ip_task_type.upper() == TaskName.dark.value:
|
|
122
|
+
return self.DarkTaskTestAndExposureConditions(
|
|
123
|
+
is_dark=True, exposure_conditions=getattr(fits_obj, self.metadata_key)
|
|
124
|
+
)
|
|
125
|
+
if fits_obj.ip_task_type.upper() not in _DARK_AND_POLCAL_TASK_TYPES:
|
|
126
|
+
return self.DarkTaskTestAndExposureConditions(
|
|
127
|
+
is_dark=False, exposure_conditions=getattr(fits_obj, self.metadata_key)
|
|
128
|
+
)
|
|
129
|
+
# Polcal falls through
|
|
130
|
+
return SpilledDirt
|
|
131
|
+
|
|
132
|
+
def getter(self, key: Hashable) -> Type[Thorn]:
|
|
133
|
+
"""
|
|
134
|
+
Parse all exposure conditions and raise an error if any non-dark exposure condition is missing from the set of dark exposure conditions.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
key
|
|
139
|
+
The input key
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
Thorn
|
|
144
|
+
"""
|
|
145
|
+
dark_task_test_and_exposure_conditions_set = set(self.key_to_petal_dict.values())
|
|
146
|
+
dark_exposure_conditions_set = {
|
|
147
|
+
item.exposure_conditions
|
|
148
|
+
for item in dark_task_test_and_exposure_conditions_set
|
|
149
|
+
if item.is_dark
|
|
150
|
+
}
|
|
151
|
+
other_exposure_conditions_set = {
|
|
152
|
+
item.exposure_conditions
|
|
153
|
+
for item in dark_task_test_and_exposure_conditions_set
|
|
154
|
+
if not item.is_dark
|
|
155
|
+
}
|
|
156
|
+
other_exposure_conditions_missing_from_dark_exposure_conditions = (
|
|
157
|
+
other_exposure_conditions_set - dark_exposure_conditions_set
|
|
158
|
+
)
|
|
159
|
+
if other_exposure_conditions_missing_from_dark_exposure_conditions:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Exposure conditions required in the set of dark frames not found. Missing conditions = {other_exposure_conditions_missing_from_dark_exposure_conditions}"
|
|
162
|
+
)
|
|
163
|
+
return Thorn
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Stems for organizing files into separate dsps repeats."""
|
|
2
|
+
from dkist_processing_cryonirsp.models.constants import CryonirspBudName
|
|
3
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspStemName
|
|
4
|
+
from dkist_processing_cryonirsp.parsers.scan_step import MapScanStepStemBase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MapScanFlower(MapScanStepStemBase):
|
|
8
|
+
"""Flower for computing and assigning map scan numbers."""
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__(stem_name=CryonirspStemName.map_scan.value)
|
|
12
|
+
|
|
13
|
+
def getter(self, key: str) -> int:
|
|
14
|
+
"""Compute the map scan number for a single frame.
|
|
15
|
+
|
|
16
|
+
The frame implies a SingleScanStep. That object is then compared to the sorted list of objects for a single
|
|
17
|
+
(raster_step, meas_num, modstate, sub_repeat) tuple. The location within that sorted list is the map scan number.
|
|
18
|
+
"""
|
|
19
|
+
return self.get_map_scan_for_key(key)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NumMapScansBud(MapScanStepStemBase):
|
|
23
|
+
"""
|
|
24
|
+
Bud for determining the total number of dsps repeats.
|
|
25
|
+
|
|
26
|
+
Also checks that all scan steps have the same number of dsps repeats.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
super().__init__(stem_name=CryonirspBudName.num_map_scans.value)
|
|
31
|
+
|
|
32
|
+
def getter(self, key: str) -> int:
|
|
33
|
+
"""
|
|
34
|
+
Compute the total number of dsps repeats.
|
|
35
|
+
|
|
36
|
+
The number of map_scans for every scan step are calculated and if a map_scan is incomplete,
|
|
37
|
+
it will not be included.
|
|
38
|
+
Assumes the incomplete map_scan is always the last one due to summit abort or cancellation.
|
|
39
|
+
"""
|
|
40
|
+
return self.number_of_complete_map_scans
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Copies of UniqueBud and SingleValueSingleKeyFlower from common that only activate if the frames are "observe" task."""
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
5
|
+
from dkist_processing_common.parsers.single_value_single_key_flower import (
|
|
6
|
+
SingleValueSingleKeyFlower,
|
|
7
|
+
)
|
|
8
|
+
from dkist_processing_common.parsers.unique_bud import UniqueBud
|
|
9
|
+
|
|
10
|
+
from dkist_processing_cryonirsp.models.constants import CryonirspBudName
|
|
11
|
+
from dkist_processing_cryonirsp.models.tags import CryonirspStemName
|
|
12
|
+
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NumberOfMeasurementsBud(UniqueBud):
|
|
16
|
+
"""Bud for finding the total number of measurements per scan step."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.metadata_key = "num_meas"
|
|
20
|
+
super().__init__(
|
|
21
|
+
constant_name=CryonirspBudName.num_meas.value, metadata_key=self.metadata_key
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def setter(self, fits_obj: CryonirspL0FitsAccess) -> Type[SpilledDirt] | int:
|
|
25
|
+
"""
|
|
26
|
+
Setter for the bud.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
fits_obj:
|
|
31
|
+
A single FitsAccess object
|
|
32
|
+
"""
|
|
33
|
+
if fits_obj.ip_task_type != "observe":
|
|
34
|
+
return SpilledDirt
|
|
35
|
+
return getattr(fits_obj, self.metadata_key)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class MeasurementNumberFlower(SingleValueSingleKeyFlower):
|
|
39
|
+
"""Flower for a measurement number."""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
super().__init__(tag_stem_name=CryonirspStemName.meas_num.value, metadata_key="meas_num")
|
|
43
|
+
|
|
44
|
+
def setter(self, fits_obj: CryonirspL0FitsAccess) -> Type[SpilledDirt] | int:
|
|
45
|
+
"""
|
|
46
|
+
Setter for a flower.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
fits_obj:
|
|
51
|
+
A single FitsAccess object
|
|
52
|
+
"""
|
|
53
|
+
if fits_obj.ip_task_type != "observe":
|
|
54
|
+
return SpilledDirt
|
|
55
|
+
return super().setter(fits_obj)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Copy of SingleValueSingleKeyFlower from common that only activates if the frames are "observe" task."""
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
5
|
+
from dkist_processing_common.models.tags import StemName
|
|
6
|
+
from dkist_processing_common.parsers.single_value_single_key_flower import (
|
|
7
|
+
SingleValueSingleKeyFlower,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspL0FitsAccess
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ModstateNumberFlower(SingleValueSingleKeyFlower):
|
|
14
|
+
"""Flower for a modstate number."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
super().__init__(tag_stem_name=StemName.modstate.value, metadata_key="modulator_state")
|
|
18
|
+
|
|
19
|
+
def setter(self, fits_obj: CryonirspL0FitsAccess) -> Type[SpilledDirt] | int:
|
|
20
|
+
"""
|
|
21
|
+
Setter for a flower.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
fits_obj:
|
|
26
|
+
A single FitsAccess object
|
|
27
|
+
"""
|
|
28
|
+
# Some intensity data incorrectly has modulator state = 0
|
|
29
|
+
if getattr(fits_obj, self.metadata_key) == 0:
|
|
30
|
+
return 1
|
|
31
|
+
return super().setter(fits_obj)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""PickyBud to implement early parsing of Optical Density Filter Names."""
|
|
2
|
+
from typing import Hashable
|
|
3
|
+
|
|
4
|
+
from dkist_processing_common.models.flower_pot import Stem
|
|
5
|
+
from dkist_processing_common.models.flower_pot import Thorn
|
|
6
|
+
|
|
7
|
+
from dkist_processing_cryonirsp.models.exposure_conditions import AllowableOpticalDensityFilterNames
|
|
8
|
+
from dkist_processing_cryonirsp.parsers.cryonirsp_l0_fits_access import CryonirspRampFitsAccess
|
|
9
|
+
|
|
10
|
+
ALLOWABLE_OPTICAL_DENSITY_FILTERS = {f for f in AllowableOpticalDensityFilterNames}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OpticalDensityFiltersPickyBud(Stem):
|
|
14
|
+
"""PickyBud to implement early parsing of Optical Density Filter Names."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
super().__init__(stem_name="OpticalDensityFilterNamePickyBud")
|
|
18
|
+
|
|
19
|
+
def setter(self, fits_obj: CryonirspRampFitsAccess):
|
|
20
|
+
"""
|
|
21
|
+
Set the optical density filter name for this fits object.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
fits_obj
|
|
26
|
+
The input fits obj
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
The optical density filter name associated with this fits object
|
|
31
|
+
"""
|
|
32
|
+
return fits_obj.filter_name.upper()
|
|
33
|
+
|
|
34
|
+
def getter(self, key: Hashable) -> Thorn:
|
|
35
|
+
"""Return a Thorn for valid names or raise an exception for bad names."""
|
|
36
|
+
filter_names = set(self.key_to_petal_dict.values())
|
|
37
|
+
bad_filter_names = filter_names.difference(ALLOWABLE_OPTICAL_DENSITY_FILTERS)
|
|
38
|
+
if bad_filter_names:
|
|
39
|
+
raise ValueError(f"Unknown Optical Density Filter Name(s): {bad_filter_names = }")
|
|
40
|
+
return Thorn
|