dkist-processing-dlnirsp 0.32.9__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 (31) 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 +45 -6
  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/workflows/l0_processing.py +6 -8
  21. dkist_processing_dlnirsp/workflows/trial_workflow.py +7 -7
  22. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/METADATA +41 -24
  23. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/RECORD +27 -29
  24. docs/gain.rst +7 -3
  25. dkist_processing_dlnirsp/tasks/assemble_movie.py +0 -150
  26. dkist_processing_dlnirsp/tasks/make_movie_frames.py +0 -156
  27. dkist_processing_dlnirsp/tests/test_assemble_movie.py +0 -169
  28. dkist_processing_dlnirsp/tests/test_make_movie_frames.py +0 -98
  29. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/WHEEL +0 -0
  30. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/entry_points.txt +0 -0
  31. {dkist_processing_dlnirsp-0.32.9.dist-info → dkist_processing_dlnirsp-0.33.0.dist-info}/top_level.txt +0 -0
@@ -13,7 +13,6 @@ 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.assemble_movie import AssembleDlnirspMovie
17
16
  from dkist_processing_dlnirsp.tasks.bad_pixel_map import BadPixelCalibration
18
17
  from dkist_processing_dlnirsp.tasks.dark import DarkCalibration
19
18
  from dkist_processing_dlnirsp.tasks.geometric import GeometricCalibration
@@ -22,7 +21,7 @@ from dkist_processing_dlnirsp.tasks.instrument_polarization import InstrumentPol
22
21
  from dkist_processing_dlnirsp.tasks.l1_output_data import DlnirspAssembleQualityData
23
22
  from dkist_processing_dlnirsp.tasks.lamp import LampCalibration
24
23
  from dkist_processing_dlnirsp.tasks.linearity_correction import LinearityCorrection
25
- from dkist_processing_dlnirsp.tasks.make_movie_frames import MakeDlnirspMovieFrames
24
+ from dkist_processing_dlnirsp.tasks.movie import MakeDlnirspMovie
26
25
  from dkist_processing_dlnirsp.tasks.parse import ParseL0DlnirspLinearizedData
27
26
  from dkist_processing_dlnirsp.tasks.parse import ParseL0DlnirspRampData
28
27
  from dkist_processing_dlnirsp.tasks.quality_metrics import DlnirspL0QualityMetrics
@@ -171,9 +170,10 @@ def main(
171
170
  load_bad_pixel: bool = False,
172
171
  load_geometric: bool = False,
173
172
  load_wavelength_calibration: bool = False,
174
- load_solar: bool = False,
175
173
  load_inst_polcal: bool = False,
174
+ load_solar: bool = False,
176
175
  load_calibrated_data: bool = False,
176
+ skip_movie: bool = False,
177
177
  transfer_trial_data: str | None = None,
178
178
  ):
179
179
  with ManualProcessing(
@@ -261,18 +261,18 @@ def main(
261
261
  manual_processing_run.run_task(task=WavelengthCalibration)
262
262
  manual_processing_run.run_task(task=SaveWavelengthCal)
263
263
 
264
- if load_solar:
265
- manual_processing_run.run_task(task=LoadSolarCal)
266
- else:
267
- manual_processing_run.run_task(task=SolarCalibration)
268
- manual_processing_run.run_task(task=SaveSolarCal)
269
-
270
264
  if load_inst_polcal:
271
265
  manual_processing_run.run_task(task=LoadInstPolCal)
272
266
  else:
273
267
  manual_processing_run.run_task(task=InstrumentPolarizationCalibration)
274
268
  manual_processing_run.run_task(task=SaveInstPolCal)
275
269
 
270
+ if load_solar:
271
+ manual_processing_run.run_task(task=LoadSolarCal)
272
+ else:
273
+ manual_processing_run.run_task(task=SolarCalibration)
274
+ manual_processing_run.run_task(task=SaveSolarCal)
275
+
276
276
  if load_calibrated_data:
277
277
  manual_processing_run.run_task(task=LoadCalibratedData)
278
278
  else:
@@ -281,8 +281,6 @@ def main(
281
281
 
282
282
  manual_processing_run.run_task(task=DlnirspWriteL1Frame)
283
283
  manual_processing_run.run_task(task=ValidateL1Output)
284
- manual_processing_run.run_task(task=MakeDlnirspMovieFrames)
285
- manual_processing_run.run_task(task=AssembleDlnirspMovie)
286
284
 
287
285
  manual_processing_run.run_task(task=DlnirspL0QualityMetrics)
288
286
  manual_processing_run.run_task(task=DlnirspL1QualityMetrics)
@@ -293,6 +291,9 @@ def main(
293
291
 
294
292
  manual_processing_run.run_task(task=CreateTrialAsdf)
295
293
 
294
+ if not skip_movie:
295
+ manual_processing_run.run_task(task=MakeDlnirspMovie)
296
+
296
297
  if transfer_trial_data:
297
298
  transfer_trial_data_locally(
298
299
  trial_output_location=transfer_trial_data, processing_run=manual_processing_run
@@ -391,21 +392,22 @@ if __name__ == "__main__":
391
392
  help="Load wavelength calibration solution from previously saved run",
392
393
  action="store_true",
393
394
  )
394
- parser.add_argument(
395
- "-S",
396
- "--load-solar",
397
- help="Load solar calibration from previously saved run",
398
- action="store_true",
399
- )
400
395
  parser.add_argument(
401
396
  "-P",
402
397
  "--load-inst-polcal",
403
398
  help="Load instrument polarization calibration from previously saved run",
404
399
  action="store_true",
405
400
  )
401
+ parser.add_argument(
402
+ "-S",
403
+ "--load-solar",
404
+ help="Load solar calibration from previously saved run",
405
+ action="store_true",
406
+ )
406
407
  parser.add_argument(
407
408
  "-C", "--load-calibrated-data", help="Load CALIBRATED science frames", action="store_true"
408
409
  )
410
+ parser.add_argument("-V", "--skip-movie", help="Don't make a browse movie", action="store_true")
409
411
  parser.add_argument(
410
412
  "-p",
411
413
  "--param-dir",
@@ -432,9 +434,10 @@ if __name__ == "__main__":
432
434
  load_bad_pixel=args.load_bad_pixel,
433
435
  load_geometric=args.load_geometric,
434
436
  load_wavelength_calibration=args.load_wavelength_calibration,
435
- load_solar=args.load_solar,
436
437
  load_inst_polcal=args.load_inst_polcal,
438
+ load_solar=args.load_solar,
437
439
  load_calibrated_data=args.load_calibrated_data,
440
+ skip_movie=args.skip_movie,
438
441
  transfer_trial_data=args.transfer_trial_data,
439
442
  )
440
443
  )
@@ -89,7 +89,7 @@ def permissive_write_l1_task(force_intensity_only: bool):
89
89
  self, header: fits.Header, stokes: Literal["I", "Q", "U", "V"]
90
90
  ) -> fits.Header:
91
91
  if force_intensity_only:
92
- header[MetadataKey.polarimeter_mode] = "Stokes I"
92
+ header[DlnirspMetadataKey.polarimeter_mode] = "Stokes I"
93
93
 
94
94
  return super().add_dataset_headers(header=header, stokes=stokes)
95
95
 
@@ -28,6 +28,7 @@ class test_constants:
28
28
  arm_position_mm: float = 39.2
29
29
  grating_constant_inverse_mm: float = 19.0
30
30
  grating_position_deg: float = 87.4
31
+ obs_ip_end_time: str = "2000-01-01T00:01:00"
31
32
  # We don't need all the common ones, but let's put one just to check
32
33
  instrument: str = "CHECK_OUT_THIS_INSTRUMENT"
33
34
 
@@ -47,6 +48,7 @@ def simple_constant_names():
47
48
  "observe_exposure_times",
48
49
  "num_modstates",
49
50
  "solar_gain_ip_start_time",
51
+ "obs_ip_end_time",
50
52
  "instrument",
51
53
  ]
52
54
 
@@ -0,0 +1,141 @@
1
+ import shutil
2
+
3
+ import numpy as np
4
+ import pytest
5
+ from dkist_processing_common._util.scratch import WorkflowFileSystem
6
+ from dkist_processing_common.codecs.json import json_encoder
7
+
8
+ from dkist_processing_dlnirsp.models.tags import DlnirspTag
9
+ from dkist_processing_dlnirsp.tasks.movie import MakeDlnirspMovie
10
+ from dkist_processing_dlnirsp.tests.conftest import CalibratedHeaders
11
+ from dkist_processing_dlnirsp.tests.conftest import DlnirspTestingConstants
12
+ from dkist_processing_dlnirsp.tests.conftest import DlnirspTestingParameters
13
+ from dkist_processing_dlnirsp.tests.conftest import write_calibrated_frames_to_task
14
+
15
+
16
+ @pytest.fixture
17
+ def movie_task(recipe_run_id, tmp_path, link_constants_db, assign_input_dataset_doc_to_task):
18
+ link_constants_db(
19
+ recipe_run_id=recipe_run_id,
20
+ constants_obj=DlnirspTestingConstants(),
21
+ )
22
+ with MakeDlnirspMovie(
23
+ recipe_run_id=recipe_run_id,
24
+ workflow_name="workflow_name",
25
+ workflow_version="workflow_version",
26
+ ) as task:
27
+ assign_input_dataset_doc_to_task(
28
+ task,
29
+ DlnirspTestingParameters(),
30
+ arm_id="VIS",
31
+ )
32
+ task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
33
+
34
+ yield task
35
+ task._purge()
36
+
37
+
38
+ @pytest.fixture(scope="session")
39
+ def num_spectral_pix() -> int:
40
+ return 396
41
+
42
+
43
+ @pytest.fixture(scope="session")
44
+ def valid_wavelength_solution_header(num_spectral_pix) -> dict[str, str | int | float]:
45
+ """A valid wavelength solution header that depends on `arm_id='vis'` in `movie_task`."""
46
+ return {
47
+ "CTYPE3": "AWAV-GRA",
48
+ "CUNIT3": "nm",
49
+ "CRPIX3": num_spectral_pix // 2,
50
+ "CRVAL3": 854.17,
51
+ "CDELT3": 0.00229,
52
+ "PV3_0": 23000,
53
+ "PV3_1": 90,
54
+ "PV3_2": 65.69,
55
+ }
56
+
57
+
58
+ def mindices_data_func(frame: CalibratedHeaders) -> np.ndarray:
59
+ shape = frame.array_shape
60
+
61
+ return np.ones(shape) * (1000 * frame.current_MINDEX1_value + 100 * frame.current_MINDEX2_value)
62
+
63
+
64
+ @pytest.mark.parametrize(
65
+ "has_multiple_mosaics",
66
+ [pytest.param(True, id="multi_mosaic"), pytest.param(False, id="single_mosaic")],
67
+ )
68
+ @pytest.mark.parametrize(
69
+ "is_polarimetric",
70
+ [pytest.param(True, id="polarimetric"), pytest.param(False, id="spectrographic")],
71
+ )
72
+ @pytest.mark.parametrize(
73
+ "is_mosaiced", [pytest.param(True, id="mosaiced"), pytest.param(False, id="no_mosaic")]
74
+ )
75
+ def test_movie_task(
76
+ movie_task,
77
+ has_multiple_mosaics,
78
+ is_polarimetric,
79
+ is_mosaiced,
80
+ num_spectral_pix,
81
+ valid_wavelength_solution_header,
82
+ link_constants_db,
83
+ mocker,
84
+ fake_gql_client,
85
+ ):
86
+ """
87
+ Given: A `MakeDlnirspMovie` task with CALIBRATED frames
88
+ When: Running the task
89
+ Then: The dang thing runs and produces the expected movie file
90
+ """
91
+ mocker.patch(
92
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
93
+ )
94
+
95
+ num_dither = 1
96
+ num_mosaics = 2 if has_multiple_mosaics else 1
97
+ num_X_tiles = 2 if is_mosaiced else 1
98
+ num_Y_tiles = 2 if is_mosaiced else 1
99
+ stokes_params = ["I"]
100
+ if is_polarimetric:
101
+ stokes_params += ["Q", "U", "V"]
102
+ num_modstates = 8
103
+ pol_mode = "Full Stokes"
104
+ else:
105
+ num_modstates = 1
106
+ pol_mode = "Stokes I"
107
+
108
+ task = movie_task
109
+
110
+ constants_db = DlnirspTestingConstants(
111
+ POLARIMETER_MODE=pol_mode,
112
+ NUM_DITHER_STEPS=num_dither,
113
+ NUM_MOSAIC_REPEATS=num_mosaics,
114
+ NUM_MOSAIC_TILES_X=num_X_tiles,
115
+ NUM_MOSAIC_TILES_Y=num_Y_tiles,
116
+ NUM_MODSTATES=num_modstates,
117
+ )
118
+ link_constants_db(task.recipe_run_id, constants_db)
119
+
120
+ write_calibrated_frames_to_task(
121
+ task,
122
+ num_mosaics=num_mosaics,
123
+ num_X_tiles=num_X_tiles,
124
+ num_Y_tiles=num_Y_tiles,
125
+ dither_mode_on=False,
126
+ is_polarimetric=is_polarimetric,
127
+ array_shape=(num_spectral_pix, 20, 30),
128
+ data_func=mindices_data_func,
129
+ )
130
+
131
+ task.write(
132
+ data=valid_wavelength_solution_header,
133
+ tags=[DlnirspTag.intermediate(), DlnirspTag.task_wavelength_solution()],
134
+ encoder=json_encoder,
135
+ )
136
+
137
+ task()
138
+
139
+ movie_file_list = list(task.read(tags=[DlnirspTag.output(), DlnirspTag.movie()]))
140
+
141
+ assert len(movie_file_list) == 1
@@ -53,6 +53,12 @@ def arm_parameter_names() -> list[str]:
53
53
  "ifu_y_pos_file_vis",
54
54
  "ifu_y_pos_file_jband",
55
55
  "ifu_y_pos_file_hband",
56
+ "movie_core_wave_value_nm_vis",
57
+ "movie_core_wave_value_nm_jband",
58
+ "movie_core_wave_value_nm_hband",
59
+ "movie_cont_wave_value_nm_vis",
60
+ "movie_cont_wave_value_nm_jband",
61
+ "movie_cont_wave_value_nm_hband",
56
62
  ]
57
63
 
58
64
 
@@ -170,6 +176,8 @@ def test_standard_parameters(
170
176
  np.testing.assert_array_equal(param_obj_value, pv)
171
177
  elif isinstance(param_obj_value, BaseModel):
172
178
  assert param_obj_value.model_dump() == pv
179
+ elif property_name == "movie_vertical_nan_slices":
180
+ assert [slice(*i) for i in pv] == param_obj_value
173
181
  else:
174
182
  assert param_obj_value == pv
175
183
 
@@ -1,3 +1,5 @@
1
+ import datetime
2
+
1
3
  import numpy as np
2
4
  import pytest
3
5
  from astropy.io import fits
@@ -255,6 +257,8 @@ def test_parse_linearized_data(
255
257
  grating_constant=grating_constant * 300,
256
258
  grating_angle=grating_angle * -6,
257
259
  )
260
+ obs_start_time = "2020-01-01T01:23:45"
261
+ modstate_length_sec = 0.5
258
262
  num_obs = write_observe_frames_to_task(
259
263
  task,
260
264
  num_modstates=num_mod,
@@ -267,8 +271,13 @@ def test_parse_linearized_data(
267
271
  grating_constant=grating_constant,
268
272
  grating_angle=grating_angle,
269
273
  dither_mode_on=dither_mode_on,
274
+ start_date=obs_start_time,
275
+ modstate_length_sec=modstate_length_sec,
270
276
  swap_crpix_values="swap" in task.parameters.wcs_crpix_correction_method,
271
277
  )
278
+ obs_end_time = datetime.datetime.fromisoformat(obs_start_time) + datetime.timedelta(
279
+ seconds=num_obs * modstate_length_sec
280
+ )
272
281
 
273
282
  task()
274
283
 
@@ -350,6 +359,7 @@ def test_parse_linearized_data(
350
359
  assert task.constants._db_dict[BudName.solar_gain_gos_level3_status] == "clear"
351
360
  assert task.constants._db_dict[BudName.solar_gain_num_raw_frames_per_fpa] == 30
352
361
  assert task.constants._db_dict[BudName.polcal_num_raw_frames_per_fpa] == 10
362
+ assert task.constants._db_dict[DlnirspBudName.obs_ip_end_time] == obs_end_time.isoformat("T")
353
363
 
354
364
 
355
365
  def test_crpix_and_spatial_step_association_swapped(
@@ -70,15 +70,6 @@ def make_solar_data(solar_signal):
70
70
  return make_array
71
71
 
72
72
 
73
- @pytest.fixture
74
- def make_full_demodulation_matrix(demodulation_matrix):
75
- def make_array(frame: Spec122Dataset):
76
- array_shape = frame.array_shape[1:]
77
- return np.ones(array_shape + demodulation_matrix.shape) * demodulation_matrix
78
-
79
- return make_array
80
-
81
-
82
73
  @pytest.fixture
83
74
  def make_linearized_science_data(
84
75
  dark_signal,
@@ -12,9 +12,11 @@ from dkist_processing_dlnirsp.models.tags import DlnirspTag
12
12
  from dkist_processing_dlnirsp.tasks.solar import SolarCalibration
13
13
  from dkist_processing_dlnirsp.tests.conftest import DlnirspTestingConstants
14
14
  from dkist_processing_dlnirsp.tests.conftest import DlnirspTestingParameters
15
+ from dkist_processing_dlnirsp.tests.conftest import tag_on_modstate
15
16
  from dkist_processing_dlnirsp.tests.conftest import write_dark_frames_to_task
16
17
  from dkist_processing_dlnirsp.tests.conftest import write_geometric_calibration_to_task
17
18
  from dkist_processing_dlnirsp.tests.conftest import write_lamp_gain_frames_to_task
19
+ from dkist_processing_dlnirsp.tests.conftest import write_simple_frames_to_task
18
20
  from dkist_processing_dlnirsp.tests.conftest import write_solar_gain_frames_to_task
19
21
 
20
22
 
@@ -30,7 +32,7 @@ def lamp_signal() -> float:
30
32
 
31
33
  @pytest.fixture
32
34
  def solar_signal() -> float:
33
- return 2000.0
35
+ return 62831.85
34
36
 
35
37
 
36
38
  @pytest.fixture
@@ -52,11 +54,29 @@ def make_lamp_data(lamp_signal):
52
54
 
53
55
 
54
56
  @pytest.fixture
55
- def make_solar_data(solar_signal, dark_signal, lamp_signal):
57
+ def solar_stokes_stack(solar_signal) -> np.ndarray:
58
+ I = solar_signal
59
+ Q = np.random.random() - 0.5
60
+ U = np.random.random() - 0.5
61
+ V = np.random.random() - 0.5
62
+
63
+ return np.array([I, Q, U, V])
64
+
65
+
66
+ @pytest.fixture
67
+ def modulated_solar_signal(solar_stokes_stack, modulation_matrix) -> np.ndarray:
68
+ return modulation_matrix @ solar_stokes_stack
69
+
70
+
71
+ @pytest.fixture
72
+ def make_solar_data(
73
+ modulated_solar_signal, solar_signal, dark_signal, lamp_signal, is_polarimetric
74
+ ):
56
75
  def make_array(frame: Spec122Dataset):
57
76
  shape = frame.array_shape[1:]
58
77
  modstate = frame.header()["DLN__015"]
59
- return (np.ones(shape) * solar_signal + 100 * modstate) * lamp_signal + dark_signal
78
+ true_signal = modulated_solar_signal[modstate - 1] if is_polarimetric else solar_signal
79
+ return (np.ones(shape) * true_signal) * lamp_signal + dark_signal
60
80
 
61
81
  return make_array
62
82
 
@@ -121,13 +141,20 @@ def solar_task_for_basic_corrections(
121
141
  shifts_and_scales,
122
142
  constants_class_with_different_num_slits,
123
143
  reference_wave_axis,
144
+ modulation_matrix,
145
+ make_full_demodulation_matrix,
146
+ is_polarimetric,
124
147
  ):
125
148
  solar_exp_time = 1.0
126
- num_modstates = 4
149
+ num_modstates = modulation_matrix.shape[0] if is_polarimetric else 1
127
150
  array_shape = jband_group_id_array.shape
128
151
  link_constants_db(
129
152
  recipe_run_id=recipe_run_id,
130
- constants_obj=DlnirspTestingConstants(SOLAR_GAIN_EXPOSURE_TIMES=(solar_exp_time,)),
153
+ constants_obj=DlnirspTestingConstants(
154
+ SOLAR_GAIN_EXPOSURE_TIMES=(solar_exp_time,),
155
+ NUM_MODSTATES=num_modstates,
156
+ POLARIMETER_MODE="Full Stokes" if is_polarimetric else "Stokes I",
157
+ ),
131
158
  )
132
159
 
133
160
  with SolarCalibration(
@@ -172,6 +199,7 @@ def solar_task_for_basic_corrections(
172
199
  DlnirspTag.task_solar_gain(),
173
200
  DlnirspTag.exposure_time(solar_exp_time),
174
201
  ],
202
+ tag_func=tag_on_modstate,
175
203
  data_func=make_solar_data,
176
204
  )
177
205
  shift_dict, scale_dict = shifts_and_scales
@@ -179,6 +207,16 @@ def solar_task_for_basic_corrections(
179
207
  task, shift_dict=shift_dict, scale_dict=scale_dict, wave_axis=reference_wave_axis
180
208
  )
181
209
 
210
+ if is_polarimetric:
211
+ write_simple_frames_to_task(
212
+ task,
213
+ task_type=TaskName.polcal.value,
214
+ array_shape=array_shape,
215
+ num_modstates=1,
216
+ tags=[DlnirspTag.intermediate(), DlnirspTag.task_demodulation_matrices()],
217
+ data_func=make_full_demodulation_matrix,
218
+ )
219
+
182
220
  yield task, num_modstates, num_solar_frames
183
221
  task._purge()
184
222
 
@@ -272,8 +310,15 @@ def solar_task_with_no_data(
272
310
  task._purge()
273
311
 
274
312
 
313
+ @pytest.mark.parametrize(
314
+ "is_polarimetric", [pytest.param(True, id="polarimetric"), pytest.param(False, id="intensity")]
315
+ )
275
316
  def test_compute_average_gain(
276
- solar_task_for_basic_corrections, solar_signal, write_drifted_group_ids_to_task
317
+ solar_task_for_basic_corrections,
318
+ is_polarimetric,
319
+ solar_signal,
320
+ lamp_signal,
321
+ write_drifted_group_ids_to_task,
277
322
  ):
278
323
  """
279
324
  Given: A SolarCalibration task with associated solar gain, lamp, geometric, and dark frames
@@ -284,22 +329,36 @@ def test_compute_average_gain(
284
329
 
285
330
  write_drifted_group_ids_to_task(task)
286
331
 
287
- task.compute_average_corrected_gains()
332
+ pol_tag = []
333
+ if is_polarimetric:
334
+ task.compute_demodulated_I_gains()
335
+ pol_tag.append(DlnirspTag.stokes("I"))
336
+ else:
337
+ task.compute_intensity_only_avg_gains()
288
338
 
289
- tags = [DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_FULL_CORR")]
339
+ tags = [DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_FULL_CORR")] + pol_tag
290
340
  arrays = list(task.read(tags=tags, decoder=fits_array_decoder))
291
341
 
342
+ if not is_polarimetric:
343
+ assert task.count(tags=tags + [DlnirspTag.stokes("I")]) == 0
344
+
292
345
  assert len(arrays) == 1
293
346
  avg_array = arrays[0]
294
347
  assert avg_array.shape == task.rectified_array_shape
295
- expected_value = np.mean(solar_signal + 100 * np.arange(1, num_modstates + 1))
296
- np.testing.assert_equal(avg_array[~np.isnan(avg_array)], expected_value)
348
+ expected_value = solar_signal
349
+ np.testing.assert_array_almost_equal(avg_array[~np.isnan(avg_array)], expected_value)
297
350
 
298
351
  dark_only_list = list(
299
- task.read(tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_DARK_ONLY")])
352
+ task.read(
353
+ tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_DARK_ONLY")],
354
+ decoder=fits_array_decoder,
355
+ )
300
356
  )
301
357
  assert len(dark_only_list) == 1
302
- assert dark_only_list[0].exists()
358
+ expected_dark_only_value = solar_signal * lamp_signal
359
+ np.testing.assert_array_almost_equal(
360
+ dark_only_list[0][~np.isnan(dark_only_list[0])], expected_dark_only_value
361
+ )
303
362
 
304
363
 
305
364
  def test_compute_characteristic_spectra(solar_task_with_full_corr, groups_by_slitbeam):
@@ -326,8 +385,16 @@ def test_compute_characteristic_spectra(solar_task_with_full_corr, groups_by_sli
326
385
  np.testing.assert_equal(rectified_group[~np.isnan(rectified_group)], slitbeam_median)
327
386
 
328
387
 
388
+ @pytest.mark.parametrize(
389
+ "is_polarimetric", [pytest.param(True, id="polarimetric"), pytest.param(False, id="intensity")]
390
+ )
329
391
  def test_solar_task_completes(
330
- solar_task_for_basic_corrections, write_drifted_group_ids_to_task, mocker, fake_gql_client
392
+ solar_task_for_basic_corrections,
393
+ write_drifted_group_ids_to_task,
394
+ is_polarimetric,
395
+ modulation_matrix,
396
+ mocker,
397
+ fake_gql_client,
331
398
  ):
332
399
  """
333
400
  Given: A SolarCalibration task with necessary starting data
@@ -341,16 +408,46 @@ def test_solar_task_completes(
341
408
 
342
409
  write_drifted_group_ids_to_task(task)
343
410
 
411
+ # Just make sure we set up the test correctly
412
+ assert num_solar_frames == modulation_matrix.shape[0] if is_polarimetric else 1
413
+
344
414
  task()
345
415
 
346
- solar_cal_list = list(
347
- task.read(
348
- tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task_solar_gain()],
416
+ # Make sure the correct code paths were taken; intensity-only data intermediates don't get the `stokes("I")` tag
417
+ assert task.count(tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_DARK_ONLY")]) == 1
418
+ assert task.count(tags=[DlnirspTag.intermediate_frame(), DlnirspTag.task("SC_FULL_CORR")]) == 1
419
+ if not is_polarimetric:
420
+ assert (
421
+ task.count(
422
+ tags=[
423
+ DlnirspTag.intermediate_frame(),
424
+ DlnirspTag.task("SC_DARK_ONLY"),
425
+ DlnirspTag.stokes("I"),
426
+ ]
427
+ )
428
+ == 0
349
429
  )
350
- )
430
+ assert (
431
+ task.count(
432
+ tags=[
433
+ DlnirspTag.intermediate_frame(),
434
+ DlnirspTag.task("SC_FULL_CORR"),
435
+ DlnirspTag.stokes("I"),
436
+ ]
437
+ )
438
+ == 0
439
+ )
440
+
441
+ tags = [DlnirspTag.intermediate_frame(), DlnirspTag.task_solar_gain()]
442
+ if is_polarimetric:
443
+ tags.append(DlnirspTag.stokes("I"))
444
+ solar_cal_list = list(task.read(tags=tags))
351
445
  assert len(solar_cal_list) == 1
352
446
  assert solar_cal_list[0].exists()
353
447
 
448
+ if not is_polarimetric:
449
+ assert task.count(tags=tags + [DlnirspTag.stokes("I")]) == 0
450
+
354
451
  quality_files = list(task.read(tags=[DlnirspTag.quality("TASK_TYPES")]))
355
452
  assert len(quality_files) == 1
356
453
  file = quality_files[0]
@@ -9,7 +9,6 @@ from dkist_processing_common.tasks import TransferL1Data
9
9
  from dkist_processing_core import ResourceQueue
10
10
  from dkist_processing_core import Workflow
11
11
 
12
- from dkist_processing_dlnirsp.tasks import AssembleDlnirspMovie
13
12
  from dkist_processing_dlnirsp.tasks import BadPixelCalibration
14
13
  from dkist_processing_dlnirsp.tasks import DarkCalibration
15
14
  from dkist_processing_dlnirsp.tasks import DlnirspAssembleQualityData
@@ -21,7 +20,7 @@ from dkist_processing_dlnirsp.tasks import IfuDriftCalibration
21
20
  from dkist_processing_dlnirsp.tasks import InstrumentPolarizationCalibration
22
21
  from dkist_processing_dlnirsp.tasks import LampCalibration
23
22
  from dkist_processing_dlnirsp.tasks import LinearityCorrection
24
- from dkist_processing_dlnirsp.tasks import MakeDlnirspMovieFrames
23
+ from dkist_processing_dlnirsp.tasks import MakeDlnirspMovie
25
24
  from dkist_processing_dlnirsp.tasks import ParseL0DlnirspLinearizedData
26
25
  from dkist_processing_dlnirsp.tasks import ParseL0DlnirspRampData
27
26
  from dkist_processing_dlnirsp.tasks import ScienceCalibration
@@ -47,22 +46,21 @@ l0_pipeline.add_node(task=LampCalibration, upstreams=DarkCalibration)
47
46
  l0_pipeline.add_node(task=BadPixelCalibration, upstreams=[LampCalibration, IfuDriftCalibration])
48
47
  l0_pipeline.add_node(task=GeometricCalibration, upstreams=[IfuDriftCalibration, LampCalibration])
49
48
  l0_pipeline.add_node(task=WavelengthCalibration, upstreams=GeometricCalibration)
50
- l0_pipeline.add_node(task=SolarCalibration, upstreams=GeometricCalibration)
51
49
  l0_pipeline.add_node(
52
50
  task=InstrumentPolarizationCalibration,
53
51
  resource_queue=ResourceQueue.HIGH_MEMORY,
54
52
  upstreams=BadPixelCalibration,
55
53
  )
56
54
  l0_pipeline.add_node(
57
- task=ScienceCalibration, upstreams=[InstrumentPolarizationCalibration, SolarCalibration]
55
+ task=SolarCalibration, upstreams=[GeometricCalibration, InstrumentPolarizationCalibration]
58
56
  )
57
+ l0_pipeline.add_node(task=ScienceCalibration, upstreams=SolarCalibration)
59
58
  l0_pipeline.add_node(
60
59
  task=DlnirspWriteL1Frame, upstreams=[WavelengthCalibration, ScienceCalibration]
61
60
  )
62
61
 
63
62
  # Movie flow
64
- l0_pipeline.add_node(task=MakeDlnirspMovieFrames, upstreams=ScienceCalibration)
65
- l0_pipeline.add_node(task=AssembleDlnirspMovie, upstreams=MakeDlnirspMovieFrames)
63
+ l0_pipeline.add_node(task=MakeDlnirspMovie, upstreams=[ScienceCalibration, WavelengthCalibration])
66
64
 
67
65
  # Quality flow
68
66
  l0_pipeline.add_node(task=DlnirspL0QualityMetrics, upstreams=ParseL0DlnirspLinearizedData)
@@ -76,11 +74,11 @@ l0_pipeline.add_node(
76
74
  # Output flow
77
75
  l0_pipeline.add_node(
78
76
  task=TransferL1Data,
79
- upstreams=[DlnirspWriteL1Frame, AssembleDlnirspMovie, DlnirspAssembleQualityData],
77
+ upstreams=[DlnirspWriteL1Frame, MakeDlnirspMovie, DlnirspAssembleQualityData],
80
78
  )
81
79
  l0_pipeline.add_node(
82
80
  task=SubmitDatasetMetadata,
83
- upstreams=[DlnirspWriteL1Frame, AssembleDlnirspMovie],
81
+ upstreams=[DlnirspWriteL1Frame, MakeDlnirspMovie],
84
82
  )
85
83
  l0_pipeline.add_node(
86
84
  task=PublishCatalogAndQualityMessages, upstreams=[TransferL1Data, SubmitDatasetMetadata]