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,120 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import asdict
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import field
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from dkist_processing_common.tasks.base import WorkflowTaskBase
|
|
8
|
+
|
|
9
|
+
from dkist_processing_trend.models.constants import TrendBudName
|
|
10
|
+
from dkist_processing_trend.models.constants import TrendConstants
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class TrendOnlyConstantDB:
|
|
15
|
+
earliest_ip_start_time: str = "1900-01-01"
|
|
16
|
+
arm_id_list: tuple[str | int] = (1, 2, 3)
|
|
17
|
+
wavelength_for_arm: dict[str, float] = field(default_factory=lambda: {"1": 123.4, '"CI"': 567})
|
|
18
|
+
observing_program_id_list: tuple[str] = ("TOP1", "TOP2")
|
|
19
|
+
experiment_id_list: tuple[str] = "EX1"
|
|
20
|
+
proposal_id_list: tuple[str] = "PROP5"
|
|
21
|
+
hls_version_list: tuple[str] = ("Tau 6.2.8", "Pi 3.14")
|
|
22
|
+
visp_grating_angle_for_arm: dict[str, float] = field(
|
|
23
|
+
default_factory=lambda: {"1": 9.87, "2": 9.872}
|
|
24
|
+
)
|
|
25
|
+
visp_arm_position_for_arm: dict[str, float] = field(
|
|
26
|
+
default_factory=lambda: {"1": 8.76, "2": 8.762}
|
|
27
|
+
)
|
|
28
|
+
visp_grating_constant_for_arm: dict[str, float] = field(
|
|
29
|
+
default_factory=lambda: {"1": 9870, "2": 9872}
|
|
30
|
+
)
|
|
31
|
+
cryonirsp_od_filter_name_for_arm: dict[str, str] = field(
|
|
32
|
+
default_factory=lambda: {'"CI"': "CRYOFILT"}
|
|
33
|
+
)
|
|
34
|
+
cryonirsp_grating_angle_for_arm: dict[str, float] = field(
|
|
35
|
+
default_factory=lambda: {'"CI"': 123.4, '"SP"': 123.5}
|
|
36
|
+
)
|
|
37
|
+
cryonirsp_grating_littrow_angle_for_arm: dict[str, float] = field(
|
|
38
|
+
default_factory=lambda: {'"CI"': 12.34, '"SP"': 12.35}
|
|
39
|
+
)
|
|
40
|
+
cryonirsp_grating_constant_for_arm: dict[str, float] = field(
|
|
41
|
+
default_factory=lambda: {'"SP"': 1234}
|
|
42
|
+
)
|
|
43
|
+
cryonirsp_slit_width_for_arm: dict[str, float] = field(default_factory=lambda: {'"CI"': 0.1234})
|
|
44
|
+
dlnirsp_grating_angle_for_arm: dict[str, float] = field(
|
|
45
|
+
default_factory=lambda: {'"VIS"': 2.22, '"JBand"': 2.222, '"HBand"': 2.2222}
|
|
46
|
+
)
|
|
47
|
+
dlnirsp_arm_position_for_arm: dict[str, float] = field(
|
|
48
|
+
default_factory=lambda: {'"JBand"': 3.33}
|
|
49
|
+
)
|
|
50
|
+
dlnirsp_grating_constant_for_arm: dict[str, float] = field(
|
|
51
|
+
default_factory=lambda: {'"HBand"': 4444}
|
|
52
|
+
)
|
|
53
|
+
# And one "common" constant just to confirm subclassing
|
|
54
|
+
instrument: str = "an instrument"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.fixture(scope="session")
|
|
58
|
+
def arm_id_lookup_constant_names():
|
|
59
|
+
return {
|
|
60
|
+
"wavelength_for_arm",
|
|
61
|
+
"visp_grating_angle_for_arm",
|
|
62
|
+
"visp_arm_position_for_arm",
|
|
63
|
+
"visp_grating_constant_for_arm",
|
|
64
|
+
"cryonirsp_od_filter_name_for_arm",
|
|
65
|
+
"cryonirsp_grating_angle_for_arm",
|
|
66
|
+
"cryonirsp_grating_littrow_angle_for_arm",
|
|
67
|
+
"cryonirsp_grating_constant_for_arm",
|
|
68
|
+
"cryonirsp_slit_width_for_arm",
|
|
69
|
+
"dlnirsp_grating_angle_for_arm",
|
|
70
|
+
"dlnirsp_arm_position_for_arm",
|
|
71
|
+
"dlnirsp_grating_constant_for_arm",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.fixture
|
|
76
|
+
def trend_constants_db():
|
|
77
|
+
values = TrendOnlyConstantDB()
|
|
78
|
+
return {k.upper(): v for k, v in asdict(values).items()}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.fixture
|
|
82
|
+
def task_with_trend_constants(recipe_run_id, link_constants_db, trend_constants_db):
|
|
83
|
+
class Task(WorkflowTaskBase):
|
|
84
|
+
@property
|
|
85
|
+
def constants_model_class(self):
|
|
86
|
+
return TrendConstants
|
|
87
|
+
|
|
88
|
+
def run(self): ...
|
|
89
|
+
|
|
90
|
+
link_constants_db(recipe_run_id, trend_constants_db)
|
|
91
|
+
with Task(
|
|
92
|
+
recipe_run_id=recipe_run_id,
|
|
93
|
+
workflow_name="workflow_name",
|
|
94
|
+
workflow_version="workflow_version",
|
|
95
|
+
) as task:
|
|
96
|
+
yield task
|
|
97
|
+
task._purge()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_trend_constants(
|
|
101
|
+
task_with_trend_constants, trend_constants_db, arm_id_lookup_constant_names
|
|
102
|
+
):
|
|
103
|
+
"""
|
|
104
|
+
Given: A task whose `constants_model_class` property is `TrendConstants`
|
|
105
|
+
When: Accessing the constants implied by `TrendBudName`
|
|
106
|
+
Then: The property on `.constants` matches the correct value in the constants db
|
|
107
|
+
"""
|
|
108
|
+
task = task_with_trend_constants
|
|
109
|
+
task_constants = task.constants
|
|
110
|
+
assert task_constants.instrument == trend_constants_db["INSTRUMENT"]
|
|
111
|
+
for constant in TrendBudName:
|
|
112
|
+
expected_value = trend_constants_db[constant.value]
|
|
113
|
+
if isinstance(expected_value, tuple):
|
|
114
|
+
# B/c JSON serializes/deserializes lists as tuples
|
|
115
|
+
expected_value = list(expected_value)
|
|
116
|
+
if constant.name in arm_id_lookup_constant_names:
|
|
117
|
+
expected_value = {json.loads(k): v for k, v in expected_value.items()}
|
|
118
|
+
assert (
|
|
119
|
+
getattr(task_constants, constant.name) == expected_value
|
|
120
|
+
), f"{constant.name} does not match"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
4
|
+
from dkist_processing_common.codecs.fits import fits_array_decoder
|
|
5
|
+
|
|
6
|
+
from dkist_processing_trend.models.instrument import InstrumentName
|
|
7
|
+
from dkist_processing_trend.models.tags import TrendTag
|
|
8
|
+
from dkist_processing_trend.tasks.dark import DarkCalibration
|
|
9
|
+
from dkist_processing_trend.tests.conftest import ModulatedCSStepHeaders
|
|
10
|
+
from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
|
|
11
|
+
from dkist_processing_trend.tests.conftest import write_calibration_sequence_frames_to_task
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def arm_id() -> str:
|
|
16
|
+
return "arm2"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def dark_task(tmp_path, recipe_run_id, link_constants_db, arm_id):
|
|
21
|
+
|
|
22
|
+
link_constants_db(recipe_run_id, TrendTestingConstantsDatabase(ARM_ID_LIST=(1, "arm3", "arm2")))
|
|
23
|
+
with DarkCalibration(
|
|
24
|
+
arm_id=arm_id, recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
|
|
25
|
+
) as task:
|
|
26
|
+
task.scratch = WorkflowFileSystem(recipe_run_id=recipe_run_id, scratch_base_path=tmp_path)
|
|
27
|
+
yield task
|
|
28
|
+
task._purge
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def correct_arm_dark_signal_from_modstate(frame: ModulatedCSStepHeaders) -> np.ndarray:
|
|
32
|
+
shape = frame.array_shape
|
|
33
|
+
signal = frame.current_modstate * 100
|
|
34
|
+
return np.full(shape, signal)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def wrong_arm_dark_signal_from_modstate(frame: ModulatedCSStepHeaders) -> np.ndarray:
|
|
38
|
+
return correct_arm_dark_signal_from_modstate(frame) * 1000
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_dark_task(dark_task, arm_id, mocker, fake_gql_client):
|
|
42
|
+
"""
|
|
43
|
+
Given: A `DarkCalibration` task and some frames tagged with POLCAL_DARK in *two different arms*
|
|
44
|
+
When: Running the task instantiated with a single arm
|
|
45
|
+
Then: The correct dark frame is made and did not include data from arms other than the task was instantiated with
|
|
46
|
+
"""
|
|
47
|
+
mocker.patch(
|
|
48
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
|
|
49
|
+
)
|
|
50
|
+
task = dark_task
|
|
51
|
+
shape = (3, 3)
|
|
52
|
+
|
|
53
|
+
# Write POLCAL_DARK data for the arm we want
|
|
54
|
+
write_calibration_sequence_frames_to_task(
|
|
55
|
+
task=task,
|
|
56
|
+
instrument=InstrumentName.visp,
|
|
57
|
+
arm_id=arm_id,
|
|
58
|
+
wavelength=636.0,
|
|
59
|
+
pol_status=["clear"],
|
|
60
|
+
pol_theta=[99.0],
|
|
61
|
+
ret_status=["clear"],
|
|
62
|
+
ret_theta=[99.0],
|
|
63
|
+
dark_status=["DarkShutter"],
|
|
64
|
+
num_modstates=4,
|
|
65
|
+
array_shape=shape,
|
|
66
|
+
data_func=correct_arm_dark_signal_from_modstate,
|
|
67
|
+
tags=[TrendTag.task_polcal_dark(), TrendTag.arm_id(arm_id)],
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Now the same, but for a different arm with totally different signal
|
|
71
|
+
write_calibration_sequence_frames_to_task(
|
|
72
|
+
task=task,
|
|
73
|
+
instrument=InstrumentName.visp,
|
|
74
|
+
arm_id="wrong_arm",
|
|
75
|
+
wavelength=636.0,
|
|
76
|
+
pol_status=["clear"],
|
|
77
|
+
pol_theta=[99.0],
|
|
78
|
+
ret_status=["clear"],
|
|
79
|
+
ret_theta=[99.0],
|
|
80
|
+
dark_status=["DarkShutter"],
|
|
81
|
+
num_modstates=4,
|
|
82
|
+
array_shape=(2, 2), # Different!
|
|
83
|
+
data_func=wrong_arm_dark_signal_from_modstate,
|
|
84
|
+
tags=[TrendTag.task_polcal_dark(), TrendTag.arm_id("wrong_arm")],
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
task()
|
|
88
|
+
|
|
89
|
+
tags = [
|
|
90
|
+
TrendTag.arm_id(arm_id),
|
|
91
|
+
TrendTag.intermediate(),
|
|
92
|
+
TrendTag.frame(),
|
|
93
|
+
TrendTag.task_dark(),
|
|
94
|
+
]
|
|
95
|
+
assert task.count(tags=tags) == 1
|
|
96
|
+
dark_array = next(task.read(tags=tags, decoder=fits_array_decoder))
|
|
97
|
+
np.testing.assert_array_equal(dark_array, 250.0) # avg of [1, 2, 3, 4] * 100
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
6
|
+
from dkist_processing_common.codecs.fits import fits_array_decoder
|
|
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.tags import TrendTag
|
|
11
|
+
from dkist_processing_trend.tasks.gain import GainCalibration
|
|
12
|
+
from dkist_processing_trend.tests.conftest import ModulatedCSStepHeaders
|
|
13
|
+
from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
|
|
14
|
+
from dkist_processing_trend.tests.conftest import write_calibration_sequence_frames_to_task
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def arm_id() -> str:
|
|
19
|
+
return "arm2"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def gain_task(tmp_path, recipe_run_id, link_constants_db, arm_id):
|
|
24
|
+
|
|
25
|
+
link_constants_db(recipe_run_id, TrendTestingConstantsDatabase(ARM_ID_LIST=(1, "arm3", "arm2")))
|
|
26
|
+
with GainCalibration(
|
|
27
|
+
arm_id=arm_id, recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
|
|
28
|
+
) as task:
|
|
29
|
+
task.scratch = WorkflowFileSystem(recipe_run_id=recipe_run_id, scratch_base_path=tmp_path)
|
|
30
|
+
yield task
|
|
31
|
+
task._purge
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def dark_signal() -> float:
|
|
36
|
+
return 314.0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def correct_arm_gain_signal_from_modstate(
|
|
40
|
+
frame: ModulatedCSStepHeaders, dark_signal: float
|
|
41
|
+
) -> np.ndarray:
|
|
42
|
+
shape = frame.array_shape
|
|
43
|
+
signal = frame.current_modstate * 1000
|
|
44
|
+
return np.full(shape, signal) + dark_signal
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def wrong_arm_gain_signal_from_modstate(
|
|
48
|
+
frame: ModulatedCSStepHeaders, dark_signal: float
|
|
49
|
+
) -> np.ndarray:
|
|
50
|
+
return correct_arm_gain_signal_from_modstate(frame, dark_signal=0) * 1000 + dark_signal
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_gain_task(gain_task, arm_id, dark_signal, mocker, fake_gql_client):
|
|
54
|
+
"""
|
|
55
|
+
Given: A `GainCalibration` task and some frames tagged with POLCAL_GAIN in *two different arms*
|
|
56
|
+
When: Running the task instantiated with a single arm
|
|
57
|
+
Then: The correct gain frame is made and did not include data from arms other than the task was instantiated with
|
|
58
|
+
"""
|
|
59
|
+
mocker.patch(
|
|
60
|
+
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
|
|
61
|
+
)
|
|
62
|
+
task = gain_task
|
|
63
|
+
shape = (3, 3)
|
|
64
|
+
|
|
65
|
+
# Write POLCAL_GAIN data for the arm we want
|
|
66
|
+
write_calibration_sequence_frames_to_task(
|
|
67
|
+
task=task,
|
|
68
|
+
instrument=InstrumentName.visp,
|
|
69
|
+
arm_id=arm_id,
|
|
70
|
+
wavelength=636.0,
|
|
71
|
+
pol_status=["clear"],
|
|
72
|
+
pol_theta=[99.0],
|
|
73
|
+
ret_status=["clear"],
|
|
74
|
+
ret_theta=[99.0],
|
|
75
|
+
dark_status=["clear"],
|
|
76
|
+
num_modstates=4,
|
|
77
|
+
array_shape=shape,
|
|
78
|
+
data_func=partial(correct_arm_gain_signal_from_modstate, dark_signal=dark_signal),
|
|
79
|
+
tags=[TrendTag.task_polcal_gain(), TrendTag.arm_id(arm_id)],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Now the same, but for a different arm with totally different signal
|
|
83
|
+
write_calibration_sequence_frames_to_task(
|
|
84
|
+
task=task,
|
|
85
|
+
instrument=InstrumentName.visp,
|
|
86
|
+
arm_id="wrong_arm",
|
|
87
|
+
wavelength=636.0,
|
|
88
|
+
pol_status=["clear"],
|
|
89
|
+
pol_theta=[99.0],
|
|
90
|
+
ret_status=["clear"],
|
|
91
|
+
ret_theta=[99.0],
|
|
92
|
+
dark_status=["DarkShutter"],
|
|
93
|
+
num_modstates=4,
|
|
94
|
+
array_shape=(2, 2), # Different!
|
|
95
|
+
data_func=partial(wrong_arm_gain_signal_from_modstate, dark_signal=dark_signal),
|
|
96
|
+
tags=[TrendTag.task_polcal_gain(), TrendTag.arm_id("wrong_arm")],
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Write two INTERMEDIATE DARK frames: one for the correct arm and one for a different arm
|
|
100
|
+
dark_array = np.full(shape, dark_signal)
|
|
101
|
+
wrong_arm_dark_array = dark_array * 100000
|
|
102
|
+
task.write(
|
|
103
|
+
data=dark_array,
|
|
104
|
+
tags=[
|
|
105
|
+
TrendTag.intermediate(),
|
|
106
|
+
TrendTag.frame(),
|
|
107
|
+
TrendTag.task_dark(),
|
|
108
|
+
TrendTag.arm_id(arm_id),
|
|
109
|
+
],
|
|
110
|
+
encoder=fits_array_encoder,
|
|
111
|
+
)
|
|
112
|
+
task.write(
|
|
113
|
+
data=wrong_arm_dark_array,
|
|
114
|
+
tags=[
|
|
115
|
+
TrendTag.intermediate(),
|
|
116
|
+
TrendTag.frame(),
|
|
117
|
+
TrendTag.task_dark(),
|
|
118
|
+
TrendTag.arm_id("wrong_arm"),
|
|
119
|
+
],
|
|
120
|
+
encoder=fits_array_encoder,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
task()
|
|
124
|
+
|
|
125
|
+
tags = [
|
|
126
|
+
TrendTag.arm_id(arm_id),
|
|
127
|
+
TrendTag.intermediate(),
|
|
128
|
+
TrendTag.frame(),
|
|
129
|
+
TrendTag.task_gain(),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
expected_gain_value = 2500.0 # Mean of [1,2,3,4] * 1000 (dark signal is removed)
|
|
133
|
+
assert task.count(tags=tags) == 1
|
|
134
|
+
dark_array = next(task.read(tags=tags, decoder=fits_array_decoder))
|
|
135
|
+
np.testing.assert_array_equal(dark_array, expected_gain_value)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from dataclasses import asdict
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
5
|
+
from dkist_processing_common.tasks.base import WorkflowTaskBase
|
|
6
|
+
|
|
7
|
+
from dkist_processing_trend.models.fit_options import FitOptions
|
|
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.instrument_options import DlnirspInstrumentOptions
|
|
11
|
+
from dkist_processing_trend.models.instrument_options import VispInstrumentOptions
|
|
12
|
+
from dkist_processing_trend.models.parameters import TrendParameters
|
|
13
|
+
from dkist_processing_trend.models.parameters import TrendParsingParameters
|
|
14
|
+
from dkist_processing_trend.tasks.trend_base import TrendTaskBase
|
|
15
|
+
from dkist_processing_trend.tests.conftest import TrendInputDatasetParameterValues
|
|
16
|
+
from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture(scope="session")
|
|
20
|
+
def parse_parameter_names() -> set[str]:
|
|
21
|
+
# The property names of all parameter properties on `TrendParsingParameters`
|
|
22
|
+
return set(k for k, v in vars(TrendParsingParameters).items() if isinstance(v, property))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope="session")
|
|
26
|
+
def parameter_names() -> set[str]:
|
|
27
|
+
return set(k for k, v in vars(TrendParameters).items() if isinstance(v, property))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture(scope="session")
|
|
31
|
+
def instrument_parameter_raw_names() -> set[str]:
|
|
32
|
+
return {"trend_instrument_processing_options_visp"}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.fixture(scope="session")
|
|
36
|
+
def instrument_parameter_property_names(instrument_parameter_raw_names) -> set[str]:
|
|
37
|
+
return {
|
|
38
|
+
p.removeprefix("trend_")
|
|
39
|
+
.replace("_visp", "")
|
|
40
|
+
.replace("_cryonirsp", "")
|
|
41
|
+
.replace("_dlnirsp", "")
|
|
42
|
+
for p in instrument_parameter_raw_names
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Task(WorkflowTaskBase):
|
|
47
|
+
def run(self):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ScienceTask(TrendTaskBase):
|
|
52
|
+
def run(self):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.fixture
|
|
57
|
+
def task_with_parsing_parameters(tmp_path, recipe_run_id, assign_input_dataset_doc_to_task):
|
|
58
|
+
with Task(
|
|
59
|
+
recipe_run_id=recipe_run_id,
|
|
60
|
+
workflow_name="workflow_name",
|
|
61
|
+
workflow_version="workflow_version",
|
|
62
|
+
) as task:
|
|
63
|
+
task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
|
|
64
|
+
param_values = TrendInputDatasetParameterValues()
|
|
65
|
+
assign_input_dataset_doc_to_task(
|
|
66
|
+
task=task,
|
|
67
|
+
parameter_values=param_values,
|
|
68
|
+
)
|
|
69
|
+
task.parameters = TrendParsingParameters(scratch=task.scratch)
|
|
70
|
+
|
|
71
|
+
yield task, param_values
|
|
72
|
+
task._purge()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.fixture
|
|
76
|
+
def task_with_parameters(
|
|
77
|
+
tmp_path, recipe_run_id, assign_input_dataset_doc_to_task, link_constants_db, instrument
|
|
78
|
+
):
|
|
79
|
+
link_constants_db(recipe_run_id, TrendTestingConstantsDatabase(INSTRUMENT=instrument))
|
|
80
|
+
with ScienceTask(
|
|
81
|
+
recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
|
|
82
|
+
) as task:
|
|
83
|
+
task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
|
|
84
|
+
param_values = TrendInputDatasetParameterValues()
|
|
85
|
+
assign_input_dataset_doc_to_task(task, parameter_values=param_values)
|
|
86
|
+
task.parameters = TrendParameters(
|
|
87
|
+
scratch=task.scratch,
|
|
88
|
+
obs_ip_start_time=task.constants.earliest_ip_start_time,
|
|
89
|
+
instrument=task.constants.instrument,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
yield task, param_values
|
|
93
|
+
task._purge()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_parse_parameters(task_with_parsing_parameters, parse_parameter_names):
|
|
97
|
+
"""
|
|
98
|
+
Given: A Task whose `parameter` property is an instance of `TrendParsingParameters`
|
|
99
|
+
When: Accessing properties for Parse parameters
|
|
100
|
+
Then: The correct value is returned
|
|
101
|
+
"""
|
|
102
|
+
task, expected_param_values = task_with_parsing_parameters
|
|
103
|
+
tested_parse_parameters = set()
|
|
104
|
+
task_param_attr = task.parameters
|
|
105
|
+
for pn, pv in asdict(expected_param_values).items():
|
|
106
|
+
property_name = pn.removeprefix("trend_")
|
|
107
|
+
if property_name in parse_parameter_names and type(pv) is not dict:
|
|
108
|
+
assert getattr(task_param_attr, property_name) == pv
|
|
109
|
+
tested_parse_parameters.add(property_name)
|
|
110
|
+
|
|
111
|
+
assert (
|
|
112
|
+
tested_parse_parameters == parse_parameter_names
|
|
113
|
+
), f"Did not find test values for parameters {parse_parameter_names - tested_parse_parameters}"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.mark.parametrize(
|
|
117
|
+
"instrument", [InstrumentName.visp, InstrumentName.cryonirsp, InstrumentName.dlnirsp]
|
|
118
|
+
)
|
|
119
|
+
def test_parameters(
|
|
120
|
+
task_with_parameters, instrument, parameter_names, instrument_parameter_property_names
|
|
121
|
+
):
|
|
122
|
+
"""
|
|
123
|
+
Given: A task with `TrendParameters` as its parameters object
|
|
124
|
+
When: Using the `.parameters` property to access parameters
|
|
125
|
+
Then: The correct values are returned
|
|
126
|
+
"""
|
|
127
|
+
task, expected_param_values = task_with_parameters
|
|
128
|
+
task_param_attr = task.parameters
|
|
129
|
+
expected_dict = asdict(expected_param_values)
|
|
130
|
+
|
|
131
|
+
for parameter_name in parameter_names:
|
|
132
|
+
db_name = f"trend_{parameter_name}"
|
|
133
|
+
if parameter_name in instrument_parameter_property_names:
|
|
134
|
+
db_name = f"{db_name}_{task_param_attr._instrument}"
|
|
135
|
+
expected = expected_dict[db_name]
|
|
136
|
+
if parameter_name == "fit_options_list":
|
|
137
|
+
expected = [FitOptions.model_validate(i) for i in expected]
|
|
138
|
+
if parameter_name == "instrument_processing_options":
|
|
139
|
+
match instrument:
|
|
140
|
+
case InstrumentName.visp:
|
|
141
|
+
expected = [VispInstrumentOptions.model_validate(i) for i in expected]
|
|
142
|
+
case InstrumentName.cryonirsp:
|
|
143
|
+
expected = [CryonirspInstrumentOptions.model_validate(i) for i in expected]
|
|
144
|
+
case InstrumentName.dlnirsp:
|
|
145
|
+
expected = [DlnirspInstrumentOptions.model_validate(i) for i in expected]
|
|
146
|
+
|
|
147
|
+
if isinstance(expected, tuple):
|
|
148
|
+
expected = list(expected)
|
|
149
|
+
assert getattr(task_param_attr, parameter_name) == expected
|