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.
- changelog/.gitempty +0 -0
- dkist_processing_trend/__init__.py +10 -0
- dkist_processing_trend/config.py +11 -0
- dkist_processing_trend/models/__init__.py +1 -0
- dkist_processing_trend/models/constants.py +143 -0
- dkist_processing_trend/models/fit_options.py +15 -0
- dkist_processing_trend/models/fits_access.py +65 -0
- dkist_processing_trend/models/instrument.py +35 -0
- dkist_processing_trend/models/instrument_options.py +35 -0
- dkist_processing_trend/models/parameters.py +134 -0
- dkist_processing_trend/models/tags.py +117 -0
- dkist_processing_trend/models/task_name.py +20 -0
- dkist_processing_trend/parsers/__init__.py +1 -0
- dkist_processing_trend/parsers/arm_id.py +103 -0
- dkist_processing_trend/parsers/instrument_unique_bud.py +40 -0
- dkist_processing_trend/parsers/time.py +27 -0
- dkist_processing_trend/parsers/trend_l0_fits_access.py +123 -0
- dkist_processing_trend/tasks/__init__.py +27 -0
- dkist_processing_trend/tasks/arm_task_factory.py +57 -0
- dkist_processing_trend/tasks/dark.py +56 -0
- dkist_processing_trend/tasks/gain.py +74 -0
- dkist_processing_trend/tasks/initialize_arm_tasks.py +50 -0
- dkist_processing_trend/tasks/parse.py +169 -0
- dkist_processing_trend/tasks/prepare_fit_data_base.py +257 -0
- dkist_processing_trend/tasks/run_pac_fitter.py +392 -0
- dkist_processing_trend/tasks/trend_base.py +97 -0
- dkist_processing_trend/tasks/trend_output_data.py +167 -0
- dkist_processing_trend/tasks/visp/__init__.py +6 -0
- dkist_processing_trend/tasks/visp/visp_dmpd.py +410 -0
- dkist_processing_trend/tasks/visp/visp_extract_beam.py +14 -0
- dkist_processing_trend/tasks/visp/visp_geometric.py +260 -0
- dkist_processing_trend/tasks/visp/visp_prep_fit_data.py +162 -0
- dkist_processing_trend/tasks/visp/visp_process_demod.py +236 -0
- dkist_processing_trend/tasks/write_trend.py +663 -0
- dkist_processing_trend/tests/__init__.py +1 -0
- dkist_processing_trend/tests/conftest.py +718 -0
- dkist_processing_trend/tests/local_trial_workflows/__init__.py +0 -0
- dkist_processing_trend/tests/local_trial_workflows/l0_to_trend_visp_polcal.py +294 -0
- dkist_processing_trend/tests/local_trial_workflows/local_trial_helpers.py +488 -0
- dkist_processing_trend/tests/test_arm_task_factory.py +82 -0
- dkist_processing_trend/tests/test_base_tasks.py +86 -0
- dkist_processing_trend/tests/test_constants.py +120 -0
- dkist_processing_trend/tests/test_dark.py +97 -0
- dkist_processing_trend/tests/test_gain.py +135 -0
- dkist_processing_trend/tests/test_parameters.py +149 -0
- dkist_processing_trend/tests/test_parse.py +276 -0
- dkist_processing_trend/tests/test_prep_fit_data_base.py +233 -0
- dkist_processing_trend/tests/test_publish_catalog_messages.py +45 -0
- dkist_processing_trend/tests/test_run_pac_fitter.py +371 -0
- dkist_processing_trend/tests/test_stems.py +75 -0
- dkist_processing_trend/tests/test_transfer_output_data.py +76 -0
- dkist_processing_trend/tests/test_trend_fits_access.py +173 -0
- dkist_processing_trend/tests/test_visp.py +874 -0
- dkist_processing_trend/tests/test_workflows.py +10 -0
- dkist_processing_trend/tests/test_write_trend.py +460 -0
- dkist_processing_trend/workflows/__init__.py +3 -0
- dkist_processing_trend/workflows/visp.py +58 -0
- dkist_processing_trend-0.1.0.dist-info/METADATA +549 -0
- dkist_processing_trend-0.1.0.dist-info/RECORD +66 -0
- dkist_processing_trend-0.1.0.dist-info/WHEEL +5 -0
- dkist_processing_trend-0.1.0.dist-info/top_level.txt +3 -0
- docs/conf.py +57 -0
- docs/index.rst +10 -0
- docs/l0_to_trend_visp_polcal.rst +4 -0
- docs/landing_page.rst +11 -0
- docs/requirements_table.rst +8 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import astropy.units as u
|
|
4
|
+
import pytest
|
|
5
|
+
from astropy.time.core import Time
|
|
6
|
+
from astropy.time.core import TimeDelta
|
|
7
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
8
|
+
from dkist_processing_common.models.constants import BudName
|
|
9
|
+
|
|
10
|
+
from dkist_processing_trend.models.constants import TrendBudName
|
|
11
|
+
from dkist_processing_trend.models.instrument import InstrumentName
|
|
12
|
+
from dkist_processing_trend.models.parameters import TrendParsingParameters
|
|
13
|
+
from dkist_processing_trend.models.tags import TrendTag
|
|
14
|
+
from dkist_processing_trend.tasks.parse import ParseL0TrendInputData
|
|
15
|
+
from dkist_processing_trend.tests.conftest import ARM_ANGLE_FOR_ARM
|
|
16
|
+
from dkist_processing_trend.tests.conftest import CRYO_OD_FILTER_FOR_ARM
|
|
17
|
+
from dkist_processing_trend.tests.conftest import CRYO_SLIT_WIDTH_FOR_ARM
|
|
18
|
+
from dkist_processing_trend.tests.conftest import GRATING_ANGLE_FOR_ARM
|
|
19
|
+
from dkist_processing_trend.tests.conftest import GRATING_CONSTANT_FOR_ARM
|
|
20
|
+
from dkist_processing_trend.tests.conftest import WAVELENGTH_FOR_ARM
|
|
21
|
+
from dkist_processing_trend.tests.conftest import TrendInputDatasetParameterValues
|
|
22
|
+
from dkist_processing_trend.tests.conftest import write_calibration_sequence_frames_to_task
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def input_parse_task(tmp_path, recipe_run_id, assign_input_dataset_doc_to_task):
|
|
27
|
+
with ParseL0TrendInputData(
|
|
28
|
+
recipe_run_id=recipe_run_id,
|
|
29
|
+
workflow_name="workflow_name",
|
|
30
|
+
workflow_version="workflow_version",
|
|
31
|
+
) as task:
|
|
32
|
+
task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
|
|
33
|
+
assign_input_dataset_doc_to_task(
|
|
34
|
+
task=task,
|
|
35
|
+
parameter_values=TrendInputDatasetParameterValues(),
|
|
36
|
+
)
|
|
37
|
+
task.parameters = TrendParsingParameters(scratch=task.scratch)
|
|
38
|
+
|
|
39
|
+
yield task
|
|
40
|
+
task._purge()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.parametrize(
|
|
44
|
+
"instrument, arm_id_list, op_id_list, expected_op_id_constant, experiment_id_list, expected_experiment_id_constant, proposal_id_list, expected_proposal_id_constant, hls_list, expected_hls_version_constant",
|
|
45
|
+
[
|
|
46
|
+
pytest.param(
|
|
47
|
+
InstrumentName.visp,
|
|
48
|
+
[1, 2, 3],
|
|
49
|
+
["OP1"],
|
|
50
|
+
["OP1"],
|
|
51
|
+
["EX1"],
|
|
52
|
+
["EX1"],
|
|
53
|
+
["PROP1"],
|
|
54
|
+
["PROP1"],
|
|
55
|
+
["hlsv1"],
|
|
56
|
+
["hlsv1"],
|
|
57
|
+
id="visp",
|
|
58
|
+
),
|
|
59
|
+
pytest.param(
|
|
60
|
+
InstrumentName.cryonirsp,
|
|
61
|
+
["CI", "SP"],
|
|
62
|
+
["OP1"],
|
|
63
|
+
["OP1"],
|
|
64
|
+
["EX1"],
|
|
65
|
+
["EX1"],
|
|
66
|
+
["PROP1"],
|
|
67
|
+
["PROP1"],
|
|
68
|
+
["hlsv1"],
|
|
69
|
+
["hlsv1"],
|
|
70
|
+
id="cryonirsp",
|
|
71
|
+
),
|
|
72
|
+
pytest.param(
|
|
73
|
+
InstrumentName.dlnirsp,
|
|
74
|
+
["VIS", "JBand", "HBand"],
|
|
75
|
+
["OP1"],
|
|
76
|
+
["OP1"],
|
|
77
|
+
["EX1"],
|
|
78
|
+
["EX1"],
|
|
79
|
+
["PROP1"],
|
|
80
|
+
["PROP1"],
|
|
81
|
+
["hlsv1"],
|
|
82
|
+
["hlsv1"],
|
|
83
|
+
id="dlnirsp",
|
|
84
|
+
),
|
|
85
|
+
pytest.param(
|
|
86
|
+
InstrumentName.visp,
|
|
87
|
+
[1, 2],
|
|
88
|
+
["OP2", "OP1", "OP2"],
|
|
89
|
+
["OP2", "OP1"],
|
|
90
|
+
["EX6", "EX6", "EX7"],
|
|
91
|
+
["EX6", "EX7"],
|
|
92
|
+
["PROP1", "PROP1", "PROP1"],
|
|
93
|
+
["PROP1"],
|
|
94
|
+
["hlsv1", "hlsv2", "hlsv2"],
|
|
95
|
+
["hlsv2", "hlsv1"],
|
|
96
|
+
id="multi_OP",
|
|
97
|
+
),
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
def test_parse_input_data(
|
|
101
|
+
input_parse_task,
|
|
102
|
+
instrument,
|
|
103
|
+
arm_id_list,
|
|
104
|
+
op_id_list,
|
|
105
|
+
expected_op_id_constant,
|
|
106
|
+
experiment_id_list,
|
|
107
|
+
expected_experiment_id_constant,
|
|
108
|
+
proposal_id_list,
|
|
109
|
+
expected_proposal_id_constant,
|
|
110
|
+
hls_list,
|
|
111
|
+
expected_hls_version_constant,
|
|
112
|
+
visp_grating_angle,
|
|
113
|
+
visp_arm_position,
|
|
114
|
+
visp_grating_constant,
|
|
115
|
+
cryonirsp_od_filter_name,
|
|
116
|
+
cryonirsp_grating_angle,
|
|
117
|
+
cryonirsp_grating_littrow_angle,
|
|
118
|
+
cryonirsp_grating_constant,
|
|
119
|
+
cryonirsp_slit_width,
|
|
120
|
+
dlnirsp_grating_angle,
|
|
121
|
+
dlnirsp_arm_position,
|
|
122
|
+
dlnirsp_grating_constant,
|
|
123
|
+
small_calibration_sequence,
|
|
124
|
+
small_num_gain_steps,
|
|
125
|
+
small_num_dark_steps,
|
|
126
|
+
small_num_cs_steps,
|
|
127
|
+
):
|
|
128
|
+
"""
|
|
129
|
+
Given: A set of INPUT frames and a Parse task
|
|
130
|
+
When: Parsing the frames
|
|
131
|
+
Then: The frames are tagged correctly and constants are populated correctly
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
task = input_parse_task
|
|
135
|
+
|
|
136
|
+
pol_status, pol_theta, ret_status, ret_theta, dark_status = small_calibration_sequence
|
|
137
|
+
num_modstates = 3
|
|
138
|
+
num_ops = len(op_id_list)
|
|
139
|
+
start_time = Time("2026-02-01T15:00:12")
|
|
140
|
+
|
|
141
|
+
for i, (op_id, ex_id, prop_id, hls_v) in enumerate(
|
|
142
|
+
zip(op_id_list, experiment_id_list, proposal_id_list, hls_list)
|
|
143
|
+
):
|
|
144
|
+
polcal_op_start_time = (start_time + i * TimeDelta(1 * u.day)).fits
|
|
145
|
+
for arm_id in arm_id_list:
|
|
146
|
+
write_calibration_sequence_frames_to_task(
|
|
147
|
+
task=task,
|
|
148
|
+
instrument=instrument,
|
|
149
|
+
arm_id=arm_id,
|
|
150
|
+
wavelength=WAVELENGTH_FOR_ARM[arm_id],
|
|
151
|
+
num_modstates=num_modstates,
|
|
152
|
+
pol_status=pol_status,
|
|
153
|
+
pol_theta=pol_theta,
|
|
154
|
+
ret_status=ret_status,
|
|
155
|
+
ret_theta=ret_theta,
|
|
156
|
+
dark_status=dark_status,
|
|
157
|
+
array_shape=(2, 2),
|
|
158
|
+
input_tags=[TrendTag.input()],
|
|
159
|
+
op_start_time=polcal_op_start_time,
|
|
160
|
+
op_id=op_id,
|
|
161
|
+
experiment_id=ex_id,
|
|
162
|
+
proposal_id=prop_id,
|
|
163
|
+
hls_version=hls_v,
|
|
164
|
+
visp_grating_angle=GRATING_ANGLE_FOR_ARM[arm_id],
|
|
165
|
+
visp_arm_position=ARM_ANGLE_FOR_ARM[arm_id],
|
|
166
|
+
visp_grating_constant=GRATING_CONSTANT_FOR_ARM[arm_id],
|
|
167
|
+
cryonirsp_od_filter_name=CRYO_OD_FILTER_FOR_ARM[arm_id],
|
|
168
|
+
cryonirsp_grating_angle=GRATING_ANGLE_FOR_ARM[arm_id],
|
|
169
|
+
cryonirsp_grating_littrow_angle=ARM_ANGLE_FOR_ARM[arm_id],
|
|
170
|
+
cryonirsp_grating_constant=GRATING_CONSTANT_FOR_ARM[arm_id],
|
|
171
|
+
cryonirsp_slit_width=CRYO_SLIT_WIDTH_FOR_ARM[arm_id],
|
|
172
|
+
dlnirsp_grating_angle=GRATING_ANGLE_FOR_ARM[arm_id],
|
|
173
|
+
dlnirsp_arm_position=ARM_ANGLE_FOR_ARM[arm_id],
|
|
174
|
+
dlnirsp_grating_constant=GRATING_CONSTANT_FOR_ARM[arm_id],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
task()
|
|
178
|
+
|
|
179
|
+
# Tags applied correctly
|
|
180
|
+
for arm_id in arm_id_list:
|
|
181
|
+
for step in range(small_num_cs_steps * num_ops):
|
|
182
|
+
for modstate in range(1, num_modstates + 1):
|
|
183
|
+
assert (
|
|
184
|
+
task.count(
|
|
185
|
+
tags=[
|
|
186
|
+
TrendTag.task_polcal(),
|
|
187
|
+
TrendTag.arm_id(arm_id),
|
|
188
|
+
TrendTag.cs_step(step),
|
|
189
|
+
TrendTag.modstate(modstate),
|
|
190
|
+
]
|
|
191
|
+
)
|
|
192
|
+
== 1
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
for arm_id in arm_id_list:
|
|
196
|
+
for modstate in range(1, num_modstates + 1):
|
|
197
|
+
assert (
|
|
198
|
+
task.count(
|
|
199
|
+
tags=[
|
|
200
|
+
TrendTag.arm_id(arm_id),
|
|
201
|
+
TrendTag.modstate(modstate),
|
|
202
|
+
TrendTag.task_polcal_gain(),
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
== small_num_gain_steps * num_ops
|
|
206
|
+
)
|
|
207
|
+
assert (
|
|
208
|
+
task.count(
|
|
209
|
+
tags=[
|
|
210
|
+
TrendTag.arm_id(arm_id),
|
|
211
|
+
TrendTag.modstate(modstate),
|
|
212
|
+
TrendTag.task_polcal_dark(),
|
|
213
|
+
]
|
|
214
|
+
)
|
|
215
|
+
== small_num_dark_steps * num_ops
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Constants parsed correctly
|
|
219
|
+
# Wrap both side in `Time(...).fits` to ensure the same formatting of decimal places in string
|
|
220
|
+
assert (
|
|
221
|
+
Time(task.constants._db_dict[TrendBudName.earliest_ip_start_time]).fits == start_time.fits
|
|
222
|
+
)
|
|
223
|
+
assert task.constants._db_dict[TrendBudName.wavelength_for_arm] == {
|
|
224
|
+
json.dumps(arm): WAVELENGTH_FOR_ARM[arm] for arm in arm_id_list
|
|
225
|
+
}
|
|
226
|
+
assert task.constants._db_dict[BudName.instrument] == instrument
|
|
227
|
+
assert task.constants._db_dict[BudName.num_cs_steps] == small_num_cs_steps * num_ops
|
|
228
|
+
assert task.constants._db_dict[BudName.num_modstates] == num_modstates
|
|
229
|
+
assert sorted(task.constants._db_dict[TrendBudName.arm_id_list]) == sorted(arm_id_list)
|
|
230
|
+
assert (
|
|
231
|
+
task.constants._db_dict[TrendBudName.observing_program_id_list] == expected_op_id_constant
|
|
232
|
+
)
|
|
233
|
+
assert (
|
|
234
|
+
task.constants._db_dict[TrendBudName.experiment_id_list] == expected_experiment_id_constant
|
|
235
|
+
)
|
|
236
|
+
assert task.constants._db_dict[TrendBudName.proposal_id_list] == expected_proposal_id_constant
|
|
237
|
+
assert task.constants._db_dict[TrendBudName.hls_version_list] == expected_hls_version_constant
|
|
238
|
+
|
|
239
|
+
if instrument == InstrumentName.visp:
|
|
240
|
+
assert task.constants._db_dict[TrendBudName.visp_grating_angle_for_arm] == {
|
|
241
|
+
json.dumps(arm): GRATING_ANGLE_FOR_ARM[arm] for arm in arm_id_list
|
|
242
|
+
}
|
|
243
|
+
assert task.constants._db_dict[TrendBudName.visp_arm_position_for_arm] == {
|
|
244
|
+
json.dumps(arm): ARM_ANGLE_FOR_ARM[arm] for arm in arm_id_list
|
|
245
|
+
}
|
|
246
|
+
assert task.constants._db_dict[TrendBudName.visp_grating_constant_for_arm] == {
|
|
247
|
+
json.dumps(arm): GRATING_CONSTANT_FOR_ARM[arm] for arm in arm_id_list
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if instrument == InstrumentName.cryonirsp:
|
|
251
|
+
assert task.constants._db_dict[TrendBudName.cryonirsp_od_filter_name_for_arm] == {
|
|
252
|
+
json.dumps(arm): CRYO_OD_FILTER_FOR_ARM[arm] for arm in arm_id_list
|
|
253
|
+
}
|
|
254
|
+
assert task.constants._db_dict[TrendBudName.cryonirsp_grating_angle_for_arm] == {
|
|
255
|
+
json.dumps(arm): GRATING_ANGLE_FOR_ARM[arm] for arm in arm_id_list
|
|
256
|
+
}
|
|
257
|
+
assert task.constants._db_dict[TrendBudName.cryonirsp_grating_littrow_angle_for_arm] == {
|
|
258
|
+
json.dumps(arm): ARM_ANGLE_FOR_ARM[arm] for arm in arm_id_list
|
|
259
|
+
}
|
|
260
|
+
assert task.constants._db_dict[TrendBudName.cryonirsp_grating_constant_for_arm] == {
|
|
261
|
+
json.dumps(arm): GRATING_CONSTANT_FOR_ARM[arm] for arm in arm_id_list
|
|
262
|
+
}
|
|
263
|
+
assert task.constants._db_dict[TrendBudName.cryonirsp_slit_width_for_arm] == {
|
|
264
|
+
json.dumps(arm): CRYO_SLIT_WIDTH_FOR_ARM[arm] for arm in arm_id_list
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if instrument == InstrumentName.dlnirsp:
|
|
268
|
+
assert task.constants._db_dict[TrendBudName.dlnirsp_grating_angle_for_arm] == {
|
|
269
|
+
json.dumps(arm): GRATING_ANGLE_FOR_ARM[arm] for arm in arm_id_list
|
|
270
|
+
}
|
|
271
|
+
assert task.constants._db_dict[TrendBudName.dlnirsp_arm_position_for_arm] == {
|
|
272
|
+
json.dumps(arm): ARM_ANGLE_FOR_ARM[arm] for arm in arm_id_list
|
|
273
|
+
}
|
|
274
|
+
assert task.constants._db_dict[TrendBudName.dlnirsp_grating_constant_for_arm] == {
|
|
275
|
+
json.dumps(arm): GRATING_CONSTANT_FOR_ARM[arm] for arm in arm_id_list
|
|
276
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
4
|
+
from dkist_processing_common.codecs.asdf import asdf_decoder
|
|
5
|
+
from dkist_processing_common.codecs.fits import fits_array_encoder
|
|
6
|
+
|
|
7
|
+
from dkist_processing_trend.models.fits_access import CryonirspMetadataKey
|
|
8
|
+
from dkist_processing_trend.models.instrument import InstrumentName
|
|
9
|
+
from dkist_processing_trend.models.instrument_options import CryonirspInstrumentOptions
|
|
10
|
+
from dkist_processing_trend.models.parameters import TrendParameters
|
|
11
|
+
from dkist_processing_trend.models.tags import TrendTag
|
|
12
|
+
from dkist_processing_trend.tasks.prepare_fit_data_base import PrepareFitDataBase
|
|
13
|
+
from dkist_processing_trend.tests.conftest import ModulatedCSStepHeaders
|
|
14
|
+
from dkist_processing_trend.tests.conftest import TrendInputDatasetParameterValues
|
|
15
|
+
from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
|
|
16
|
+
from dkist_processing_trend.tests.conftest import write_calibration_sequence_frames_to_task
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class InstrumentPrepData(PrepareFitDataBase):
|
|
20
|
+
|
|
21
|
+
def extract_beam(self, array: np.ndarray, beam: int) -> np.ndarray:
|
|
22
|
+
return array[beam - 1 :: 2, :]
|
|
23
|
+
|
|
24
|
+
def apply_global_instrument_options(
|
|
25
|
+
self, array: np.ndarray, instrument_options: CryonirspInstrumentOptions
|
|
26
|
+
) -> np.ndarray:
|
|
27
|
+
return np.mean(array)[None, None]
|
|
28
|
+
|
|
29
|
+
def apply_local_instrument_options(
|
|
30
|
+
self, array: np.ndarray, instrument_options: CryonirspInstrumentOptions
|
|
31
|
+
) -> np.ndarray:
|
|
32
|
+
return np.mean(array, axis=instrument_options.num_spatial_px)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def instrument_options_list() -> dict[str, str | int]:
|
|
37
|
+
# pydantic forces the structure of these dicts. See `apply_local_instrument_options` above for how these parameters
|
|
38
|
+
# are actually used.
|
|
39
|
+
return [
|
|
40
|
+
{"name": "axis0_option", "num_spatial_px": 0, "num_spectral_px": 1},
|
|
41
|
+
{"name": "axis1_option", "num_spatial_px": 1, "num_spectral_px": 1},
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
def num_modstates() -> int:
|
|
47
|
+
return 4
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.fixture
|
|
51
|
+
def dark_signal() -> float:
|
|
52
|
+
return 6.28
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.fixture
|
|
56
|
+
def gain_signal() -> float:
|
|
57
|
+
return 3.14
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture
|
|
61
|
+
def make_data_function(dark_signal, gain_signal):
|
|
62
|
+
def make_data(frame: ModulatedCSStepHeaders) -> np.ndarray:
|
|
63
|
+
shape = frame.array_shape[1:]
|
|
64
|
+
cs_step = frame.cs_step_num
|
|
65
|
+
modstate = frame.current_modstate
|
|
66
|
+
|
|
67
|
+
true_signal = cs_step * 1000 + modstate * 100
|
|
68
|
+
raw_signal = true_signal * gain_signal + dark_signal
|
|
69
|
+
return np.full(shape, raw_signal)
|
|
70
|
+
|
|
71
|
+
return make_data
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.fixture
|
|
75
|
+
def instrument_fit_data_task(
|
|
76
|
+
recipe_run_id,
|
|
77
|
+
tmp_path,
|
|
78
|
+
link_constants_db,
|
|
79
|
+
assign_input_dataset_doc_to_task,
|
|
80
|
+
arm_id,
|
|
81
|
+
instrument_options_list,
|
|
82
|
+
small_num_cs_steps,
|
|
83
|
+
num_modstates,
|
|
84
|
+
):
|
|
85
|
+
link_constants_db(
|
|
86
|
+
recipe_run_id,
|
|
87
|
+
TrendTestingConstantsDatabase(
|
|
88
|
+
INSTRUMENT=InstrumentName.cryonirsp,
|
|
89
|
+
ARM_ID_LIST=["CI", "SP"],
|
|
90
|
+
NUM_CS_STEPS=small_num_cs_steps,
|
|
91
|
+
NUM_MODSTATES=num_modstates,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
with InstrumentPrepData(
|
|
95
|
+
arm_id=arm_id, recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
|
|
96
|
+
) as task:
|
|
97
|
+
task.scratch = WorkflowFileSystem(recipe_run_id=recipe_run_id, scratch_base_path=tmp_path)
|
|
98
|
+
assign_input_dataset_doc_to_task(
|
|
99
|
+
task,
|
|
100
|
+
TrendInputDatasetParameterValues(
|
|
101
|
+
trend_instrument_processing_options_cryonirsp=instrument_options_list
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
task.parameters = TrendParameters(
|
|
105
|
+
scratch=task.scratch,
|
|
106
|
+
obs_ip_start_time=task.constants.earliest_ip_start_time,
|
|
107
|
+
instrument=task.constants.instrument,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
yield task
|
|
111
|
+
task._purge()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@pytest.mark.parametrize("arm_id", [pytest.param("SP"), pytest.param("CI")])
|
|
115
|
+
def test_prepare_data_base(
|
|
116
|
+
instrument_fit_data_task,
|
|
117
|
+
instrument_options_list,
|
|
118
|
+
small_calibration_sequence,
|
|
119
|
+
small_num_cs_steps,
|
|
120
|
+
num_modstates,
|
|
121
|
+
arm_id,
|
|
122
|
+
make_data_function,
|
|
123
|
+
dark_signal,
|
|
124
|
+
gain_signal,
|
|
125
|
+
mocker,
|
|
126
|
+
fake_gql_client,
|
|
127
|
+
):
|
|
128
|
+
"""
|
|
129
|
+
Given: A dummy child class of `PrepareFitDataBase` and a correct set of INPUT and INTERMEDIATE data.
|
|
130
|
+
When: Running the task
|
|
131
|
+
Then: The expected outputs are produced and they have the expected structure and data values
|
|
132
|
+
"""
|
|
133
|
+
mocker.patch(
|
|
134
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
|
|
135
|
+
)
|
|
136
|
+
num_beams = 1 if arm_id == "CI" else 2
|
|
137
|
+
shape = (8, 9)
|
|
138
|
+
task = instrument_fit_data_task
|
|
139
|
+
|
|
140
|
+
pol_status, pol_theta, ret_status, ret_theta, dark_status = small_calibration_sequence
|
|
141
|
+
|
|
142
|
+
write_calibration_sequence_frames_to_task(
|
|
143
|
+
task=task,
|
|
144
|
+
instrument=InstrumentName.cryonirsp,
|
|
145
|
+
arm_id=arm_id,
|
|
146
|
+
wavelength=1074.7,
|
|
147
|
+
num_modstates=num_modstates,
|
|
148
|
+
pol_status=pol_status,
|
|
149
|
+
pol_theta=pol_theta,
|
|
150
|
+
ret_status=ret_status,
|
|
151
|
+
ret_theta=ret_theta,
|
|
152
|
+
dark_status=dark_status,
|
|
153
|
+
array_shape=shape,
|
|
154
|
+
tags=[TrendTag.arm_id(arm_id)],
|
|
155
|
+
data_func=make_data_function,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# INTERMEIDATE dark and gain frames
|
|
159
|
+
task.write(
|
|
160
|
+
data=np.full(shape, dark_signal),
|
|
161
|
+
tags=[
|
|
162
|
+
TrendTag.intermediate(),
|
|
163
|
+
TrendTag.frame(),
|
|
164
|
+
TrendTag.arm_id(arm_id),
|
|
165
|
+
TrendTag.task_dark(),
|
|
166
|
+
],
|
|
167
|
+
encoder=fits_array_encoder,
|
|
168
|
+
)
|
|
169
|
+
task.write(
|
|
170
|
+
data=np.full(shape, gain_signal),
|
|
171
|
+
tags=[
|
|
172
|
+
TrendTag.intermediate(),
|
|
173
|
+
TrendTag.frame(),
|
|
174
|
+
TrendTag.arm_id(arm_id),
|
|
175
|
+
TrendTag.task_gain(),
|
|
176
|
+
],
|
|
177
|
+
encoder=fits_array_encoder,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
task()
|
|
181
|
+
|
|
182
|
+
for inst_opt in instrument_options_list:
|
|
183
|
+
|
|
184
|
+
# See `InstrumentPrepData.apply_local_instrument_options` above for why these values are what they are
|
|
185
|
+
if inst_opt["num_spatial_px"] == 0:
|
|
186
|
+
expected_local_shape = (9,)
|
|
187
|
+
elif inst_opt["num_spatial_px"] == 1:
|
|
188
|
+
expected_local_shape = (4,)
|
|
189
|
+
|
|
190
|
+
for beam in range(1, num_beams + 1):
|
|
191
|
+
global_tags = [
|
|
192
|
+
TrendTag.intermediate(),
|
|
193
|
+
TrendTag.arm_id(arm_id),
|
|
194
|
+
TrendTag.beam(beam),
|
|
195
|
+
TrendTag.instrument_processing_options(inst_opt["name"]),
|
|
196
|
+
TrendTag.task_global_pac_input(),
|
|
197
|
+
]
|
|
198
|
+
local_tags = [
|
|
199
|
+
TrendTag.intermediate(),
|
|
200
|
+
TrendTag.arm_id(arm_id),
|
|
201
|
+
TrendTag.beam(beam),
|
|
202
|
+
TrendTag.instrument_processing_options(inst_opt["name"]),
|
|
203
|
+
TrendTag.task_local_pac_input(),
|
|
204
|
+
]
|
|
205
|
+
assert task.count(global_tags) == 1
|
|
206
|
+
assert task.count(local_tags) == 1
|
|
207
|
+
|
|
208
|
+
global_dict = next(task.read(tags=global_tags, decoder=asdf_decoder))
|
|
209
|
+
local_dict = next(task.read(tags=local_tags, decoder=asdf_decoder))
|
|
210
|
+
for cs_step in range(small_num_cs_steps):
|
|
211
|
+
global_modstate_list = global_dict[cs_step]
|
|
212
|
+
local_modstate_list = local_dict[cs_step]
|
|
213
|
+
assert isinstance(global_modstate_list, list)
|
|
214
|
+
assert isinstance(local_modstate_list, list)
|
|
215
|
+
assert len(global_modstate_list) == len(local_modstate_list) == num_modstates
|
|
216
|
+
for m in range(num_modstates):
|
|
217
|
+
global_mod_dict = global_modstate_list[m]
|
|
218
|
+
global_header = global_mod_dict["header"]
|
|
219
|
+
assert global_header[CryonirspMetadataKey.modulator_state] == m + 1
|
|
220
|
+
global_data = global_mod_dict["data"]
|
|
221
|
+
assert global_data.shape == (1, 1)
|
|
222
|
+
np.testing.assert_array_equal(global_data, 1000 * cs_step + 100 * (m + 1))
|
|
223
|
+
|
|
224
|
+
local_mod_dict = local_modstate_list[m]
|
|
225
|
+
local_header = local_mod_dict["header"]
|
|
226
|
+
assert local_header[CryonirspMetadataKey.modulator_state] == m + 1
|
|
227
|
+
local_data = local_mod_dict["data"]
|
|
228
|
+
assert local_data.shape == expected_local_shape
|
|
229
|
+
np.testing.assert_array_equal(local_data, 1000 * cs_step + 100 * (m + 1))
|
|
230
|
+
|
|
231
|
+
# These asserts are checking that there aren't any extra CS steps beyond what we expect
|
|
232
|
+
assert small_num_cs_steps not in global_dict.keys()
|
|
233
|
+
assert small_num_cs_steps not in local_dict.keys()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from dkist_processing_trend.tasks.trend_output_data import PublishTrendCatalogMessages
|
|
6
|
+
from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def publish_catalog_and_quality_messages_task(
|
|
11
|
+
recipe_run_id, link_constants_db, mocker, fake_gql_client
|
|
12
|
+
):
|
|
13
|
+
instrument = "DOPE"
|
|
14
|
+
full_date = "2024-01-02T12:34:56.2341234234"
|
|
15
|
+
dir_date = "20240102T123456"
|
|
16
|
+
link_constants_db(
|
|
17
|
+
recipe_run_id,
|
|
18
|
+
TrendTestingConstantsDatabase(INSTRUMENT=instrument, EARLIEST_IP_START_TIME=full_date),
|
|
19
|
+
)
|
|
20
|
+
mocker.patch(
|
|
21
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
|
|
22
|
+
)
|
|
23
|
+
with PublishTrendCatalogMessages(
|
|
24
|
+
recipe_run_id=recipe_run_id,
|
|
25
|
+
workflow_name="workflow_name",
|
|
26
|
+
workflow_version="workflow_version",
|
|
27
|
+
) as task:
|
|
28
|
+
yield task, instrument, dir_date
|
|
29
|
+
task.constants._purge()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_frame_messages(publish_catalog_and_quality_messages_task):
|
|
33
|
+
"""
|
|
34
|
+
Given: a PublishCatalogAndQualityMessages task
|
|
35
|
+
When: creating frame messages
|
|
36
|
+
Then: the attributes are correctly populated
|
|
37
|
+
"""
|
|
38
|
+
task, instrument, dir_date = publish_catalog_and_quality_messages_task
|
|
39
|
+
filenames = [f"test_frame_{i}.ext" for i in range(10)]
|
|
40
|
+
filepaths = [Path(f"a/b/c/{filename}") for filename in filenames]
|
|
41
|
+
frame_messages = task.frame_messages(paths=filepaths)
|
|
42
|
+
for message in frame_messages:
|
|
43
|
+
assert message.body.bucket == "data"
|
|
44
|
+
assert str(Path(message.body.objectName).parent) == f"trend/polcal/{dir_date}/{instrument}"
|
|
45
|
+
assert message.body.conversationId == str(task.recipe_run_id)
|