dkist-processing-trend 0.1.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 (66) hide show
  1. changelog/.gitempty +0 -0
  2. dkist_processing_trend/__init__.py +10 -0
  3. dkist_processing_trend/config.py +11 -0
  4. dkist_processing_trend/models/__init__.py +1 -0
  5. dkist_processing_trend/models/constants.py +143 -0
  6. dkist_processing_trend/models/fit_options.py +15 -0
  7. dkist_processing_trend/models/fits_access.py +65 -0
  8. dkist_processing_trend/models/instrument.py +35 -0
  9. dkist_processing_trend/models/instrument_options.py +35 -0
  10. dkist_processing_trend/models/parameters.py +134 -0
  11. dkist_processing_trend/models/tags.py +117 -0
  12. dkist_processing_trend/models/task_name.py +20 -0
  13. dkist_processing_trend/parsers/__init__.py +1 -0
  14. dkist_processing_trend/parsers/arm_id.py +103 -0
  15. dkist_processing_trend/parsers/instrument_unique_bud.py +40 -0
  16. dkist_processing_trend/parsers/time.py +27 -0
  17. dkist_processing_trend/parsers/trend_l0_fits_access.py +123 -0
  18. dkist_processing_trend/tasks/__init__.py +27 -0
  19. dkist_processing_trend/tasks/arm_task_factory.py +57 -0
  20. dkist_processing_trend/tasks/dark.py +56 -0
  21. dkist_processing_trend/tasks/gain.py +74 -0
  22. dkist_processing_trend/tasks/initialize_arm_tasks.py +50 -0
  23. dkist_processing_trend/tasks/parse.py +169 -0
  24. dkist_processing_trend/tasks/prepare_fit_data_base.py +257 -0
  25. dkist_processing_trend/tasks/run_pac_fitter.py +392 -0
  26. dkist_processing_trend/tasks/trend_base.py +97 -0
  27. dkist_processing_trend/tasks/trend_output_data.py +167 -0
  28. dkist_processing_trend/tasks/visp/__init__.py +6 -0
  29. dkist_processing_trend/tasks/visp/visp_dmpd.py +410 -0
  30. dkist_processing_trend/tasks/visp/visp_extract_beam.py +14 -0
  31. dkist_processing_trend/tasks/visp/visp_geometric.py +260 -0
  32. dkist_processing_trend/tasks/visp/visp_prep_fit_data.py +162 -0
  33. dkist_processing_trend/tasks/visp/visp_process_demod.py +236 -0
  34. dkist_processing_trend/tasks/write_trend.py +663 -0
  35. dkist_processing_trend/tests/__init__.py +1 -0
  36. dkist_processing_trend/tests/conftest.py +718 -0
  37. dkist_processing_trend/tests/local_trial_workflows/__init__.py +0 -0
  38. dkist_processing_trend/tests/local_trial_workflows/l0_to_trend_visp_polcal.py +294 -0
  39. dkist_processing_trend/tests/local_trial_workflows/local_trial_helpers.py +488 -0
  40. dkist_processing_trend/tests/test_arm_task_factory.py +82 -0
  41. dkist_processing_trend/tests/test_base_tasks.py +86 -0
  42. dkist_processing_trend/tests/test_constants.py +120 -0
  43. dkist_processing_trend/tests/test_dark.py +97 -0
  44. dkist_processing_trend/tests/test_gain.py +135 -0
  45. dkist_processing_trend/tests/test_parameters.py +149 -0
  46. dkist_processing_trend/tests/test_parse.py +276 -0
  47. dkist_processing_trend/tests/test_prep_fit_data_base.py +233 -0
  48. dkist_processing_trend/tests/test_publish_catalog_messages.py +45 -0
  49. dkist_processing_trend/tests/test_run_pac_fitter.py +371 -0
  50. dkist_processing_trend/tests/test_stems.py +75 -0
  51. dkist_processing_trend/tests/test_transfer_output_data.py +76 -0
  52. dkist_processing_trend/tests/test_trend_fits_access.py +173 -0
  53. dkist_processing_trend/tests/test_visp.py +874 -0
  54. dkist_processing_trend/tests/test_workflows.py +10 -0
  55. dkist_processing_trend/tests/test_write_trend.py +460 -0
  56. dkist_processing_trend/workflows/__init__.py +3 -0
  57. dkist_processing_trend/workflows/visp.py +58 -0
  58. dkist_processing_trend-0.1.0.dist-info/METADATA +549 -0
  59. dkist_processing_trend-0.1.0.dist-info/RECORD +66 -0
  60. dkist_processing_trend-0.1.0.dist-info/WHEEL +5 -0
  61. dkist_processing_trend-0.1.0.dist-info/top_level.txt +3 -0
  62. docs/conf.py +57 -0
  63. docs/index.rst +10 -0
  64. docs/l0_to_trend_visp_polcal.rst +4 -0
  65. docs/landing_page.rst +11 -0
  66. docs/requirements_table.rst +8 -0
@@ -0,0 +1,10 @@
1
+ """Test integrity of workflows."""
2
+
3
+ from dkist_processing_core.build_utils import validate_workflows
4
+
5
+ from dkist_processing_trend import workflows
6
+
7
+
8
+ def test_workflow_integrity():
9
+ """Validate workflow to ensure acyclic-ness and export compilation"""
10
+ validate_workflows(workflows)
@@ -0,0 +1,460 @@
1
+ import json
2
+
3
+ import numpy as np
4
+ import pytest
5
+ from astropy.io import fits
6
+ from dkist_processing_common._util.scratch import WorkflowFileSystem
7
+ from dkist_processing_common.codecs.fits import fits_array_encoder
8
+
9
+ from dkist_processing_trend.models.instrument import InstrumentName
10
+ from dkist_processing_trend.models.parameters import TrendParameters
11
+ from dkist_processing_trend.models.tags import TrendTag
12
+ from dkist_processing_trend.tasks.write_trend import WriteTrendOutputs
13
+ from dkist_processing_trend.tests.conftest import ARM_ANGLE_FOR_ARM
14
+ from dkist_processing_trend.tests.conftest import CRYO_OD_FILTER_FOR_ARM
15
+ from dkist_processing_trend.tests.conftest import CRYO_SLIT_WIDTH_FOR_ARM
16
+ from dkist_processing_trend.tests.conftest import GRATING_ANGLE_FOR_ARM
17
+ from dkist_processing_trend.tests.conftest import GRATING_CONSTANT_FOR_ARM
18
+ from dkist_processing_trend.tests.conftest import WAVELENGTH_FOR_ARM
19
+ from dkist_processing_trend.tests.conftest import TrendInputDatasetParameterValues
20
+ from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
21
+
22
+
23
+ @pytest.fixture(scope="session")
24
+ def num_polcal_ops() -> int:
25
+ return 1
26
+
27
+
28
+ @pytest.fixture(scope="session")
29
+ def num_cs_steps() -> int:
30
+ return 3
31
+
32
+
33
+ @pytest.fixture(scope="session")
34
+ def num_modstates() -> int:
35
+ return 4
36
+
37
+
38
+ @pytest.fixture(scope="session")
39
+ def fov_shape() -> tuple[int, int]:
40
+ return (12, 11)
41
+
42
+
43
+ def num_beams_from_arm_id(arm_id) -> int:
44
+ if arm_id == "CI":
45
+ return 1
46
+ return 2
47
+
48
+
49
+ @pytest.fixture(scope="session")
50
+ def fit_options_list() -> list[dict[str, str | bool]]:
51
+ return [
52
+ {
53
+ "name": "option1",
54
+ "remove_I_trend": True,
55
+ "fit_mode_name": "use_M12",
56
+ "init_set_name": "OCCal_VIS",
57
+ },
58
+ {
59
+ "name": "option2",
60
+ "remove_I_trend": False,
61
+ "fit_mode_name": "use_M12_I_sys_per_step",
62
+ "init_set_name": "OCCal_VIS",
63
+ },
64
+ ]
65
+
66
+
67
+ @pytest.fixture(scope="session")
68
+ def visp_instrument_options_list() -> list[dict]:
69
+ return [
70
+ {
71
+ "name": "full",
72
+ "num_spatial_px": 2560,
73
+ "num_spectral_px": 1000,
74
+ "spatial_smoothing_fit_order": 22,
75
+ },
76
+ {
77
+ "name": "single",
78
+ "num_spatial_px": 1,
79
+ "num_spectral_px": 1,
80
+ "spatial_smoothing_fit_order": 1,
81
+ },
82
+ ]
83
+
84
+
85
+ @pytest.fixture(scope="session")
86
+ def cryonirsp_instrument_options_list() -> list[dict]:
87
+ return [
88
+ {"name": "full", "num_spatial_px": 1000, "num_spectral_px": 1000},
89
+ {"name": "single", "num_spatial_px": 1, "num_spectral_px": 1},
90
+ ]
91
+
92
+
93
+ @pytest.fixture(scope="session")
94
+ def dlnirsp_instrument_options_list() -> list[dict]:
95
+ return [
96
+ {
97
+ "name": "full",
98
+ "num_spatial_px": 1280,
99
+ "num_spectral_px": 1,
100
+ "spatial_smoothing_fit_order": 13,
101
+ },
102
+ {
103
+ "name": "single",
104
+ "num_spatial_px": 1,
105
+ "num_spectral_px": 1,
106
+ "spatial_smoothing_fit_order": 1,
107
+ },
108
+ ]
109
+
110
+
111
+ @pytest.fixture
112
+ def write_trend_task(
113
+ tmp_path,
114
+ recipe_run_id,
115
+ link_constants_db,
116
+ assign_input_dataset_doc_to_task,
117
+ num_cs_steps,
118
+ num_modstates,
119
+ instrument,
120
+ fit_options_list,
121
+ visp_instrument_options_list,
122
+ cryonirsp_instrument_options_list,
123
+ dlnirsp_instrument_options_list,
124
+ arm_id_list,
125
+ ):
126
+ link_constants_db(
127
+ recipe_run_id,
128
+ TrendTestingConstantsDatabase(
129
+ INSTRUMENT=instrument,
130
+ NUM_CS_STEPS=num_cs_steps,
131
+ NUM_MODSTATES=num_modstates,
132
+ ARM_ID_LIST=arm_id_list,
133
+ WAVELENGTH_FOR_ARM={json.dumps(arm): WAVELENGTH_FOR_ARM[arm] for arm in arm_id_list},
134
+ VISP_GRATING_ANGLE_FOR_ARM={
135
+ json.dumps(arm): GRATING_ANGLE_FOR_ARM[arm] for arm in arm_id_list
136
+ },
137
+ VISP_ARM_POSITION_FOR_ARM={
138
+ json.dumps(arm): ARM_ANGLE_FOR_ARM[arm] for arm in arm_id_list
139
+ },
140
+ VISP_GRATING_CONSTANT_FOR_ARM={
141
+ json.dumps(arm): GRATING_CONSTANT_FOR_ARM[arm] for arm in arm_id_list
142
+ },
143
+ CRYONIRSP_GRATING_ANGLE_FOR_ARM={
144
+ json.dumps(arm): GRATING_ANGLE_FOR_ARM[arm] for arm in arm_id_list
145
+ },
146
+ CRYONIRSP_GRATING_LITTROW_ANGLE_FOR_ARM={
147
+ json.dumps(arm): ARM_ANGLE_FOR_ARM[arm] for arm in arm_id_list
148
+ },
149
+ CRYONIRSP_GRATING_CONSTANT_FOR_ARM={
150
+ json.dumps(arm): GRATING_CONSTANT_FOR_ARM[arm] for arm in arm_id_list
151
+ },
152
+ CRYONIRSP_OD_FILTER_NAME_FOR_ARM={
153
+ json.dumps(arm): CRYO_OD_FILTER_FOR_ARM[arm] for arm in arm_id_list
154
+ },
155
+ CRYONIRSP_SLIT_WIDTH_FOR_ARM={
156
+ json.dumps(arm): CRYO_SLIT_WIDTH_FOR_ARM[arm] for arm in arm_id_list
157
+ },
158
+ DLNIRSP_GRATING_ANGLE_FOR_ARM={
159
+ json.dumps(arm): GRATING_ANGLE_FOR_ARM[arm] for arm in arm_id_list
160
+ },
161
+ DLNIRSP_ARM_POSITION_FOR_ARM={
162
+ json.dumps(arm): ARM_ANGLE_FOR_ARM[arm] for arm in arm_id_list
163
+ },
164
+ DLNIRSP_GRATING_CONSTANT_FOR_ARM={
165
+ json.dumps(arm): GRATING_CONSTANT_FOR_ARM[arm] for arm in arm_id_list
166
+ },
167
+ ),
168
+ )
169
+ with WriteTrendOutputs(
170
+ recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
171
+ ) as task:
172
+ task.scratch = WorkflowFileSystem(recipe_run_id=recipe_run_id, scratch_base_path=tmp_path)
173
+ assign_input_dataset_doc_to_task(
174
+ task,
175
+ TrendInputDatasetParameterValues(
176
+ trend_fit_options_list=fit_options_list,
177
+ trend_instrument_processing_options_visp=visp_instrument_options_list,
178
+ trend_instrument_processing_options_cryonirsp=cryonirsp_instrument_options_list,
179
+ trend_instrument_processing_options_dlnirsp=dlnirsp_instrument_options_list,
180
+ ),
181
+ )
182
+ task.parameters = TrendParameters(
183
+ scratch=task.scratch,
184
+ obs_ip_start_time=task.constants.earliest_ip_start_time,
185
+ instrument=task.constants.instrument,
186
+ )
187
+
188
+ yield task
189
+ task._purge()
190
+
191
+
192
+ def write_pre_output_files_to_task(
193
+ task: WriteTrendOutputs,
194
+ raw_calibration_sequence_data_shape: tuple[int, int, int],
195
+ raw_fit_parameter_shape: tuple[int, ...],
196
+ raw_demod_shape: tuple[int, ...],
197
+ raw_flux_shape: tuple[int, ...],
198
+ raw_dmpd_shape: tuple[int, ...],
199
+ ):
200
+
201
+ inst_options_list = task.parameters.instrument_processing_options
202
+ fit_options_list = task.parameters.fit_options_list
203
+
204
+ rng = np.random.default_rng()
205
+
206
+ for arm_id in task.constants.arm_id_list:
207
+ # Calibration sequence
208
+ data = rng.random(raw_calibration_sequence_data_shape, dtype=np.float64)
209
+ task.write(
210
+ data=data,
211
+ tags=[
212
+ TrendTag.intermediate(),
213
+ TrendTag.arm_id(arm_id),
214
+ TrendTag.task_calibration_sequence(),
215
+ ],
216
+ encoder=fits_array_encoder,
217
+ )
218
+
219
+ for inst_opt in inst_options_list:
220
+ inst_opt_name = inst_opt.name
221
+ for fit_opt in fit_options_list:
222
+ fit_opt_name = fit_opt.name
223
+
224
+ # DMPD doesn't depend on beam
225
+ data = rng.random(raw_dmpd_shape, dtype=np.float64)
226
+ task.write(
227
+ data=data,
228
+ tags=[
229
+ TrendTag.intermediate(),
230
+ TrendTag.arm_id(arm_id),
231
+ TrendTag.instrument_processing_options(inst_opt_name),
232
+ TrendTag.pac_fit_options(fit_opt_name),
233
+ TrendTag.task_demodulated_polcal_data(),
234
+ ],
235
+ encoder=fits_array_encoder,
236
+ )
237
+
238
+ for beam in range(1, num_beams_from_arm_id(arm_id) + 1):
239
+ # Fit parameters
240
+ data = rng.random(raw_fit_parameter_shape, dtype=np.float64)
241
+ task.write(
242
+ data=data,
243
+ tags=[
244
+ TrendTag.intermediate(),
245
+ TrendTag.arm_id(arm_id),
246
+ TrendTag.beam(beam),
247
+ TrendTag.instrument_processing_options(inst_opt_name),
248
+ TrendTag.pac_fit_options(fit_opt_name),
249
+ TrendTag.task_best_fit_parameters(),
250
+ ],
251
+ encoder=fits_array_encoder,
252
+ )
253
+
254
+ # Demodulation matrices
255
+ data = rng.random(raw_demod_shape, dtype=np.float64)
256
+ task.write(
257
+ data=data,
258
+ tags=[
259
+ TrendTag.intermediate(),
260
+ TrendTag.arm_id(arm_id),
261
+ TrendTag.beam(beam),
262
+ TrendTag.instrument_processing_options(inst_opt_name),
263
+ TrendTag.pac_fit_options(fit_opt_name),
264
+ TrendTag.task_best_fit_demodulation_matrices(),
265
+ ],
266
+ encoder=fits_array_encoder,
267
+ )
268
+
269
+ # Flux
270
+ data = rng.random(raw_flux_shape, dtype=np.float64)
271
+ task.write(
272
+ data=data,
273
+ tags=[
274
+ TrendTag.intermediate(),
275
+ TrendTag.arm_id(arm_id),
276
+ TrendTag.beam(beam),
277
+ TrendTag.instrument_processing_options(inst_opt_name),
278
+ TrendTag.pac_fit_options(fit_opt_name),
279
+ TrendTag.task_best_fit_flux(),
280
+ ],
281
+ encoder=fits_array_encoder,
282
+ )
283
+
284
+ # Residuals
285
+ data = rng.random(raw_flux_shape, dtype=np.float64)
286
+ task.write(
287
+ data=data,
288
+ tags=[
289
+ TrendTag.intermediate(),
290
+ TrendTag.arm_id(arm_id),
291
+ TrendTag.beam(beam),
292
+ TrendTag.instrument_processing_options(inst_opt_name),
293
+ TrendTag.pac_fit_options(fit_opt_name),
294
+ TrendTag.task_fit_residuals(),
295
+ ],
296
+ encoder=fits_array_encoder,
297
+ )
298
+
299
+ if arm_id != "CI":
300
+ # Single-beam DMPD
301
+ data = rng.random(raw_dmpd_shape, dtype=np.float64)
302
+ task.write(
303
+ data=data,
304
+ tags=[
305
+ TrendTag.intermediate(),
306
+ TrendTag.arm_id(arm_id),
307
+ TrendTag.beam(beam),
308
+ TrendTag.instrument_processing_options(inst_opt_name),
309
+ TrendTag.pac_fit_options(fit_opt_name),
310
+ TrendTag.task_single_beam_demodulated_polcal_data(),
311
+ ],
312
+ encoder=fits_array_encoder,
313
+ )
314
+
315
+
316
+ @pytest.mark.parametrize(
317
+ "instrument, arm_id_list",
318
+ [
319
+ pytest.param(InstrumentName.visp, [1], id="visp"),
320
+ pytest.param(InstrumentName.cryonirsp, ["CI"], id="cryo-ci"),
321
+ pytest.param(InstrumentName.cryonirsp, ["SP"], id="cryo-sp"),
322
+ pytest.param(InstrumentName.dlnirsp, ["HBand"], id="dlnirsp"),
323
+ pytest.param(InstrumentName.visp, [1, 2, 3], id="visp-multi-arm"),
324
+ pytest.param(InstrumentName.cryonirsp, ["CI", "SP"], id="cryo-multi-arm"),
325
+ pytest.param(InstrumentName.dlnirsp, ["VIS", "JBand", "HBand"], id="dlnirsp-multi-arm"),
326
+ ],
327
+ )
328
+ def test_write_trend_task(
329
+ write_trend_task,
330
+ instrument,
331
+ arm_id_list,
332
+ num_polcal_ops,
333
+ num_cs_steps,
334
+ num_modstates,
335
+ fov_shape,
336
+ fit_options_list,
337
+ visp_instrument_options_list,
338
+ cryonirsp_instrument_options_list,
339
+ dlnirsp_instrument_options_list,
340
+ mocker,
341
+ fake_gql_client,
342
+ ):
343
+ """
344
+ Given: A `WriteTrendOutputs` task with some pre-output data on disk
345
+ When: Running the task
346
+ Then: The correct number of outputs is written, and the structure of each is correct
347
+ """
348
+ mocker.patch(
349
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
350
+ )
351
+
352
+ raw_cs_shape = (num_polcal_ops, num_modstates, num_cs_steps)
353
+ raw_fit_param_shape = (*fov_shape, num_polcal_ops, 15 + num_cs_steps, 3)
354
+ raw_demod_shape = (*fov_shape, 4, num_modstates)
355
+ raw_flux_shape = (*fov_shape, num_modstates, num_cs_steps)
356
+ raw_dmpd_shape = (num_polcal_ops, num_cs_steps, *fov_shape)
357
+
358
+ task = write_trend_task
359
+
360
+ write_pre_output_files_to_task(
361
+ task=task,
362
+ raw_calibration_sequence_data_shape=raw_cs_shape,
363
+ raw_fit_parameter_shape=raw_fit_param_shape,
364
+ raw_demod_shape=raw_demod_shape,
365
+ raw_flux_shape=raw_flux_shape,
366
+ raw_dmpd_shape=raw_dmpd_shape,
367
+ )
368
+
369
+ task()
370
+
371
+ match instrument:
372
+ case InstrumentName.visp:
373
+ inst_opt_list = visp_instrument_options_list
374
+ case InstrumentName.cryonirsp:
375
+ inst_opt_list = cryonirsp_instrument_options_list
376
+ case InstrumentName.dlnirsp:
377
+ inst_opt_list = dlnirsp_instrument_options_list
378
+ case _:
379
+ raise ValueError(
380
+ f"Do not recognize {instrument = }. Your fixtures/parametrization are jacked."
381
+ )
382
+
383
+ for arm_id in arm_id_list:
384
+ num_beams = num_beams_from_arm_id(arm_id)
385
+ expected_cs_shape = (num_polcal_ops, num_modstates, num_cs_steps)
386
+ expected_fit_param_shape = (num_beams, *fov_shape, num_polcal_ops, 15 + num_cs_steps, 3)
387
+ expected_modmat_shape = (num_beams, *fov_shape, num_modstates, 4)
388
+ expected_flux_shape = (num_beams, *fov_shape, num_modstates, num_cs_steps)
389
+ expected_dmpd_shape = (num_polcal_ops, num_cs_steps, *fov_shape)
390
+ expected_num_hdus = 9
391
+ expected_hdu_name_order = [
392
+ "Inventory",
393
+ "Calibration sequence",
394
+ "Fit info",
395
+ "Fit parameters",
396
+ "Modulation matrices",
397
+ "Best fit flux",
398
+ "Best fit residuals",
399
+ "Demodulated polcal data",
400
+ ]
401
+ expected_data_shape_list = [
402
+ # None for HDUs that have no data
403
+ None,
404
+ expected_cs_shape,
405
+ None,
406
+ expected_fit_param_shape,
407
+ expected_modmat_shape,
408
+ expected_flux_shape,
409
+ expected_flux_shape,
410
+ expected_dmpd_shape,
411
+ ]
412
+
413
+ if arm_id != "CI":
414
+ expected_num_hdus += 2
415
+ expected_hdu_name_order.insert(-1, "Beam 1 demodulated polcal data")
416
+ expected_hdu_name_order.insert(-1, "Beam 2 demodulated polcal data")
417
+ expected_data_shape_list += [expected_dmpd_shape, expected_dmpd_shape]
418
+
419
+ for fit_opt in fit_options_list:
420
+ for inst_opt in inst_opt_list:
421
+ tags = [
422
+ TrendTag.output(),
423
+ TrendTag.frame(),
424
+ TrendTag.arm_id(arm_id),
425
+ TrendTag.pac_fit_options(fit_opt["name"]),
426
+ TrendTag.instrument_processing_options(inst_opt["name"]),
427
+ ]
428
+ assert task.count(tags) == 1
429
+ full_path = next(task.read(tags))
430
+ filename = full_path.name
431
+ assert filename.startswith("trend_polcal")
432
+ assert f"_{instrument}_" in filename
433
+ assert f"_{arm_id}_" in filename
434
+ assert f"_{fit_opt["name"]}-{inst_opt["name"]}_" in filename
435
+ assert filename.endswith(".fits")
436
+
437
+ with fits.open(full_path) as hdul:
438
+ assert len(hdul) == expected_num_hdus
439
+ assert isinstance(hdul[0], fits.PrimaryHDU)
440
+ assert [hdu.name for hdu in hdul[1:]] == expected_hdu_name_order
441
+
442
+ for hdu, expected_shape in zip(
443
+ hdul[1:],
444
+ expected_data_shape_list,
445
+ ):
446
+ if hdu.name == "Inventory":
447
+ assert "FRAMEVOL" in hdu.header
448
+ else:
449
+ assert "FRAMEVOL" not in hdu.header
450
+ if hdu.data is not None:
451
+ assert isinstance(hdu, fits.CompImageHDU)
452
+ assert hdu.data.dtype is np.dtype(np.float32)
453
+ assert hdu.header["BITPIX"] == -32
454
+ assert (
455
+ hdu.data.shape == expected_shape
456
+ ), f"Data shape {hdu.data.shape} does not match expected {expected_shape} for HDU {hdu.name}"
457
+ else:
458
+ assert isinstance(hdu, fits.ImageHDU)
459
+
460
+ # TODO: Test validation
@@ -0,0 +1,3 @@
1
+ """Workflow package."""
2
+
3
+ from dkist_processing_trend.config import dkist_processing_trend_configurations
@@ -0,0 +1,58 @@
1
+ """Trend pipelines for ViSP."""
2
+
3
+ from dkist_processing_common.tasks import Teardown
4
+ from dkist_processing_common.tasks import TransferL0Data
5
+ from dkist_processing_core import Workflow
6
+
7
+ from dkist_processing_trend.models.instrument import VispArms
8
+ from dkist_processing_trend.tasks import DarkCalibration
9
+ from dkist_processing_trend.tasks import GainCalibration
10
+ from dkist_processing_trend.tasks import ParseL0TrendInputData
11
+ from dkist_processing_trend.tasks import PublishTrendCatalogMessages
12
+ from dkist_processing_trend.tasks import RunPacFitter
13
+ from dkist_processing_trend.tasks import TransferTrendData
14
+ from dkist_processing_trend.tasks import WriteTrendOutputs
15
+ from dkist_processing_trend.tasks.arm_task_factory import retreive_arm_task
16
+ from dkist_processing_trend.tasks.visp import VispDemodulatedPolcalData
17
+ from dkist_processing_trend.tasks.visp import VispGeometricCalibration
18
+ from dkist_processing_trend.tasks.visp import VispPrepareFitData
19
+ from dkist_processing_trend.tasks.visp import VispProcessDemodulationMatrices
20
+
21
+ visp_polcal_pipeline = Workflow(
22
+ category="visp",
23
+ input_data="l0",
24
+ output_data="trend",
25
+ detail="polcal",
26
+ workflow_package=__package__,
27
+ )
28
+ visp_polcal_pipeline.add_node(task=TransferL0Data, upstreams=None)
29
+ visp_polcal_pipeline.add_node(task=ParseL0TrendInputData, upstreams=TransferL0Data)
30
+
31
+ output_upstreams = []
32
+ for arm_id in VispArms:
33
+ dark_task = retreive_arm_task(DarkCalibration, arm_id=arm_id)
34
+ gain_task = retreive_arm_task(GainCalibration, arm_id=arm_id)
35
+ geo_task = retreive_arm_task(VispGeometricCalibration, arm_id=arm_id)
36
+ prep_data_task = retreive_arm_task(VispPrepareFitData, arm_id=arm_id)
37
+ run_fit_task = retreive_arm_task(RunPacFitter, arm_id=arm_id)
38
+ process_demod_task = retreive_arm_task(VispProcessDemodulationMatrices, arm_id=arm_id)
39
+ dmpd_task = retreive_arm_task(VispDemodulatedPolcalData, arm_id=arm_id)
40
+
41
+ # Generate demod flow
42
+ visp_polcal_pipeline.add_node(task=dark_task, upstreams=ParseL0TrendInputData)
43
+ visp_polcal_pipeline.add_node(task=gain_task, upstreams=dark_task)
44
+ visp_polcal_pipeline.add_node(task=prep_data_task, upstreams=gain_task)
45
+ visp_polcal_pipeline.add_node(task=run_fit_task, upstreams=prep_data_task)
46
+
47
+ # DMPD flow
48
+ visp_polcal_pipeline.add_node(task=geo_task, upstreams=ParseL0TrendInputData)
49
+ visp_polcal_pipeline.add_node(task=process_demod_task, upstreams=run_fit_task)
50
+ visp_polcal_pipeline.add_node(task=dmpd_task, upstreams=[geo_task, process_demod_task])
51
+
52
+ output_upstreams += [dmpd_task]
53
+
54
+ # Output
55
+ visp_polcal_pipeline.add_node(task=WriteTrendOutputs, upstreams=output_upstreams)
56
+ visp_polcal_pipeline.add_node(task=TransferTrendData, upstreams=WriteTrendOutputs)
57
+ visp_polcal_pipeline.add_node(task=PublishTrendCatalogMessages, upstreams=TransferTrendData)
58
+ visp_polcal_pipeline.add_node(task=Teardown, upstreams=PublishTrendCatalogMessages)