dkist-processing-dlnirsp 0.32.8__py3-none-any.whl → 0.33.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. dkist_processing_dlnirsp/models/constants.py +6 -0
  2. dkist_processing_dlnirsp/models/parameters.py +33 -3
  3. dkist_processing_dlnirsp/parsers/task.py +2 -25
  4. dkist_processing_dlnirsp/parsers/time.py +2 -2
  5. dkist_processing_dlnirsp/tasks/__init__.py +1 -2
  6. dkist_processing_dlnirsp/tasks/movie.py +1121 -0
  7. dkist_processing_dlnirsp/tasks/parse.py +13 -8
  8. dkist_processing_dlnirsp/tasks/solar.py +129 -30
  9. dkist_processing_dlnirsp/tests/conftest.py +46 -7
  10. dkist_processing_dlnirsp/tests/local_trial_workflows/l0_polcals_as_science.py +21 -18
  11. dkist_processing_dlnirsp/tests/local_trial_workflows/l0_solar_gain_as_science.py +21 -18
  12. dkist_processing_dlnirsp/tests/local_trial_workflows/l0_to_l1.py +21 -18
  13. dkist_processing_dlnirsp/tests/local_trial_workflows/local_trial_dev_mockers.py +1 -1
  14. dkist_processing_dlnirsp/tests/test_dlnirsp_constants.py +2 -0
  15. dkist_processing_dlnirsp/tests/test_movie.py +141 -0
  16. dkist_processing_dlnirsp/tests/test_parameters.py +8 -0
  17. dkist_processing_dlnirsp/tests/test_parse.py +10 -0
  18. dkist_processing_dlnirsp/tests/test_science.py +0 -9
  19. dkist_processing_dlnirsp/tests/test_solar.py +114 -17
  20. dkist_processing_dlnirsp/tests/test_wavelength_calibration.py +4 -1
  21. dkist_processing_dlnirsp/workflows/l0_processing.py +6 -8
  22. dkist_processing_dlnirsp/workflows/trial_workflow.py +7 -7
  23. {dkist_processing_dlnirsp-0.32.8.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/METADATA +52 -35
  24. {dkist_processing_dlnirsp-0.32.8.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/RECORD +28 -30
  25. docs/gain.rst +7 -3
  26. dkist_processing_dlnirsp/tasks/assemble_movie.py +0 -150
  27. dkist_processing_dlnirsp/tasks/make_movie_frames.py +0 -156
  28. dkist_processing_dlnirsp/tests/test_assemble_movie.py +0 -169
  29. dkist_processing_dlnirsp/tests/test_make_movie_frames.py +0 -98
  30. {dkist_processing_dlnirsp-0.32.8.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/WHEEL +0 -0
  31. {dkist_processing_dlnirsp-0.32.8.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/entry_points.txt +0 -0
  32. {dkist_processing_dlnirsp-0.32.8.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@ from dkist_processing_common.parsers.single_value_single_key_flower import (
13
13
  SingleValueSingleKeyFlower,
14
14
  )
15
15
  from dkist_processing_common.parsers.task import PolcalTaskFlower
16
+ from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
16
17
  from dkist_processing_common.parsers.time import ExposureTimeFlower
17
18
  from dkist_processing_common.parsers.time import ObsIpStartTimeBud
18
19
  from dkist_processing_common.parsers.time import TaskExposureTimesBud
@@ -36,7 +37,6 @@ from dkist_processing_dlnirsp.parsers.mosaic import NumMosaicRepeatsBud
36
37
  from dkist_processing_dlnirsp.parsers.mosaic import NumMosaicXTilesBud
37
38
  from dkist_processing_dlnirsp.parsers.mosaic import NumMosaicYTilesBud
38
39
  from dkist_processing_dlnirsp.parsers.task import DlnirspTaskTypeFlower
39
- from dkist_processing_dlnirsp.parsers.task import parse_header_ip_task
40
40
  from dkist_processing_dlnirsp.parsers.time import DLnirspSolarGainIpStartTimeBud
41
41
  from dkist_processing_dlnirsp.parsers.time import DlnirspTimeObsBud
42
42
  from dkist_processing_dlnirsp.parsers.wavelength import ObserveWavelengthBud
@@ -145,6 +145,11 @@ class ParseL0DlnirspLinearizedData(ParseDataBase):
145
145
  """Add DLNIRSP specific constants to common constants."""
146
146
  return default_constant_bud_factory() + [
147
147
  ObserveWavelengthBud(),
148
+ TaskUniqueBud(
149
+ constant_name=DlnirspBudName.obs_ip_end_time.value,
150
+ metadata_key=MetadataKey.ip_end_time,
151
+ ip_task_types=TaskName.observe,
152
+ ),
148
153
  DLnirspSolarGainIpStartTimeBud(),
149
154
  NumCSStepBud(max_cs_step_time_sec=self.parameters.max_cs_step_time_sec),
150
155
  UniqueBud(
@@ -175,42 +180,42 @@ class ParseL0DlnirspLinearizedData(ParseDataBase):
175
180
  TaskExposureTimesBud(
176
181
  stem_name=DlnirspBudName.lamp_gain_exposure_times.value,
177
182
  ip_task_types=TaskName.lamp_gain.value,
178
- header_task_parsing_func=parse_header_ip_task,
183
+ header_task_parsing_func=parse_header_ip_task_with_gains,
179
184
  ),
180
185
  TaskExposureTimesBud(
181
186
  stem_name=DlnirspBudName.solar_gain_exposure_times.value,
182
187
  ip_task_types=TaskName.solar_gain.value,
183
- header_task_parsing_func=parse_header_ip_task,
188
+ header_task_parsing_func=parse_header_ip_task_with_gains,
184
189
  ),
185
190
  TaskExposureTimesBud(
186
191
  stem_name=DlnirspBudName.observe_exposure_times.value,
187
192
  ip_task_types=TaskName.observe.value,
188
- header_task_parsing_func=parse_header_ip_task,
193
+ header_task_parsing_func=parse_header_ip_task_with_gains,
189
194
  ),
190
195
  TaskExposureTimesBud(
191
196
  stem_name=DlnirspBudName.polcal_exposure_times.value,
192
197
  ip_task_types=TaskName.polcal.value,
193
- header_task_parsing_func=parse_header_ip_task,
198
+ header_task_parsing_func=parse_header_ip_task_with_gains,
194
199
  ),
195
200
  TaskNearFloatBud(
196
201
  constant_name=DlnirspBudName.arm_position_mm.value,
197
202
  metadata_key=DlnirspMetadataKey.arm_position_mm,
198
203
  ip_task_types=[TaskName.solar_gain.value, TaskName.observe.value],
199
204
  tolerance=0.01,
200
- task_type_parsing_function=parse_header_ip_task,
205
+ task_type_parsing_function=parse_header_ip_task_with_gains,
201
206
  ),
202
207
  TaskUniqueBud(
203
208
  constant_name=DlnirspBudName.grating_constant_inverse_mm.value,
204
209
  metadata_key=DlnirspMetadataKey.grating_constant_inverse_mm,
205
210
  ip_task_types=[TaskName.solar_gain.value, TaskName.observe.value],
206
- task_type_parsing_function=parse_header_ip_task,
211
+ task_type_parsing_function=parse_header_ip_task_with_gains,
207
212
  ),
208
213
  TaskNearFloatBud(
209
214
  constant_name=DlnirspBudName.grating_position_deg.value,
210
215
  metadata_key=DlnirspMetadataKey.grating_position_deg,
211
216
  ip_task_types=[TaskName.solar_gain.value, TaskName.observe.value],
212
217
  tolerance=0.01,
213
- task_type_parsing_function=parse_header_ip_task,
218
+ task_type_parsing_function=parse_header_ip_task_with_gains,
214
219
  ),
215
220
  ]
216
221
 
@@ -8,6 +8,7 @@ from dkist_processing_common.models.task_name import TaskName
8
8
  from dkist_processing_common.tasks.mixin.quality import QualityMixin
9
9
  from dkist_processing_math.arithmetic import divide_arrays_by_array
10
10
  from dkist_processing_math.arithmetic import subtract_array_from_arrays
11
+ from dkist_processing_math.linear_algebra import nd_left_matrix_multiply
11
12
  from dkist_processing_math.statistics import average_numpy_arrays
12
13
  from dkist_service_configuration.logging import logger
13
14
 
@@ -17,8 +18,10 @@ from dkist_processing_dlnirsp.tasks.mixin.corrections import CorrectionsMixin
17
18
 
18
19
  __all__ = ["SolarCalibration"]
19
20
 
21
+ from dkist_processing_dlnirsp.tasks.mixin.group_id import GroupIdMixin
20
22
 
21
- class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
23
+
24
+ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin, GroupIdMixin):
22
25
  """
23
26
  Task for computing an intermediate solar gain image.
24
27
 
@@ -33,21 +36,23 @@ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
33
36
  """
34
37
  Compute a solar gain image with the solar spectrum removed.
35
38
 
36
- 1. Apply dark, lamp, and geometric corrections to all solar gain frames and average them together.
37
-
38
- 2. Compute a single characteristic spectrum across all slitbeams and place into the full array
39
-
40
- 3. Re-apply the geometric calibration (spectral shifts and scales) to the characteristic spectra
41
-
42
- 4. Remove the characteristic solar spectra from the dark-corrected solar gain image
39
+ #. Compute dark-only and fully (additional lamp and geometric) corrected solar gain data.
43
40
 
44
- 5. Rescale each slitbeam to have the same average value as the raw, dark corrected solar gain image
41
+ a. For polarimetric data, compute a separate average for each modstate and then demodulate and use the Stokes I data
42
+ #. For intensity data, use the average over all modstates (there should be only one).
45
43
 
46
- 6. Write the final, solar-spectrum-removed, solar gain image.
44
+ #. Compute a single characteristic spectrum across all slitbeams and place into the full array
45
+ #. Re-apply the geometric calibration (spectral shifts and scales) to the characteristic spectra
46
+ #. Remove the characteristic solar spectra from the dark-corrected solar gain image
47
+ #. Rescale each slitbeam to have the same average value as the raw, dark corrected solar gain image
48
+ #. Write the final, solar-spectrum-removed solar gain image.
47
49
  """
48
50
  with self.telemetry_span("Apply dark and lamp corrections"):
49
51
  logger.info("Computing average dark/lamp corrected gains")
50
- self.compute_average_corrected_gains()
52
+ if self.constants.correct_for_polarization:
53
+ self.compute_demodulated_I_gains()
54
+ else:
55
+ self.compute_intensity_only_avg_gains()
51
56
 
52
57
  with self.telemetry_span("Compute characteristic spectra"):
53
58
  logger.info("Computing characteristic spectra")
@@ -81,9 +86,12 @@ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
81
86
 
82
87
  with self.telemetry_span("Write solar flat calibration"):
83
88
  logger.info("Writing solar flat calibration")
89
+ tags = [DlnirspTag.intermediate_frame(), DlnirspTag.task_solar_gain()]
90
+ if self.constants.correct_for_polarization:
91
+ tags.append(DlnirspTag.stokes("I"))
84
92
  self.write(
85
93
  data=cleaned_gain,
86
- tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task_solar_gain()],
94
+ tags=tags,
87
95
  encoder=fits_array_encoder,
88
96
  )
89
97
 
@@ -96,13 +104,110 @@ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
96
104
  task_type=TaskName.solar_gain.value, total_frames=no_of_raw_solar_frames
97
105
  )
98
106
 
99
- def compute_average_corrected_gains(self):
107
+ def compute_intensity_only_avg_gains(self):
108
+ """
109
+ Compute dark-only and fully-corrected average solar gains for intensity mode data.
110
+
111
+ The raw solar gain frames are averaged over all modstates (of which there should only be one anyway).
112
+ The lamp-corrected data also have geometric corrections applied prior to saving.
113
+ """
114
+ dark_corr, lamp_corr = self.compute_average_corrected_gain_for_modstate(modstate=None)
115
+
116
+ # This is the array we remove the characteristic solar spectrum from to produce the final gain array
117
+ self.write(
118
+ data=dark_corr,
119
+ tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_DARK_ONLY")],
120
+ encoder=fits_array_encoder,
121
+ )
122
+
123
+ geo_corr_gain = self.apply_geometric_correction(lamp_corr)
124
+
125
+ self.write(
126
+ data=geo_corr_gain,
127
+ tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_FULL_CORR")],
128
+ encoder=fits_array_encoder,
129
+ )
130
+
131
+ def compute_demodulated_I_gains(self):
132
+ """
133
+ Compute demodulated dark-only and fully-corrected solar gain data.
134
+
135
+ The raw solar gains are averaged once for each modstate before having dark and lamp corrections applied.
136
+ These two sets of arrays (dark-only and dark + lamp) are then demodulated. The resulting Stokes I are then saved
137
+ to disk for later use. The lamp-corrected data have a geometric correction applied prior to saving.
138
+ """
139
+ dark_corr_modstate_stack = np.empty(
140
+ self.unrectified_array_shape + (self.constants.num_modstates,)
141
+ )
142
+ lamp_corr_modstate_stack = np.empty_like(dark_corr_modstate_stack)
143
+ for modstate in range(1, self.constants.num_modstates + 1):
144
+ dark_corr, lamp_corr = self.compute_average_corrected_gain_for_modstate(modstate)
145
+ dark_corr_modstate_stack[:, :, modstate - 1] = dark_corr
146
+ lamp_corr_modstate_stack[:, :, modstate - 1] = lamp_corr
147
+
148
+ logger.info("Loading demodulation matrices")
149
+ demod_matrices = next(
150
+ self.read(
151
+ tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task_demodulation_matrices()],
152
+ decoder=fits_array_decoder,
153
+ )
154
+ )
155
+
156
+ demodulated_dark_corr_gain = nd_left_matrix_multiply(
157
+ vector_stack=dark_corr_modstate_stack, matrix_stack=demod_matrices
158
+ )
159
+ demodulated_lamp_corr_gain = nd_left_matrix_multiply(
160
+ vector_stack=lamp_corr_modstate_stack, matrix_stack=demod_matrices
161
+ )
162
+
163
+ dark_corr_I = demodulated_dark_corr_gain[:, :, 0]
164
+ lamp_corr_I = demodulated_lamp_corr_gain[:, :, 0]
165
+
166
+ # This is the array we remove the characteristic solar spectrum from to produce the final gain array
167
+ self.write(
168
+ data=dark_corr_I,
169
+ tags=[
170
+ DlnirspTag.intermediate_frame(),
171
+ DlnirspTag.task("SC_DARK_ONLY"),
172
+ DlnirspTag.stokes("I"),
173
+ ],
174
+ encoder=fits_array_encoder,
175
+ )
176
+
177
+ geo_corr_gain = self.apply_geometric_correction(lamp_corr_I)
178
+
179
+ self.write(
180
+ data=geo_corr_gain,
181
+ tags=[
182
+ DlnirspTag.intermediate_frame(),
183
+ DlnirspTag.task("SC_FULL_CORR"),
184
+ DlnirspTag.stokes("I"),
185
+ ],
186
+ encoder=fits_array_encoder,
187
+ )
188
+
189
+ def compute_average_corrected_gain_for_modstate(
190
+ self, modstate: int | None
191
+ ) -> tuple[np.ndarray, np.ndarray]:
100
192
  """
101
- Compute a single, averaged frame from all linearized solar gain frames.
193
+ Compute a single, averaged frame from all linearized solar gain frames for a given modstate.
102
194
 
103
- Also apply dark, lamp, and geometric calibrations. If there are multiple exposure times present in the solar
195
+ Also apply dark and lamp corrections. If there are multiple exposure times present in the solar
104
196
  gain images, all frames for a single exposure time are averged prior to dark correction. Then all averaged
105
197
  exposure time frames are averaged again into a single frame.
198
+
199
+ Parameters
200
+ ----------
201
+ modstate
202
+ The modstate to average over. If `None` then the average is performed over all modstates.
203
+
204
+ Returns
205
+ -------
206
+ dark_corrected
207
+ The average solar gains with only a dark correction applied
208
+
209
+ lamp_corrected
210
+ The average solar gains with both dark and lamp corrections applied
106
211
  """
107
212
  all_exp_times = []
108
213
  for exp_time in self.constants.solar_gain_exposure_times:
@@ -114,12 +219,14 @@ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
114
219
  )
115
220
  )
116
221
 
117
- logger.info(f"Loading solar gain frames for {exp_time = }")
222
+ logger.info(f"Loading solar gain frames for {modstate = } and {exp_time = }")
118
223
  tags = [
119
224
  DlnirspTag.linearized_frame(),
120
225
  DlnirspTag.task_solar_gain(),
121
226
  DlnirspTag.exposure_time(exp_time),
122
227
  ]
228
+ if modstate is not None:
229
+ tags.append(DlnirspTag.modstate(modstate))
123
230
  gain_arrays = self.read(tags=tags, decoder=fits_array_decoder)
124
231
 
125
232
  logger.info("Averaging solar gain frames")
@@ -135,13 +242,6 @@ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
135
242
  logger.info(f"Computing final average gain array for {len(all_exp_times)} exposure times")
136
243
  avg_gain_array = average_numpy_arrays(all_exp_times)
137
244
 
138
- # This is the array we remove the characteristic solar spectrum from to produce the final gain array
139
- self.write(
140
- data=avg_gain_array,
141
- tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_DARK_ONLY")],
142
- encoder=fits_array_encoder,
143
- )
144
-
145
245
  logger.info("Loading lamp calibration")
146
246
  lamp_array = next(
147
247
  self.read(
@@ -155,8 +255,11 @@ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
155
255
  divide_arrays_by_array(arrays=avg_gain_array, array_to_divide_by=lamp_array)
156
256
  )
157
257
 
158
- logger.info("Loading geometric calibration")
258
+ return avg_gain_array, lamp_corrected_array
159
259
 
260
+ def apply_geometric_correction(self, array: np.ndarray) -> np.ndarray:
261
+ """Apply the geometric correction to an array."""
262
+ logger.info("Loading geometric calibration")
160
263
  geometric_correction = next(
161
264
  self.read(
162
265
  tags=[DlnirspTag.intermediate(), DlnirspTag.task_geometric()], decoder=asdf_decoder
@@ -169,7 +272,7 @@ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
169
272
  logger.info("Applying geometric calibration")
170
273
  final_gain_array = next(
171
274
  self.corrections_remove_spec_geometry(
172
- arrays=lamp_corrected_array,
275
+ arrays=array,
173
276
  shift_dict=shifts,
174
277
  scale_dict=scales,
175
278
  reference_wavelength_axis=reference_wavelength_axis,
@@ -177,11 +280,7 @@ class SolarCalibration(DlnirspTaskBase, CorrectionsMixin, QualityMixin):
177
280
  )
178
281
  )
179
282
 
180
- self.write(
181
- data=final_gain_array,
182
- tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_FULL_CORR")],
183
- encoder=fits_array_encoder,
184
- )
283
+ return final_gain_array
185
284
 
186
285
  def compute_characteristic_spectra(self) -> np.ndarray:
187
286
  """
@@ -132,7 +132,7 @@ class DlnirspTestingParameters:
132
132
  dlnirsp_geo_reference_wave_min_nonnan_frac: float = 0.05
133
133
  dlnirsp_wavecal_atlas_download_config: dict[str, str] = field(
134
134
  default_factory=lambda: {
135
- "base_url": "doi:10.5281/zenodo.14646787/",
135
+ "base_url": "https://g-a36282.cd214.a567.data.globus.org/atlas/",
136
136
  "telluric_reference_atlas_file_name": "telluric_reference_atlas.npy",
137
137
  "telluric_reference_atlas_hash_id": "md5:8db5e12508b293bca3495d81a0747447",
138
138
  "solar_reference_atlas_file_name": "solar_reference_atlas.npy",
@@ -170,12 +170,25 @@ class DlnirspTestingParameters:
170
170
  dlnirsp_bad_pixel_correction_interpolation_kernel_shape: tuple[int, int] = (10, 3)
171
171
  dlnirsp_wcs_pc_correction_matrix: tuple[tuple[int]] = ((1, 0), (0, -1))
172
172
  dlnirsp_wcs_crpix_correction_method: str = "swap_then_flip_crpix2"
173
+ dlnirsp_movie_core_wave_value_nm_vis: float = 853.98
174
+ dlnirsp_movie_core_wave_value_nm_jband: float = 1083.0
175
+ dlnirsp_movie_core_wave_value_nm_hband: float = 1564.8
176
+ dlnirsp_movie_cont_wave_value_nm_vis: float = 854.23
177
+ dlnirsp_movie_cont_wave_value_nm_jband: float = 1083.268
178
+ dlnirsp_movie_cont_wave_value_nm_hband: float = 1565.06
179
+ dlnirsp_movie_vertical_nan_slices: tuple[tuple[int | None, int | None]] = (
180
+ (None, 1),
181
+ (31, 34),
182
+ (-2, None),
183
+ )
184
+ dlnirsp_movie_nan_replacement_kernel_shape: tuple[int, int] = (5, 5)
173
185
 
174
186
 
175
187
  @dataclass
176
188
  class DlnirspTestingConstants:
177
189
  INSTRUMENT: str = "DLNIRSP"
178
- OBS_IP_START_TIME: str = "2024-06-06"
190
+ OBS_IP_START_TIME: str = "2024-06-06T00:00:00"
191
+ OBS_IP_END_TIME: str = "2024-06-06T00:10:00"
179
192
  ARM_ID: str = "HBand"
180
193
  NUM_MODSTATES: int = 8
181
194
  NUM_MOSAIC_TILES_X: int = 2
@@ -204,6 +217,7 @@ class DlnirspTestingConstants:
204
217
  "EXPERID2",
205
218
  "EXPERID3",
206
219
  )
220
+ EXPERIMENT_ID: str = "eid_6_28"
207
221
  ARM_POSITION_MM: float = -4.2
208
222
  GRATING_CONSTANT_INVERSE_MM: float = 20.0
209
223
  GRATING_POSITION_DEG: float = 87.4
@@ -1229,6 +1243,8 @@ class CalibratedHeaders(ModulatedObserveHeaders):
1229
1243
  is_polarimetric: bool,
1230
1244
  dither_mode_on: bool = False,
1231
1245
  array_shape: tuple[int, int, int] = (3, 4, 5),
1246
+ crpix1_step_delta: float = 40.0,
1247
+ crpix2_step_delta: float = 30.0,
1232
1248
  ):
1233
1249
 
1234
1250
  # Use the data_cycles loop to represent Stokes parameters
@@ -1246,6 +1262,8 @@ class CalibratedHeaders(ModulatedObserveHeaders):
1246
1262
  allow_3D_arrays=True,
1247
1263
  )
1248
1264
 
1265
+ self.crpix1_step_delta = crpix1_step_delta
1266
+ self.crpix2_step_delta = crpix2_step_delta
1249
1267
  self.stokes_name_list = ["I", "Q", "U", "V"]
1250
1268
  self.add_constant_key("DLN__014", 8 if is_polarimetric else 1)
1251
1269
  self.add_constant_key("DLN__008", "Full Stokes" if is_polarimetric else "Stokes I")
@@ -1263,12 +1281,21 @@ class CalibratedHeaders(ModulatedObserveHeaders):
1263
1281
  ) # -1 b/c data cycles are indexed from 1
1264
1282
  return self.stokes_name_list[stokes_axis_id]
1265
1283
 
1284
+ @property
1285
+ def current_crpix1(self) -> float:
1286
+ return self.array_shape[2] / 2 - self.crpix1_step_delta * self.current_MINDEX1_value
1287
+
1288
+ @property
1289
+ def current_crpix2(self) -> float:
1290
+ return self.array_shape[1] / 2 - self.crpix2_step_delta * self.current_MINDEX2_value
1291
+
1266
1292
  @property
1267
1293
  def fits_wcs(self):
1294
+ # Taken from real data from eid_2_24
1268
1295
  w = WCS(naxis=3)
1269
- w.wcs.crpix = self.array_shape[2] / 2, self.array_shape[1] / 2, self.array_shape[0] / 2
1270
- w.wcs.crval = 666.0, 999.0, 1565.0
1271
- w.wcs.cdelt = 1, 1, 0.2
1296
+ w.wcs.crpix = self.current_crpix1, self.current_crpix2, self.array_shape[0] / 2
1297
+ w.wcs.crval = 176.118, -289.575, 1565.0
1298
+ w.wcs.cdelt = 0.031, 0.031, 0.2
1272
1299
  w.wcs.cunit = "arcsec", "arcsec", "nm"
1273
1300
  w.wcs.ctype = "HPLN-TAN", "HPLT-TAN", "AWAV"
1274
1301
  w.wcs.pc = np.identity(self.array_ndim)
@@ -1342,6 +1369,15 @@ def make_cs_data(
1342
1369
  return data
1343
1370
 
1344
1371
 
1372
+ @pytest.fixture
1373
+ def make_full_demodulation_matrix(demodulation_matrix):
1374
+ def make_array(frame: Spec122Dataset):
1375
+ array_shape = frame.array_shape[1:]
1376
+ return np.ones(array_shape + demodulation_matrix.shape) * demodulation_matrix
1377
+
1378
+ return make_array
1379
+
1380
+
1345
1381
  def tag_on_modstate(frame: Spec122Dataset) -> list[str]:
1346
1382
  modstate = frame.header()["DLN__015"]
1347
1383
  return [DlnirspTag.modstate(modstate)]
@@ -1631,11 +1667,12 @@ def write_observe_frames_to_task(
1631
1667
  array_shape: tuple[int, int] = (10, 10),
1632
1668
  crpix_delta: tuple[float, float] = (80.3, 20.7),
1633
1669
  swap_crpix_values: bool = False,
1670
+ start_date: str = "2023-01-01T01:23:45",
1671
+ modstate_length_sec: float = 0.5,
1634
1672
  data_func: callable = make_random_data,
1635
1673
  tags: list[str] | None = None,
1636
1674
  tag_func: callable = lambda x: [],
1637
1675
  ) -> int:
1638
-
1639
1676
  frame_generator = ModulatedObserveHeaders(
1640
1677
  num_modstates=num_modstates,
1641
1678
  num_X_tiles=num_X_tiles,
@@ -1650,6 +1687,8 @@ def write_observe_frames_to_task(
1650
1687
  grating_angle=grating_angle,
1651
1688
  crpix_delta=crpix_delta,
1652
1689
  swap_crpix_values=swap_crpix_values,
1690
+ start_date=start_date,
1691
+ modstate_length_sec=modstate_length_sec,
1653
1692
  )
1654
1693
 
1655
1694
  num_frames = write_frames_to_task(
@@ -1700,7 +1739,7 @@ def write_calibrated_frames_to_task(
1700
1739
  is_polarimetric: bool,
1701
1740
  array_shape: tuple[int, int, int],
1702
1741
  dither_mode_on: bool = False,
1703
- data_func: callable = make_3D_random_data,
1742
+ data_func: Callable = make_3D_random_data,
1704
1743
  ):
1705
1744
  dataset = CalibratedHeaders(
1706
1745
  num_mosaics=num_mosaics,
@@ -13,11 +13,10 @@ from dkist_processing_common.tasks import WorkflowTaskBase
13
13
  from dkist_service_configuration.logging import logger
14
14
 
15
15
  from dkist_processing_dlnirsp.models.tags import DlnirspTag
16
- from dkist_processing_dlnirsp.tasks import AssembleDlnirspMovie
17
16
  from dkist_processing_dlnirsp.tasks import DlnirspAssembleQualityData
18
17
  from dkist_processing_dlnirsp.tasks import DlnirspL0QualityMetrics
19
18
  from dkist_processing_dlnirsp.tasks import DlnirspL1QualityMetrics
20
- from dkist_processing_dlnirsp.tasks import MakeDlnirspMovieFrames
19
+ from dkist_processing_dlnirsp.tasks import MakeDlnirspMovie
21
20
  from dkist_processing_dlnirsp.tasks.bad_pixel_map import BadPixelCalibration
22
21
  from dkist_processing_dlnirsp.tasks.dark import DarkCalibration
23
22
  from dkist_processing_dlnirsp.tasks.geometric import GeometricCalibration
@@ -197,10 +196,11 @@ def main(
197
196
  load_bad_pixel: bool = False,
198
197
  load_geometric: bool = False,
199
198
  load_wavelength_calibration: bool = False,
200
- load_solar: bool = False,
201
199
  load_inst_polcal: bool = False,
200
+ load_solar: bool = False,
202
201
  load_polcals_as_science: bool = False,
203
202
  load_calibrated_data: bool = False,
203
+ skip_movie: bool = False,
204
204
  transfer_trial_data: str | None = None,
205
205
  ):
206
206
  with ManualProcessing(
@@ -298,18 +298,18 @@ def main(
298
298
  manual_processing_run.run_task(task=WavelengthCalibration)
299
299
  manual_processing_run.run_task(task=SaveWavelengthCal)
300
300
 
301
- if load_solar:
302
- manual_processing_run.run_task(task=LoadSolarCal)
303
- else:
304
- manual_processing_run.run_task(task=SolarCalibration)
305
- manual_processing_run.run_task(task=SaveSolarCal)
306
-
307
301
  if load_inst_polcal:
308
302
  manual_processing_run.run_task(task=LoadInstPolCal)
309
303
  else:
310
304
  manual_processing_run.run_task(task=InstrumentPolarizationCalibration)
311
305
  manual_processing_run.run_task(task=SaveInstPolCal)
312
306
 
307
+ if load_solar:
308
+ manual_processing_run.run_task(task=LoadSolarCal)
309
+ else:
310
+ manual_processing_run.run_task(task=SolarCalibration)
311
+ manual_processing_run.run_task(task=SaveSolarCal)
312
+
313
313
  if load_polcals_as_science:
314
314
  manual_processing_run.run_task(task=LoadPolCalAsScience)
315
315
  else:
@@ -324,8 +324,6 @@ def main(
324
324
 
325
325
  manual_processing_run.run_task(task=permissive_write_l1_task(force_intensity_only=False))
326
326
  manual_processing_run.run_task(task=ValidateL1Output)
327
- manual_processing_run.run_task(task=MakeDlnirspMovieFrames)
328
- manual_processing_run.run_task(task=AssembleDlnirspMovie)
329
327
 
330
328
  manual_processing_run.run_task(task=DlnirspL0QualityMetrics)
331
329
  manual_processing_run.run_task(task=DlnirspL1QualityMetrics)
@@ -334,6 +332,9 @@ def main(
334
332
 
335
333
  manual_processing_run.run_task(task=CreateTrialQualityReport)
336
334
 
335
+ if not skip_movie:
336
+ manual_processing_run.run_task(task=MakeDlnirspMovie)
337
+
337
338
  if transfer_trial_data:
338
339
  transfer_trial_data_locally(
339
340
  trial_output_location=transfer_trial_data, processing_run=manual_processing_run
@@ -425,18 +426,18 @@ if __name__ == "__main__":
425
426
  help="Load wavelength calibration solution from previously saved run",
426
427
  action="store_true",
427
428
  )
428
- parser.add_argument(
429
- "-S",
430
- "--load-solar",
431
- help="Load solar calibration from previously saved run",
432
- action="store_true",
433
- )
434
429
  parser.add_argument(
435
430
  "-P",
436
431
  "--load-inst-polcal",
437
432
  help="Load instrument polarization calibration from previously saved run",
438
433
  action="store_true",
439
434
  )
435
+ parser.add_argument(
436
+ "-S",
437
+ "--load-solar",
438
+ help="Load solar calibration from previously saved run",
439
+ action="store_true",
440
+ )
440
441
  parser.add_argument(
441
442
  "-O",
442
443
  "--load-polcals-as-science",
@@ -446,6 +447,7 @@ if __name__ == "__main__":
446
447
  parser.add_argument(
447
448
  "-C", "--load-calibrated-data", help="Load CALIBRATED 'science' frames", action="store_true"
448
449
  )
450
+ parser.add_argument("-V", "--skip-movie", help="Don't make a browse movie", action="store_true")
449
451
  parser.add_argument(
450
452
  "-p",
451
453
  "--param-dir",
@@ -472,10 +474,11 @@ if __name__ == "__main__":
472
474
  load_bad_pixel=args.load_bad_pixel,
473
475
  load_geometric=args.load_geometric,
474
476
  load_wavelength_calibration=args.load_wavelength_calibration,
475
- load_solar=args.load_solar,
476
477
  load_inst_polcal=args.load_inst_polcal,
478
+ load_solar=args.load_solar,
477
479
  load_polcals_as_science=args.load_polcals_as_science,
478
480
  load_calibrated_data=args.load_calibrated_data,
481
+ skip_movie=args.skip_movie,
479
482
  transfer_trial_data=args.transfer_trial_data,
480
483
  )
481
484
  )