dkist-processing-dlnirsp 0.32.2__py3-none-any.whl → 0.32.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.
Files changed (21) hide show
  1. dkist_processing_dlnirsp/models/fits_access.py +32 -0
  2. dkist_processing_dlnirsp/parsers/dlnirsp_l0_fits_access.py +35 -23
  3. dkist_processing_dlnirsp/parsers/task.py +2 -6
  4. dkist_processing_dlnirsp/parsers/time.py +2 -1
  5. dkist_processing_dlnirsp/parsers/wavelength.py +5 -1
  6. dkist_processing_dlnirsp/tasks/linearity_correction.py +55 -24
  7. dkist_processing_dlnirsp/tasks/parse.py +21 -12
  8. dkist_processing_dlnirsp/tasks/science.py +7 -5
  9. dkist_processing_dlnirsp/tasks/write_l1.py +5 -4
  10. dkist_processing_dlnirsp/tests/conftest.py +13 -5
  11. dkist_processing_dlnirsp/tests/local_trial_workflows/local_trial_dev_mockers.py +18 -16
  12. dkist_processing_dlnirsp/tests/local_trial_workflows/local_trial_helpers.py +2 -1
  13. dkist_processing_dlnirsp/tests/test_dlnirsp_fits_access.py +56 -3
  14. dkist_processing_dlnirsp/tests/test_linearity_correction.py +120 -21
  15. dkist_processing_dlnirsp/tests/test_parse.py +3 -2
  16. dkist_processing_dlnirsp/tests/test_science.py +7 -6
  17. {dkist_processing_dlnirsp-0.32.2.dist-info → dkist_processing_dlnirsp-0.32.4.dist-info}/METADATA +42 -41
  18. {dkist_processing_dlnirsp-0.32.2.dist-info → dkist_processing_dlnirsp-0.32.4.dist-info}/RECORD +21 -20
  19. {dkist_processing_dlnirsp-0.32.2.dist-info → dkist_processing_dlnirsp-0.32.4.dist-info}/WHEEL +0 -0
  20. {dkist_processing_dlnirsp-0.32.2.dist-info → dkist_processing_dlnirsp-0.32.4.dist-info}/entry_points.txt +0 -0
  21. {dkist_processing_dlnirsp-0.32.2.dist-info → dkist_processing_dlnirsp-0.32.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,32 @@
1
+ """DLNIRSP control of FITS key names and values."""
2
+
3
+ from enum import StrEnum
4
+
5
+ from dkist_processing_common.models.fits_access import MetadataKey
6
+
7
+
8
+ class DlnirspMetadataKey(StrEnum):
9
+ """Controlled list of names for FITS metadata header keys."""
10
+
11
+ crpix_1 = "CRPIX1"
12
+ crpix_2 = "CRPIX2"
13
+ modulator_spin_mode = "DLMOD"
14
+ camera_readout_mode = "DLCAMSMD"
15
+ num_frames_in_ramp = "DLCAMNS"
16
+ current_frame_in_ramp = "DLCAMCUR"
17
+ arm_id = "DLARMID"
18
+ camera_sample_sequence = "DLCAMSSQ"
19
+ polarimeter_mode = "DLPOLMD"
20
+ number_of_modulator_states = "DLNUMST"
21
+ modulator_state = "DLSTNUM"
22
+ num_mosaic_repeats = "DLMOSNRP"
23
+ mosaic_num = "DLCURMOS"
24
+ num_X_tiles = "DLNSSTPX"
25
+ X_tile_num = "DLCSTPX"
26
+ num_Y_tiles = "DLNSSTPY"
27
+ Y_tile_num = "DLCSTPY"
28
+ num_dither_steps = "DLDMODE"
29
+ dither_step = "DLCURSTP"
30
+ arm_position_mm = "DLARMPS"
31
+ grating_position_deg = "DLGRTAN"
32
+ grating_constant_inverse_mm = "DLGRTCN"
@@ -3,6 +3,8 @@
3
3
  from astropy.io import fits
4
4
  from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
5
5
 
6
+ from dkist_processing_dlnirsp.models.fits_access import DlnirspMetadataKey
7
+
6
8
 
7
9
  class DlnirspRampFitsAccess(L0FitsAccess):
8
10
  """
@@ -30,12 +32,18 @@ class DlnirspRampFitsAccess(L0FitsAccess):
30
32
  ):
31
33
  super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
32
34
 
33
- self.modulator_spin_mode: str = self.header["DLMOD"]
34
- self.camera_readout_mode: str = self.header.get("DLCAMSMD", "DEFAULT_VISIBLE_CAMERA")
35
- self.num_frames_in_ramp: int = self.header.get("DLCAMNS", -99)
36
- self.current_frame_in_ramp: int = self.header.get("DLCAMCUR", -88)
37
- self.arm_id: str = self.header["DLARMID"]
38
- self.camera_sample_sequence: str = self.header.get("DLCAMSSQ", "VISIBLE_CAMERA_SEQUENCE")
35
+ self.modulator_spin_mode: str = self.header[DlnirspMetadataKey.modulator_spin_mode]
36
+ self.camera_readout_mode: str = self.header.get(
37
+ DlnirspMetadataKey.camera_readout_mode, "DEFAULT_VISIBLE_CAMERA"
38
+ )
39
+ self.num_frames_in_ramp: int = self.header.get(DlnirspMetadataKey.num_frames_in_ramp, -99)
40
+ self.current_frame_in_ramp: int = self.header.get(
41
+ DlnirspMetadataKey.current_frame_in_ramp, -88
42
+ )
43
+ self.arm_id: str = self.header[DlnirspMetadataKey.arm_id]
44
+ self.camera_sample_sequence: str = self.header.get(
45
+ DlnirspMetadataKey.camera_sample_sequence, "VISIBLE_CAMERA_SEQUENCE"
46
+ )
39
47
 
40
48
 
41
49
  class DlnirspL0FitsAccess(L0FitsAccess):
@@ -64,24 +72,28 @@ class DlnirspL0FitsAccess(L0FitsAccess):
64
72
  ):
65
73
  super().__init__(hdu=hdu, name=name, auto_squeeze=auto_squeeze)
66
74
 
67
- self.crpix_1: float = self.header["CRPIX1"]
68
- self.crpix_2: float = self.header["CRPIX2"]
69
- self.arm_id: str = self.header["DLARMID"]
70
- self.polarimeter_mode: str = self.header["DLPOLMD"]
71
- self.number_of_modulator_states: int = self.header["DLNUMST"]
72
- self.modulator_state: int = self.header["DLSTNUM"]
73
- self.num_mosaic_repeats: int = self.header["DLMOSNRP"]
74
- self.mosaic_num: int = self.header["DLCURMOS"]
75
- self.num_X_tiles: int = self.header["DLNSSTPX"]
76
- self.X_tile_num: int = self.header["DLCSTPX"]
77
- self.num_Y_tiles: int = self.header["DLNSSTPY"]
78
- self.Y_tile_num: int = self.header["DLCSTPY"]
75
+ self.crpix_1: float = self.header[DlnirspMetadataKey.crpix_1]
76
+ self.crpix_2: float = self.header[DlnirspMetadataKey.crpix_2]
77
+ self.arm_id: str = self.header[DlnirspMetadataKey.arm_id]
78
+ self.polarimeter_mode: str = self.header[DlnirspMetadataKey.polarimeter_mode]
79
+ self.number_of_modulator_states: int = self.header[
80
+ DlnirspMetadataKey.number_of_modulator_states
81
+ ]
82
+ self.modulator_state: int = self.header[DlnirspMetadataKey.modulator_state]
83
+ self.num_mosaic_repeats: int = self.header[DlnirspMetadataKey.num_mosaic_repeats]
84
+ self.mosaic_num: int = self.header[DlnirspMetadataKey.mosaic_num]
85
+ self.num_X_tiles: int = self.header[DlnirspMetadataKey.num_X_tiles]
86
+ self.X_tile_num: int = self.header[DlnirspMetadataKey.X_tile_num]
87
+ self.num_Y_tiles: int = self.header[DlnirspMetadataKey.num_Y_tiles]
88
+ self.Y_tile_num: int = self.header[DlnirspMetadataKey.Y_tile_num]
79
89
  # DLDMODE is a bool in the header; the number of dither steps is either 1 or 2, corresponding to dither
80
90
  # mode being True or False, respectively
81
- self.num_dither_steps: int = int(self.header["DLDMODE"]) + 1
91
+ self.num_dither_steps: int = int(self.header[DlnirspMetadataKey.num_dither_steps]) + 1
82
92
  # Same with DLCURSTP. We'll index the dither loop at 0 so `False` is the first step and `True` is the second
83
93
  # Use `get` because this key only exists if DLDMODE is `True`
84
- self.dither_step: int = int(self.header.get("DLCURSTP", False))
85
- self.arm_position_mm: float = self.header["DLARMPS"]
86
- self.grating_position_deg: float = self.header["DLGRTAN"]
87
- self.grating_constant_inverse_mm: float = self.header["DLGRTCN"]
94
+ self.dither_step: int = int(self.header.get(DlnirspMetadataKey.dither_step, False))
95
+ self.arm_position_mm: float = self.header[DlnirspMetadataKey.arm_position_mm]
96
+ self.grating_position_deg: float = self.header[DlnirspMetadataKey.grating_position_deg]
97
+ self.grating_constant_inverse_mm: float = self.header[
98
+ DlnirspMetadataKey.grating_constant_inverse_mm
99
+ ]
@@ -1,15 +1,11 @@
1
1
  """Custom parsers to identify task sub-groupings not captured by a single header key."""
2
2
 
3
- from typing import Callable
4
-
5
- from dkist_processing_common.models.flower_pot import SpilledDirt
3
+ from dkist_processing_common.models.fits_access import MetadataKey
6
4
  from dkist_processing_common.models.tags import StemName
7
5
  from dkist_processing_common.models.task_name import TaskName
8
- from dkist_processing_common.parsers.near_bud import NearFloatBud
9
6
  from dkist_processing_common.parsers.single_value_single_key_flower import (
10
7
  SingleValueSingleKeyFlower,
11
8
  )
12
- from dkist_processing_common.parsers.unique_bud import UniqueBud
13
9
 
14
10
  from dkist_processing_dlnirsp.parsers.dlnirsp_l0_fits_access import DlnirspL0FitsAccess
15
11
 
@@ -41,7 +37,7 @@ class DlnirspTaskTypeFlower(SingleValueSingleKeyFlower):
41
37
  """Flower to find the DLNIRSP task type."""
42
38
 
43
39
  def __init__(self):
44
- super().__init__(tag_stem_name=StemName.task.value, metadata_key="ip_task_type")
40
+ super().__init__(tag_stem_name=StemName.task.value, metadata_key=MetadataKey.ip_task_type)
45
41
 
46
42
  def setter(self, fits_obj: DlnirspL0FitsAccess):
47
43
  """
@@ -2,6 +2,7 @@
2
2
 
3
3
  from typing import Hashable
4
4
 
5
+ from dkist_processing_common.models.fits_access import MetadataKey
5
6
  from dkist_processing_common.models.flower_pot import Stem
6
7
  from dkist_processing_common.models.task_name import TaskName
7
8
  from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
@@ -17,7 +18,7 @@ class DLnirspSolarGainIpStartTimeBud(TaskUniqueBud):
17
18
  def __init__(self):
18
19
  super().__init__(
19
20
  constant_name=DlnirspBudName.solar_gain_ip_start_time.value,
20
- metadata_key="ip_start_time",
21
+ metadata_key=MetadataKey.ip_start_time,
21
22
  ip_task_types=TaskName.solar_gain.value,
22
23
  task_type_parsing_function=parse_header_ip_task,
23
24
  )
@@ -1,5 +1,6 @@
1
1
  """Bud to get the wavelength."""
2
2
 
3
+ from dkist_processing_common.models.fits_access import MetadataKey
3
4
  from dkist_processing_common.models.flower_pot import SpilledDirt
4
5
  from dkist_processing_common.parsers.unique_bud import UniqueBud
5
6
 
@@ -11,7 +12,10 @@ class ObserveWavelengthBud(UniqueBud):
11
12
  """Bud to find the wavelength."""
12
13
 
13
14
  def __init__(self):
14
- super().__init__(constant_name=DlnirspBudName.wavelength.value, metadata_key="wavelength")
15
+ super().__init__(
16
+ constant_name=DlnirspBudName.wavelength.value,
17
+ metadata_key=MetadataKey.wavelength,
18
+ )
15
19
 
16
20
  def setter(self, fits_obj: DlnirspL0FitsAccess):
17
21
  """
@@ -28,14 +28,32 @@ class LinearityCorrection(DlnirspTaskBase):
28
28
 
29
29
  record_provenance = True
30
30
 
31
- camera_sequence_regex: re.Pattern = re.compile(r"(\d*)line,(\d*)read")
31
+ valid_camera_sequence_regex: re.Pattern = re.compile(
32
+ r"^(\d*subframe)?(?(1)|(?:\d*line,\d*read,?)+)(?:,\d*line)?$"
33
+ )
32
34
  """
33
- regex pattern used to parse line-read-line values for a single coadd.
35
+ regex pattern that defines all valid camera-sample sequences.
36
+
37
+ It must start with either "Xline,Yread", which can repeat any number of times, or "Xsubframe" which must be by itself.
38
+ Either of these sequences may be padded with ",Zline" reset frames.
39
+ """
40
+
41
+ uptheramp_coadd_regex: re.Pattern = re.compile(r"(\d*)line,(\d*)read")
42
+ """
43
+ regex pattern used to parse line-read-line values for a single UpTheRamp coadd.
34
44
 
35
45
  This is where we decide that camera sequences are one or more coadd sequences, where each coadd sequence is
36
46
  "Xline,Yread". The total sequence may be padded with ",Zline" reset frames, which are not captured by this regex.
37
47
  """
38
48
 
49
+ subframe_sequence_regex: re.Pattern = re.compile(r"(\d*)subframe")
50
+ """
51
+ regex pattern used to parse coadds in SubFrame mode
52
+
53
+ Simply looks for "Xsubframe" and captures the "X". The sequence may be padded with ",Zline" reset frames, which are
54
+ not captured by this regex.
55
+ """
56
+
39
57
  def run(self):
40
58
  """
41
59
  Linearize IR camera frames or tag VIS camera frames as LINEARIZED.
@@ -110,17 +128,16 @@ class LinearityCorrection(DlnirspTaskBase):
110
128
  )
111
129
  num_coadds = len(coadd_sequence_nums_list)
112
130
 
113
- line_read_line_indices = coadd_sequence_nums_list[0]
114
- num_bias, num_read = line_read_line_indices[:2]
115
-
116
- ndr_per_coadd = num_bias + num_read
117
-
118
131
  # In `is_ramp_valid` we already confirmed that all NDRs have the same values and that they are one of the
119
132
  # expected values
120
133
  camera_readout_mode = ramp_obj_list[0].camera_readout_mode
121
134
  modulator_spin_mode = ramp_obj_list[0].modulator_spin_mode
122
135
  match camera_readout_mode:
123
136
  case "UpTheRamp":
137
+ line_read_line_indices = coadd_sequence_nums_list[0]
138
+ num_bias, num_read = line_read_line_indices[:2]
139
+ ndr_per_coadd = num_bias + num_read
140
+
124
141
  match modulator_spin_mode:
125
142
  case "Continuous":
126
143
  linearization_func = partial(
@@ -136,6 +153,11 @@ class LinearityCorrection(DlnirspTaskBase):
136
153
  )
137
154
 
138
155
  case "SubFrame":
156
+ # `self.valid_camera_sequence_regex`, along with `parse_camera_sample_sequence`, provides assurance that
157
+ # by the time we get here these assumptions are valid
158
+ num_bias = 0
159
+ num_read = 1
160
+ ndr_per_coadd = 1
139
161
  linearization_func = self.linearize_subframe_coadd
140
162
 
141
163
  coadd_stack = np.zeros((num_coadds, *ramp_obj_list[0].data.shape))
@@ -291,16 +313,25 @@ class LinearityCorrection(DlnirspTaskBase):
291
313
  "3line,45read,3line,45read,2line"
292
314
  `[[3, 45], [3, 45]]`
293
315
 
316
+ "4subframe,89line"
317
+ `[[1], [1], [1], [1]]`
318
+
294
319
  Returns
295
320
  -------
296
- A list of lists. Top-level list contains an item for each coadd. These items are themselves lists of
297
- length 2. The numbers in these inner lists correspond to the number of bias and read frames in that coadd,
298
- respectively.
321
+ A list of lists. Top-level list contains an item for each coadd. In UpTheRamp mode these items are themselves
322
+ lists of length 2. The numbers in these inner lists correspond to the number of bias and read frames in that coadd,
323
+ respectively. In SubFrame mode the inner lists will always be length 1 and should be equal to `[1]`.
299
324
  """
300
- coadd_matches = self.camera_sequence_regex.findall(camera_sample_sequence)
301
- coadd_sequence_numbers = [
302
- [int(num) for num in coadd_match] for coadd_match in coadd_matches
303
- ]
325
+ if "subframe" in camera_sample_sequence:
326
+ coadd_matches = self.subframe_sequence_regex.findall(camera_sample_sequence)
327
+ # `is_ramp_valid` ensures we only have a single match here
328
+ num_coadd = int(coadd_matches[0])
329
+ coadd_sequence_numbers = [[1]] * num_coadd
330
+ else:
331
+ coadd_matches = self.uptheramp_coadd_regex.findall(camera_sample_sequence)
332
+ coadd_sequence_numbers = [
333
+ [int(num) for num in coadd_match] for coadd_match in coadd_matches
334
+ ]
304
335
 
305
336
  return coadd_sequence_numbers
306
337
 
@@ -310,15 +341,15 @@ class LinearityCorrection(DlnirspTaskBase):
310
341
 
311
342
  Current validity checks are:
312
343
 
313
- 1. All frames in the ramp have the same value for NUM_FRAMES_IN_RAMP
314
- 2. All frames in the ramp have the same value for CAMERA_READOUT_MODE
315
- 3. All frames in the ramp have the same value for MODULATOR_SPIN_MODE
316
- 4. The CAMERA_READOUT and MODULATOR_SPIN modes have expected values
317
- 5. The value of NUM_FRAMES_IN_RAMP equals the length of actual frames found
318
- 6. All frames in the ramp have the same value for CAMERA_SAMPLE_SEQUENCE
319
- 7. The camera sample sequence has the expected form ('(,?\d*line,\d*read)*(,\d*line)?')
320
- 8. All coadds in the ramp have the same camera sample sequence
321
- 9. The ramp length is equal to the expected length from the camera sample sequence
344
+ #. All frames in the ramp have the same value for NUM_FRAMES_IN_RAMP
345
+ #. All frames in the ramp have the same value for CAMERA_READOUT_MODE
346
+ #. All frames in the ramp have the same value for MODULATOR_SPIN_MODE
347
+ #. The CAMERA_READOUT and MODULATOR_SPIN modes have expected values
348
+ #. The value of NUM_FRAMES_IN_RAMP equals the length of actual frames found
349
+ #. All frames in the ramp have the same value for CAMERA_SAMPLE_SEQUENCE
350
+ #. The camera sample sequence has the expected form (`valid_camera_sequence_regex`)
351
+ #. All coadds in the ramp have the same camera sample sequence
352
+ #. The ramp length is equal to the expected length from the camera sample sequence
322
353
 
323
354
  If a ramp is not valid then the reason is logged and `False` is returned.
324
355
  """
@@ -382,7 +413,7 @@ class LinearityCorrection(DlnirspTaskBase):
382
413
  return False
383
414
 
384
415
  camera_sample_sequence = camera_sample_sequence_set.pop()
385
- if re.search(r"\d*line,\d*line", camera_sample_sequence):
416
+ if not self.valid_camera_sequence_regex.search(camera_sample_sequence):
386
417
  logger.info(
387
418
  f"Malformed camera sample sequence: '{camera_sample_sequence}'. "
388
419
  f"{common_status_str}"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from typing import TypeVar
4
4
 
5
+ from dkist_processing_common.models.fits_access import MetadataKey
5
6
  from dkist_processing_common.models.flower_pot import Stem
6
7
  from dkist_processing_common.models.task_name import TaskName
7
8
  from dkist_processing_common.parsers.cs_step import CSStepFlower
@@ -22,6 +23,7 @@ from dkist_processing_common.tasks import default_constant_bud_factory
22
23
  from dkist_processing_common.tasks import default_tag_flower_factory
23
24
 
24
25
  from dkist_processing_dlnirsp.models.constants import DlnirspBudName
26
+ from dkist_processing_dlnirsp.models.fits_access import DlnirspMetadataKey
25
27
  from dkist_processing_dlnirsp.models.parameters import DlnirspParsingParameters
26
28
  from dkist_processing_dlnirsp.models.tags import DlnirspStemName
27
29
  from dkist_processing_dlnirsp.models.tags import DlnirspTag
@@ -72,7 +74,9 @@ class ParseL0DlnirspRampData(ParseDataBase):
72
74
  # Time Obs is the unique identifier for each ramp in the data set
73
75
  DlnirspTimeObsBud(),
74
76
  # This is used to determine whether we need to do any linearity correction at all.
75
- UniqueBud(constant_name=DlnirspBudName.arm_id.value, metadata_key="arm_id"),
77
+ UniqueBud(
78
+ constant_name=DlnirspBudName.arm_id.value, metadata_key=DlnirspMetadataKey.arm_id
79
+ ),
76
80
  ]
77
81
 
78
82
  @property
@@ -81,12 +85,12 @@ class ParseL0DlnirspRampData(ParseDataBase):
81
85
  return [
82
86
  SingleValueSingleKeyFlower(
83
87
  tag_stem_name=DlnirspStemName.current_frame_in_ramp.value,
84
- metadata_key="current_frame_in_ramp",
88
+ metadata_key=DlnirspMetadataKey.current_frame_in_ramp,
85
89
  ),
86
90
  # time_obs is a unique identifier for all raw frames in a single ramp
87
91
  SingleValueSingleKeyFlower(
88
92
  tag_stem_name=DlnirspStemName.time_obs.value,
89
- metadata_key="time_obs",
93
+ metadata_key=MetadataKey.time_obs,
90
94
  ),
91
95
  ]
92
96
 
@@ -144,12 +148,13 @@ class ParseL0DlnirspLinearizedData(ParseDataBase):
144
148
  DLnirspSolarGainIpStartTimeBud(),
145
149
  NumCSStepBud(max_cs_step_time_sec=self.parameters.max_cs_step_time_sec),
146
150
  UniqueBud(
147
- constant_name=DlnirspBudName.polarimeter_mode.value, metadata_key="polarimeter_mode"
151
+ constant_name=DlnirspBudName.polarimeter_mode.value,
152
+ metadata_key=DlnirspMetadataKey.polarimeter_mode,
148
153
  ),
149
154
  RetarderNameBud(),
150
155
  UniqueBud(
151
156
  constant_name=DlnirspBudName.num_modstates.value,
152
- metadata_key="number_of_modulator_states",
157
+ metadata_key=DlnirspMetadataKey.number_of_modulator_states,
153
158
  ),
154
159
  NumMosaicRepeatsBud(
155
160
  crpix_correction_method=self.parameters.wcs_crpix_correction_method,
@@ -189,20 +194,20 @@ class ParseL0DlnirspLinearizedData(ParseDataBase):
189
194
  ),
190
195
  TaskNearFloatBud(
191
196
  constant_name=DlnirspBudName.arm_position_mm.value,
192
- metadata_key="arm_position_mm",
197
+ metadata_key=DlnirspMetadataKey.arm_position_mm,
193
198
  ip_task_types=[TaskName.solar_gain.value, TaskName.observe.value],
194
199
  tolerance=0.01,
195
200
  task_type_parsing_function=parse_header_ip_task,
196
201
  ),
197
202
  TaskUniqueBud(
198
203
  constant_name=DlnirspBudName.grating_constant_inverse_mm.value,
199
- metadata_key="grating_constant_inverse_mm",
204
+ metadata_key=DlnirspMetadataKey.grating_constant_inverse_mm,
200
205
  ip_task_types=[TaskName.solar_gain.value, TaskName.observe.value],
201
206
  task_type_parsing_function=parse_header_ip_task,
202
207
  ),
203
208
  TaskNearFloatBud(
204
209
  constant_name=DlnirspBudName.grating_position_deg.value,
205
- metadata_key="grating_position_deg",
210
+ metadata_key=DlnirspMetadataKey.grating_position_deg,
206
211
  ip_task_types=[TaskName.solar_gain.value, TaskName.observe.value],
207
212
  tolerance=0.01,
208
213
  task_type_parsing_function=parse_header_ip_task,
@@ -216,14 +221,17 @@ class ParseL0DlnirspLinearizedData(ParseDataBase):
216
221
  DlnirspTaskTypeFlower(),
217
222
  PolcalTaskFlower(),
218
223
  SingleValueSingleKeyFlower(
219
- tag_stem_name=DlnirspStemName.arm_id.value, metadata_key="arm_id"
224
+ tag_stem_name=DlnirspStemName.arm_id.value,
225
+ metadata_key=DlnirspMetadataKey.arm_id,
220
226
  ),
221
227
  ExposureTimeFlower(),
222
228
  SingleValueSingleKeyFlower(
223
- tag_stem_name=DlnirspStemName.modstate.value, metadata_key="modulator_state"
229
+ tag_stem_name=DlnirspStemName.modstate.value,
230
+ metadata_key=DlnirspMetadataKey.modulator_state,
224
231
  ),
225
232
  SingleValueSingleKeyFlower(
226
- tag_stem_name=DlnirspStemName.mosaic_num.value, metadata_key="mosaic_num"
233
+ tag_stem_name=DlnirspStemName.mosaic_num.value,
234
+ metadata_key=DlnirspMetadataKey.mosaic_num,
227
235
  ),
228
236
  MosaicStepXFlower(
229
237
  crpix_correction_method=self.parameters.wcs_crpix_correction_method,
@@ -234,7 +242,8 @@ class ParseL0DlnirspLinearizedData(ParseDataBase):
234
242
  bin_crpix_to_multiple_of=self.parameters.parse_bin_crpix_to_multiple_of,
235
243
  ),
236
244
  SingleValueSingleKeyFlower(
237
- tag_stem_name=DlnirspStemName.dither_step.value, metadata_key="dither_step"
245
+ tag_stem_name=DlnirspStemName.dither_step.value,
246
+ metadata_key=DlnirspMetadataKey.dither_step,
238
247
  ),
239
248
  CSStepFlower(max_cs_step_time_sec=self.parameters.max_cs_step_time_sec),
240
249
  ]
@@ -18,6 +18,7 @@ from dkist_processing_common.codecs.fits import fits_access_decoder
18
18
  from dkist_processing_common.codecs.fits import fits_array_decoder
19
19
  from dkist_processing_common.codecs.fits import fits_array_encoder
20
20
  from dkist_processing_common.codecs.fits import fits_hdulist_encoder
21
+ from dkist_processing_common.models.fits_access import MetadataKey
21
22
  from dkist_processing_common.models.tags import EXP_TIME_ROUND_DIGITS
22
23
  from dkist_processing_common.models.task_name import TaskName
23
24
  from dkist_processing_common.tasks.mixin.quality import QualityMixin
@@ -29,6 +30,7 @@ from dkist_processing_pac.optics.telescope import Telescope
29
30
  from dkist_service_configuration.logging import logger
30
31
  from scipy.spatial import Delaunay
31
32
 
33
+ from dkist_processing_dlnirsp.models.fits_access import DlnirspMetadataKey
32
34
  from dkist_processing_dlnirsp.models.tags import DlnirspTag
33
35
  from dkist_processing_dlnirsp.parsers.dlnirsp_l0_fits_access import DlnirspL0FitsAccess
34
36
  from dkist_processing_dlnirsp.parsers.wcs_corrections import correct_crpix_values
@@ -738,7 +740,7 @@ class ScienceCalibration(
738
740
  date_end = (Time(sorted_obj_list[-1].time_obs) + exp_time).isot
739
741
 
740
742
  header = sorted_obj_list[0].header
741
- header["DATE-BEG"] = date_beg
743
+ header[MetadataKey.time_obs] = date_beg
742
744
  header["DATE-END"] = date_end
743
745
 
744
746
  return header
@@ -773,13 +775,13 @@ class ScienceCalibration(
773
775
 
774
776
  crpix_correction_method = self.parameters.wcs_crpix_correction_method
775
777
  cached_info_logger(f"Applying CRPIX correction method '{crpix_correction_method}'")
776
- OG_crpix1 = header["CRPIX1"]
777
- OG_crpix2 = header["CRPIX2"]
778
+ OG_crpix1 = header[DlnirspMetadataKey.crpix_1]
779
+ OG_crpix2 = header[DlnirspMetadataKey.crpix_2]
778
780
 
779
781
  new_crpix1, new_crpix2 = correct_crpix_values(OG_crpix1, OG_crpix2, crpix_correction_method)
780
782
 
781
- header["CRPIX1"] = new_crpix1
782
- header["CRPIX2"] = new_crpix2
783
+ header[DlnirspMetadataKey.crpix_1] = new_crpix1
784
+ header[DlnirspMetadataKey.crpix_2] = new_crpix2
783
785
 
784
786
  return header
785
787
 
@@ -12,6 +12,7 @@ from dkist_processing_common.tasks import WriteL1Frame
12
12
  from dkist_service_configuration.logging import logger
13
13
 
14
14
  from dkist_processing_dlnirsp.models.constants import DlnirspConstants
15
+ from dkist_processing_dlnirsp.models.fits_access import DlnirspMetadataKey
15
16
  from dkist_processing_dlnirsp.models.tags import DlnirspTag
16
17
 
17
18
  cached_info_logger = cache(logger.info)
@@ -83,7 +84,7 @@ class DlnirspWriteL1Frame(WriteL1Frame):
83
84
  header[f"DPNAME{axis_index}"] = "dither step"
84
85
  header[f"DWNAME{axis_index}"] = "time"
85
86
  header[f"DUNIT{axis_index}"] = "s"
86
- header[f"DINDEX{axis_index}"] = int(header["DLCURSTP"]) + 1
87
+ header[f"DINDEX{axis_index}"] = int(header[DlnirspMetadataKey.dither_step]) + 1
87
88
 
88
89
  if self.constants.correct_for_polarization:
89
90
  cached_info_logger("Polarimetric data detected")
@@ -211,7 +212,7 @@ class DlnirspWriteL1Frame(WriteL1Frame):
211
212
  tile_as_temporal_axis = False
212
213
  temporal_axis_size = num_mosaic_repeats
213
214
  pname = "mosaic repeat number"
214
- dindex = header["DLCURMOS"]
215
+ dindex = header[DlnirspMetadataKey.mosaic_num]
215
216
 
216
217
  elif num_mosaic_repeats == 1 and (
217
218
  (num_X_tiles == 1 and num_Y_tiles == 1) or (num_X_tiles > 1 and num_Y_tiles > 1)
@@ -224,14 +225,14 @@ class DlnirspWriteL1Frame(WriteL1Frame):
224
225
  tile_as_temporal_axis = True
225
226
  temporal_axis_size = num_X_tiles
226
227
  pname = "repeat number"
227
- dindex = header["DLCSTPX"]
228
+ dindex = header[DlnirspMetadataKey.X_tile_num]
228
229
 
229
230
  elif num_mosaic_repeats == 1 and num_X_tiles == 1 and num_Y_tiles > 1:
230
231
  has_temporal_axis = True
231
232
  tile_as_temporal_axis = True
232
233
  temporal_axis_size = num_Y_tiles
233
234
  pname = "repeat number"
234
- dindex = header["DLCSTPY"]
235
+ dindex = header[DlnirspMetadataKey.Y_tile_num]
235
236
 
236
237
  else:
237
238
  raise ValueError(
@@ -436,7 +436,19 @@ class RawRampHeaders(DlnirspHeaders):
436
436
  raise ValueError(f"{array_shape = } is weird")
437
437
  array_shape = array_shape[1:]
438
438
 
439
- self.num_frames_per_coadd = num_line + num_read
439
+ match camera_readout_mode:
440
+ case "UpTheRamp":
441
+ self.num_frames_per_coadd = num_line + num_read
442
+ coadd_read_sequence = f"{num_line}line,{num_read}read"
443
+ cam_read_sequence = ",".join([coadd_read_sequence] * num_coadd)
444
+ case "SubFrame":
445
+ self.num_frames_per_coadd = 1
446
+ cam_read_sequence = f"{num_coadd}subframe"
447
+ num_line = 0
448
+ num_read = 1
449
+ case _:
450
+ raise ValueError(f"Don't know how to make data for {camera_readout_mode = }")
451
+
440
452
  num_NDR_per_ramp = self.num_frames_per_coadd * num_coadd + num_reset
441
453
  num_frames = num_ramps * num_NDR_per_ramp
442
454
  dataset_shape = (num_frames, *array_shape)
@@ -446,12 +458,8 @@ class RawRampHeaders(DlnirspHeaders):
446
458
  self.ramp_length_sec = TimeDelta(ramp_length_sec, format="sec")
447
459
  self.num_coadd = num_coadd
448
460
  self.num_line = num_line
449
- self.num_read = num_read
450
461
  self.num_reset = num_reset
451
462
 
452
- coadd_read_sequence = f"{num_line}line,{num_read}read"
453
- cam_read_sequence = ",".join([coadd_read_sequence] * num_coadd)
454
-
455
463
  if num_reset > 0:
456
464
  cam_read_sequence += f",{num_reset}line"
457
465
 
@@ -9,6 +9,7 @@ from dkist_processing_common.codecs.fits import fits_array_decoder
9
9
  from dkist_processing_common.codecs.fits import fits_array_encoder
10
10
  from dkist_processing_common.codecs.fits import fits_hdulist_encoder
11
11
  from dkist_processing_common.models.constants import BudName
12
+ from dkist_processing_common.models.fits_access import MetadataKey
12
13
  from dkist_processing_common.models.task_name import TaskName
13
14
  from dkist_processing_common.tasks import TransferTrialData
14
15
  from dkist_processing_common.tasks import WorkflowTaskBase
@@ -17,6 +18,7 @@ from dkist_processing_math.statistics import average_numpy_arrays
17
18
  from dkist_service_configuration.logging import logger
18
19
 
19
20
  from dkist_processing_dlnirsp.models.constants import DlnirspBudName
21
+ from dkist_processing_dlnirsp.models.fits_access import DlnirspMetadataKey
20
22
  from dkist_processing_dlnirsp.models.tags import DlnirspTag
21
23
  from dkist_processing_dlnirsp.tasks import DlnirspWriteL1Frame
22
24
  from dkist_processing_dlnirsp.tasks.dlnirsp_base import DlnirspTaskBase
@@ -87,7 +89,7 @@ def permissive_write_l1_task(force_intensity_only: bool):
87
89
  self, header: fits.Header, stokes: Literal["I", "Q", "U", "V"]
88
90
  ) -> fits.Header:
89
91
  if force_intensity_only:
90
- header["DLPOLMD"] = "Stokes I"
92
+ header[MetadataKey.polarimeter_mode] = "Stokes I"
91
93
 
92
94
  return super().add_dataset_headers(header=header, stokes=stokes)
93
95
 
@@ -138,11 +140,11 @@ class TagPolcasAsScience(DlnirspTaskBase):
138
140
  avg_array = average_numpy_arrays(arrays=arrays)
139
141
 
140
142
  hdul = fits.HDUList([fits.PrimaryHDU(data=avg_array, header=first_header)])
141
- hdul[0].header["DLCSTPX"] = 1
142
- hdul[0].header["DLCSTPY"] = 1
143
- hdul[0].header["DLMOSNRP"] = self.constants.num_cs_steps
144
- hdul[0].header["DLCURMOS"] = cs_step
145
- hdul[0].header["XPOSURE"] = obs_exp_time
143
+ hdul[0].header[DlnirspMetadataKey.X_tile_num] = 1
144
+ hdul[0].header[DlnirspMetadataKey.Y_tile_num] = 1
145
+ hdul[0].header[DlnirspMetadataKey.num_mosaic_repeats] = self.constants.num_cs_steps
146
+ hdul[0].header[DlnirspMetadataKey.mosaic_num] = cs_step
147
+ hdul[0].header[MetadataKey.fpa_exposure_time_ms] = obs_exp_time
146
148
 
147
149
  new_tags = [
148
150
  DlnirspTag.task_observe(),
@@ -186,11 +188,11 @@ class TagSingleSolarGainAsScience(DlnirspTaskBase):
186
188
  avg_array = average_numpy_arrays(arrays=arrays)
187
189
 
188
190
  hdul = fits.HDUList([fits.PrimaryHDU(data=avg_array, header=first_header)])
189
- hdul[0].header["DLCSTPX"] = 1
190
- hdul[0].header["DLCSTPY"] = 1
191
- hdul[0].header["DLMOSNRP"] = 1
192
- hdul[0].header["DLCURMOS"] = 0
193
- hdul[0].header["DLDMODE"] = False
191
+ hdul[0].header[DlnirspMetadataKey.X_tile_num] = 1
192
+ hdul[0].header[DlnirspMetadataKey.Y_tile_num] = 1
193
+ hdul[0].header[DlnirspMetadataKey.num_mosaic_repeats] = 1
194
+ hdul[0].header[DlnirspMetadataKey.mosaic_num] = 0
195
+ hdul[0].header[DlnirspMetadataKey.num_dither_steps] = False
194
196
 
195
197
  new_tags = [
196
198
  DlnirspTag.task_observe(),
@@ -237,11 +239,11 @@ class TagModulatedSolarGainsAsScience(DlnirspTaskBase):
237
239
  avg_array = average_numpy_arrays(arrays=arrays)
238
240
 
239
241
  hdul = fits.HDUList([fits.PrimaryHDU(data=avg_array, header=first_header)])
240
- hdul[0].header["DLCSTPX"] = 1
241
- hdul[0].header["DLCSTPY"] = 1
242
- hdul[0].header["DLMOSNRP"] = 1
243
- hdul[0].header["DLCURMOS"] = 0
244
- hdul[0].header["DLDMODE"] = False
242
+ hdul[0].header[DlnirspMetadataKey.X_tile_num] = 1
243
+ hdul[0].header[DlnirspMetadataKey.Y_tile_num] = 1
244
+ hdul[0].header[DlnirspMetadataKey.num_mosaic_repeats] = 1
245
+ hdul[0].header[DlnirspMetadataKey.mosaic_num] = 0
246
+ hdul[0].header[DlnirspMetadataKey.num_dither_steps] = False
245
247
 
246
248
  new_tags = [
247
249
  DlnirspTag.task_observe(),
@@ -20,6 +20,7 @@ from loguru import logger
20
20
 
21
21
  from dkist_processing_dlnirsp.models.constants import DlnirspBudName
22
22
  from dkist_processing_dlnirsp.models.constants import DlnirspConstants
23
+ from dkist_processing_dlnirsp.models.fits_access import DlnirspMetadataKey
23
24
  from dkist_processing_dlnirsp.models.tags import DlnirspTag
24
25
  from dkist_processing_dlnirsp.models.task_name import DlnirspTaskName
25
26
  from dkist_processing_dlnirsp.tasks.dlnirsp_base import DlnirspTaskBase
@@ -38,7 +39,7 @@ def get_camera_number(scratch_dir: Path, suffix: str = "FITS", data_ext: int = 1
38
39
  header_list = [fits.getheader(f, ext=data_ext) for f in file_list]
39
40
  table = Table(header_list)
40
41
 
41
- camera_IDs = np.unique(table["DLARMID"])
42
+ camera_IDs = np.unique(table[DlnirspMetadataKey.arm_id])
42
43
  if camera_IDs.size > 1:
43
44
  raise ValueError(f"Found more than one arm in scratch dir. Found {camera_IDs}")
44
45