dkist-processing-trend 0.1.0rc1__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
- changelog/1.feature.rst +1 -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 +11 -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 +11 -0
- dkist_processing_trend/tasks/dark.py +56 -0
- dkist_processing_trend/tasks/gain.py +74 -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 +405 -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_class_factory.py +39 -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 +870 -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/arm_class_factory.py +30 -0
- dkist_processing_trend/workflows/visp.py +59 -0
- dkist_processing_trend-0.1.0rc1.dist-info/METADATA +549 -0
- dkist_processing_trend-0.1.0rc1.dist-info/RECORD +66 -0
- dkist_processing_trend-0.1.0rc1.dist-info/WHEEL +5 -0
- dkist_processing_trend-0.1.0rc1.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,103 @@
|
|
|
1
|
+
"""Bud for parsing the full set of input ARM IDs."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import DefaultDict
|
|
7
|
+
|
|
8
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
9
|
+
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
10
|
+
|
|
11
|
+
from dkist_processing_trend.models.constants import TrendBudName
|
|
12
|
+
from dkist_processing_trend.models.fits_access import TrendMetadataKey
|
|
13
|
+
from dkist_processing_trend.parsers.trend_l0_fits_access import TrendL0FitsAccess
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TrendArmIdListBud(SetStem):
|
|
17
|
+
"""
|
|
18
|
+
Bud for parsing the set of ARM IDs present in the input data.
|
|
19
|
+
|
|
20
|
+
The actual value stored in the db is a `list` only because `set` isn't serializable with JSON.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
super().__init__(stem_name=TrendBudName.arm_id_list)
|
|
25
|
+
self.metadata_key = TrendMetadataKey.arm_id.name
|
|
26
|
+
|
|
27
|
+
def setter(self, fits_obj: TrendL0FitsAccess) -> int | str:
|
|
28
|
+
"""Return the arm_id property of the given FitsAccess object."""
|
|
29
|
+
return getattr(fits_obj, self.metadata_key)
|
|
30
|
+
|
|
31
|
+
def getter(self) -> list[int | str]:
|
|
32
|
+
"""Return the set of values as a list for database storage."""
|
|
33
|
+
return list(self.value_set)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ArmIdLookupBud(SetStem):
|
|
37
|
+
"""
|
|
38
|
+
Bud that stores a header key as a function of arm_id.
|
|
39
|
+
|
|
40
|
+
Each arm is allowed to only have a single value of the given key.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, constant_name: str, metadata_key: StrEnum):
|
|
44
|
+
super().__init__(stem_name=constant_name)
|
|
45
|
+
self.value_metadata_key = metadata_key.name
|
|
46
|
+
self.mapping: DefaultDict[float, set[float]] = defaultdict(set)
|
|
47
|
+
|
|
48
|
+
def setter(self, fits_obj: TrendL0FitsAccess) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Update the mapping dictionary.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
fits_obj
|
|
55
|
+
The input fits object
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
Updates the dictionary and returns None
|
|
60
|
+
"""
|
|
61
|
+
arm_id = fits_obj.arm_id
|
|
62
|
+
value = getattr(fits_obj, self.value_metadata_key)
|
|
63
|
+
self.mapping[arm_id].add(value)
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def getter(self) -> dict[str, float | str]:
|
|
67
|
+
"""
|
|
68
|
+
Check that each arm only contains a single value and convert each arm's `set` to a single value.
|
|
69
|
+
|
|
70
|
+
The output dict has its keys wrapped in `json.loads` to simplify the extraction of both `int` and `str` arm IDs
|
|
71
|
+
in `~dkist_processing_trend.models.constants.TrendConstants`.
|
|
72
|
+
"""
|
|
73
|
+
output_mapping = dict()
|
|
74
|
+
error_list = []
|
|
75
|
+
for arm_id, value_set in self.mapping.items():
|
|
76
|
+
if len(value_set) > 1:
|
|
77
|
+
error_list.append(f"{arm_id = }: {value_set}")
|
|
78
|
+
|
|
79
|
+
output_mapping[json.dumps(arm_id)] = value_set.pop()
|
|
80
|
+
|
|
81
|
+
if len(error_list) > 0:
|
|
82
|
+
error_str = f"Some arms have multiple values for {self.value_metadata_key}: "
|
|
83
|
+
for arm_err in error_list:
|
|
84
|
+
error_str += f"{arm_err} "
|
|
85
|
+
raise ValueError(error_str)
|
|
86
|
+
|
|
87
|
+
return output_mapping
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class InstrumentArmIdLookupBud(ArmIdLookupBud):
|
|
91
|
+
"""Subclass of `ArmIdLookupBud` that only ingests data for a given instrument."""
|
|
92
|
+
|
|
93
|
+
def __init__(self, constant_name: str, metadata_key: StrEnum, instruments: str | list[str]):
|
|
94
|
+
super().__init__(constant_name=constant_name, metadata_key=metadata_key)
|
|
95
|
+
if isinstance(instruments, str):
|
|
96
|
+
instruments = [instruments]
|
|
97
|
+
self.instruments = [i.casefold() for i in instruments]
|
|
98
|
+
|
|
99
|
+
def setter(self, fits_obj: TrendL0FitsAccess) -> None:
|
|
100
|
+
"""Update the mapping dict only if the input object's instrument attribute matches the instrument associated with this instance."""
|
|
101
|
+
if fits_obj.instrument.casefold() not in self.instruments:
|
|
102
|
+
return SpilledDirt
|
|
103
|
+
return super().setter(fits_obj)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Version of UniqueBud that only parses frame from a specific instrument."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
from dkist_processing_common.models.flower_pot import SpilledDirt
|
|
6
|
+
from dkist_processing_common.parsers.unique_bud import UniqueBud
|
|
7
|
+
|
|
8
|
+
from dkist_processing_trend.parsers.trend_l0_fits_access import TrendL0FitsAccess
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InstrumentUniqueBud(UniqueBud):
|
|
12
|
+
"""
|
|
13
|
+
Subclass of `UniqueBud` that only considers objects that have specific task types.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
constant_name
|
|
18
|
+
The name for the constant to be defined
|
|
19
|
+
|
|
20
|
+
metadata_key
|
|
21
|
+
The metadata key associated with the constant
|
|
22
|
+
|
|
23
|
+
instruments
|
|
24
|
+
Only consider frames whose header INSTRUME value is in this list
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, constant_name: str, metadata_key: str | StrEnum, instruments: str | list[str]
|
|
29
|
+
):
|
|
30
|
+
super().__init__(constant_name=constant_name, metadata_key=metadata_key)
|
|
31
|
+
if isinstance(instruments, str):
|
|
32
|
+
instruments = [instruments]
|
|
33
|
+
self.instruments = [i.casefold() for i in instruments]
|
|
34
|
+
|
|
35
|
+
def setter(self, fits_obj: TrendL0FitsAccess) -> int | float | bool | str:
|
|
36
|
+
"""Ingest an object's metadata key only if its instrument property is in the allowed list."""
|
|
37
|
+
if fits_obj.instrument.casefold() in self.instruments:
|
|
38
|
+
return super().setter(fits_obj)
|
|
39
|
+
|
|
40
|
+
return SpilledDirt
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Stems that parse time-related constants and tags."""
|
|
2
|
+
|
|
3
|
+
from dkist_processing_common.models.flower_pot import SetStem
|
|
4
|
+
|
|
5
|
+
from dkist_processing_trend.models.constants import TrendBudName
|
|
6
|
+
from dkist_processing_trend.parsers.trend_l0_fits_access import TrendL0FitsAccess
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EarliestIpStartTime(SetStem):
|
|
10
|
+
"""
|
|
11
|
+
Bud for parsing the earliest IP start time in the input data.
|
|
12
|
+
|
|
13
|
+
This is akin to the concept of "OBSERVE IP start time" in the instrument pipelines. The wrinkle is that a POLCAL OP
|
|
14
|
+
consists of multiple IPs, so with this Bud we consider the "time of observation" to be the earliest IP we have. This
|
|
15
|
+
time is used for choosing parameter values, so high precision is not needed.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__(stem_name=TrendBudName.earliest_ip_start_time)
|
|
20
|
+
|
|
21
|
+
def setter(self, fits_obj: TrendL0FitsAccess) -> str:
|
|
22
|
+
"""Return the IP start time of the given FitsAccess object."""
|
|
23
|
+
return fits_obj.ip_start_time
|
|
24
|
+
|
|
25
|
+
def getter(self) -> str:
|
|
26
|
+
"""Return the earliest IP start time in the set of all start times."""
|
|
27
|
+
return min(self.value_set)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Trend FitsAccess classes for accessing fits header values as properties."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from astropy.io import fits
|
|
5
|
+
from dkist_processing_common.parsers.l0_fits_access import L0FitsAccess
|
|
6
|
+
|
|
7
|
+
from dkist_processing_trend.models.fits_access import CryonirspMetadataKey
|
|
8
|
+
from dkist_processing_trend.models.fits_access import DlnirspMetadataKey
|
|
9
|
+
from dkist_processing_trend.models.fits_access import TrendMetadataKey
|
|
10
|
+
from dkist_processing_trend.models.fits_access import VispMetadataKey
|
|
11
|
+
from dkist_processing_trend.models.instrument import InstrumentName
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IncorrectInstrumentError(Exception):
|
|
15
|
+
"""Error to indicate we're trying to access a property that only exists for a different instrument."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InstrumentSpecificProperty:
|
|
19
|
+
"""
|
|
20
|
+
A class for defining properties that will produce a custom (and useful) error when accessed.
|
|
21
|
+
|
|
22
|
+
The error is an indication that the user is trying to access an instrument-specific property for a `TrendL0FitsAccess`
|
|
23
|
+
object that doesn't correspond to that particular instrument.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, instrument: str):
|
|
27
|
+
self.instrument = instrument
|
|
28
|
+
|
|
29
|
+
# Inspired by https://stackoverflow.com/questions/56997102/python-notimplementederror-for-instance-attributes
|
|
30
|
+
def __get__(self, *args, **kwargs):
|
|
31
|
+
raise IncorrectInstrumentError(
|
|
32
|
+
f"This property is only defined for data from {self.instrument}, which this frame is not."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TrendL0FitsAccess(L0FitsAccess):
|
|
37
|
+
"""
|
|
38
|
+
Class to provide easy access to L0 headers for input/linearized trend data.
|
|
39
|
+
|
|
40
|
+
This class contains logic that allows the same property to look at different header keys depending on instrument.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
header
|
|
45
|
+
The header from which to parse higher-level properties
|
|
46
|
+
|
|
47
|
+
data
|
|
48
|
+
A data array to associate with the current instance
|
|
49
|
+
|
|
50
|
+
name
|
|
51
|
+
The name of the file that was loaded into this FitsAccess object
|
|
52
|
+
|
|
53
|
+
auto_squeeze
|
|
54
|
+
When set to True, dimensions of length 1 will be removed from the array
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
visp_grating_angle: float = InstrumentSpecificProperty(InstrumentName.visp)
|
|
58
|
+
visp_arm_position: float = InstrumentSpecificProperty(InstrumentName.visp)
|
|
59
|
+
visp_grating_constant: float = InstrumentSpecificProperty(InstrumentName.visp)
|
|
60
|
+
cryonirsp_od_filter_name: str = InstrumentSpecificProperty(InstrumentName.cryonirsp)
|
|
61
|
+
cryonirsp_grating_angle: float = InstrumentSpecificProperty(InstrumentName.cryonirsp)
|
|
62
|
+
cryonirsp_grating_littrow_angle: float = InstrumentSpecificProperty(InstrumentName.cryonirsp)
|
|
63
|
+
cryonirsp_grating_constant: float = InstrumentSpecificProperty(InstrumentName.cryonirsp)
|
|
64
|
+
cryonirsp_slit_width: float = InstrumentSpecificProperty(InstrumentName.cryonirsp)
|
|
65
|
+
dlnirsp_grating_angle: float = InstrumentSpecificProperty(InstrumentName.dlnirsp)
|
|
66
|
+
dlnirsp_arm_position: float = InstrumentSpecificProperty(InstrumentName.dlnirsp)
|
|
67
|
+
dlnirsp_grating_constant: float = InstrumentSpecificProperty(InstrumentName.dlnirsp)
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
header: fits.Header,
|
|
73
|
+
data: np.ndarray | None = None,
|
|
74
|
+
name: str | None = None,
|
|
75
|
+
auto_squeeze: bool = True,
|
|
76
|
+
):
|
|
77
|
+
super().__init__(header=header, data=data, name=name, auto_squeeze=auto_squeeze)
|
|
78
|
+
|
|
79
|
+
match self.instrument:
|
|
80
|
+
case InstrumentName.visp:
|
|
81
|
+
self.number_of_modulator_states: int = header[
|
|
82
|
+
VispMetadataKey.number_of_modulator_states
|
|
83
|
+
]
|
|
84
|
+
self.modulator_state: int = header[VispMetadataKey.modulator_state]
|
|
85
|
+
self.arm_id: int = header[VispMetadataKey.arm_id]
|
|
86
|
+
self.visp_grating_angle: float = header[TrendMetadataKey.visp_grating_angle]
|
|
87
|
+
self.visp_arm_position: float = header[TrendMetadataKey.visp_arm_position]
|
|
88
|
+
self.visp_grating_constant: float = header[TrendMetadataKey.visp_grating_constant]
|
|
89
|
+
|
|
90
|
+
case InstrumentName.cryonirsp:
|
|
91
|
+
self.number_of_modulator_states: int = header[
|
|
92
|
+
CryonirspMetadataKey.number_of_modulator_states
|
|
93
|
+
]
|
|
94
|
+
self.modulator_state: int = header[CryonirspMetadataKey.modulator_state]
|
|
95
|
+
self.arm_id: str = header[CryonirspMetadataKey.arm_id]
|
|
96
|
+
self.cryonirsp_od_filter_name: str = header[
|
|
97
|
+
TrendMetadataKey.cryonirsp_od_filter_name
|
|
98
|
+
]
|
|
99
|
+
self.cryonirsp_grating_angle: float = header[
|
|
100
|
+
TrendMetadataKey.cryonirsp_grating_angle
|
|
101
|
+
]
|
|
102
|
+
self.cryonirsp_grating_littrow_angle: float = header[
|
|
103
|
+
TrendMetadataKey.cryonirsp_grating_littrow_angle
|
|
104
|
+
]
|
|
105
|
+
self.cryonirsp_grating_constant: float = header[
|
|
106
|
+
TrendMetadataKey.cryonirsp_grating_constant
|
|
107
|
+
]
|
|
108
|
+
self.cryonirsp_slit_width: float = header[TrendMetadataKey.cryonirsp_slit_width]
|
|
109
|
+
|
|
110
|
+
case InstrumentName.dlnirsp:
|
|
111
|
+
self.number_of_modulator_states: int = header[
|
|
112
|
+
DlnirspMetadataKey.number_of_modulator_states
|
|
113
|
+
]
|
|
114
|
+
self.modulator_state: int = header[DlnirspMetadataKey.modulator_state]
|
|
115
|
+
self.arm_id: int = header[DlnirspMetadataKey.arm_id]
|
|
116
|
+
self.dlnirsp_grating_angle: float = header[TrendMetadataKey.dlnirsp_grating_angle]
|
|
117
|
+
self.dlnirsp_arm_position: float = header[TrendMetadataKey.dlnirsp_arm_position]
|
|
118
|
+
self.dlnirsp_grating_constant: float = header[
|
|
119
|
+
TrendMetadataKey.dlnirsp_grating_constant
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
case _:
|
|
123
|
+
raise ValueError(f"Did not expect instrument = {self.instrument}")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Task modules and classes that are used to build up a workflow."""
|
|
2
|
+
|
|
3
|
+
from dkist_processing_trend.tasks.dark import *
|
|
4
|
+
from dkist_processing_trend.tasks.gain import *
|
|
5
|
+
from dkist_processing_trend.tasks.parse import *
|
|
6
|
+
from dkist_processing_trend.tasks.run_pac_fitter import *
|
|
7
|
+
from dkist_processing_trend.tasks.trend_output_data import *
|
|
8
|
+
from dkist_processing_trend.tasks.write_trend import *
|
|
9
|
+
|
|
10
|
+
# Not used directly, but ensures that the common_configurations are logged early in a task run
|
|
11
|
+
from dkist_processing_trend.config import dkist_processing_trend_configurations # isort: skip
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Task for computing the average of the DARK CS steps."""
|
|
2
|
+
|
|
3
|
+
from dkist_processing_common.codecs.fits import fits_array_decoder
|
|
4
|
+
from dkist_processing_common.codecs.fits import fits_array_encoder
|
|
5
|
+
from dkist_processing_math.statistics import average_numpy_arrays
|
|
6
|
+
|
|
7
|
+
from dkist_processing_trend.models.tags import TrendTag
|
|
8
|
+
from dkist_processing_trend.tasks.trend_base import TrendArmTaskBase
|
|
9
|
+
|
|
10
|
+
__all__ = ["DarkCalibration"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DarkCalibration(TrendArmTaskBase):
|
|
14
|
+
"""
|
|
15
|
+
Generate an INTERMEDIATE dark frame by averaging all data from the "dark" CS steps.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
arm_id
|
|
20
|
+
id of the instrument arm to operate on
|
|
21
|
+
|
|
22
|
+
recipe_run_id
|
|
23
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
24
|
+
|
|
25
|
+
workflow_name
|
|
26
|
+
name of the workflow to which this instance of the task belongs
|
|
27
|
+
|
|
28
|
+
workflow_version
|
|
29
|
+
version of the workflow to which this instance of the task belongs
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
record_provenance = True
|
|
33
|
+
|
|
34
|
+
def run(self):
|
|
35
|
+
"""
|
|
36
|
+
Produce a single, average dark frame from all of the dark CS steps.
|
|
37
|
+
|
|
38
|
+
In other words, collect all frames tagged as POLCAL_DARK, average them together, and write the result to disk.
|
|
39
|
+
"""
|
|
40
|
+
with self.telemetry_span(f"Producing average dark for arm {self.arm_id}"):
|
|
41
|
+
input_dark_arrays = self.read(
|
|
42
|
+
tags=[TrendTag.task_polcal_dark(), TrendTag.arm_id(self.arm_id)],
|
|
43
|
+
decoder=fits_array_decoder,
|
|
44
|
+
)
|
|
45
|
+
averaged_dark_array = average_numpy_arrays(input_dark_arrays)
|
|
46
|
+
|
|
47
|
+
self.write(
|
|
48
|
+
data=averaged_dark_array,
|
|
49
|
+
tags=[
|
|
50
|
+
TrendTag.intermediate(),
|
|
51
|
+
TrendTag.frame(),
|
|
52
|
+
TrendTag.arm_id(self.arm_id),
|
|
53
|
+
TrendTag.task_dark(),
|
|
54
|
+
],
|
|
55
|
+
encoder=fits_array_encoder,
|
|
56
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Task for computing the average of the CLEAR CS steps."""
|
|
2
|
+
|
|
3
|
+
from dkist_processing_common.codecs.fits import fits_array_decoder
|
|
4
|
+
from dkist_processing_common.codecs.fits import fits_array_encoder
|
|
5
|
+
from dkist_processing_math.arithmetic import subtract_array_from_arrays
|
|
6
|
+
from dkist_processing_math.statistics import average_numpy_arrays
|
|
7
|
+
|
|
8
|
+
from dkist_processing_trend.models.tags import TrendTag
|
|
9
|
+
from dkist_processing_trend.tasks.trend_base import TrendArmTaskBase
|
|
10
|
+
|
|
11
|
+
__all__ = ["GainCalibration"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GainCalibration(TrendArmTaskBase):
|
|
15
|
+
"""
|
|
16
|
+
Generate an INTERMEDIATE gain frame by averaging all data from the "clear" CS steps.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
arm_id
|
|
21
|
+
id of the instrument arm to operate on
|
|
22
|
+
|
|
23
|
+
recipe_run_id
|
|
24
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
25
|
+
|
|
26
|
+
workflow_name
|
|
27
|
+
name of the workflow to which this instance of the task belongs
|
|
28
|
+
|
|
29
|
+
workflow_version
|
|
30
|
+
version of the workflow to which this instance of the task belongs
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
record_provenance = True
|
|
34
|
+
|
|
35
|
+
def run(self):
|
|
36
|
+
"""
|
|
37
|
+
Produce a single, average gain frame from all of the clear CS steps.
|
|
38
|
+
|
|
39
|
+
In other words, collect all frames tagged as POLCAL_GAIN, average them together, subtract the INTERMEDIATE DARK
|
|
40
|
+
signal, and write the result to disk.
|
|
41
|
+
"""
|
|
42
|
+
with self.telemetry_span(f"Producing average gain for arm {self.arm_id}"):
|
|
43
|
+
input_gain_arrays = self.read(
|
|
44
|
+
tags=[TrendTag.task_polcal_gain(), TrendTag.arm_id(self.arm_id)],
|
|
45
|
+
decoder=fits_array_decoder,
|
|
46
|
+
)
|
|
47
|
+
averaged_gain_array = average_numpy_arrays(input_gain_arrays)
|
|
48
|
+
|
|
49
|
+
dark_array = next(
|
|
50
|
+
self.read(
|
|
51
|
+
tags=[
|
|
52
|
+
TrendTag.intermediate(),
|
|
53
|
+
TrendTag.frame(),
|
|
54
|
+
TrendTag.task_dark(),
|
|
55
|
+
TrendTag.arm_id(self.arm_id),
|
|
56
|
+
],
|
|
57
|
+
decoder=fits_array_decoder,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
final_gain = next(
|
|
62
|
+
subtract_array_from_arrays(arrays=averaged_gain_array, array_to_subtract=dark_array)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.write(
|
|
66
|
+
data=final_gain,
|
|
67
|
+
tags=[
|
|
68
|
+
TrendTag.intermediate(),
|
|
69
|
+
TrendTag.frame(),
|
|
70
|
+
TrendTag.arm_id(self.arm_id),
|
|
71
|
+
TrendTag.task_gain(),
|
|
72
|
+
],
|
|
73
|
+
encoder=fits_array_encoder,
|
|
74
|
+
)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Tasks for parsing both IR and VIS data."""
|
|
2
|
+
|
|
3
|
+
from dkist_processing_common.models.constants import BudName
|
|
4
|
+
from dkist_processing_common.models.fits_access import MetadataKey
|
|
5
|
+
from dkist_processing_common.models.flower_pot import Stem
|
|
6
|
+
from dkist_processing_common.models.tags import StemName
|
|
7
|
+
from dkist_processing_common.parsers.cs_step import CSStepFlower
|
|
8
|
+
from dkist_processing_common.parsers.cs_step import NumCSStepBud
|
|
9
|
+
from dkist_processing_common.parsers.id_bud import ContributingIdsBud
|
|
10
|
+
from dkist_processing_common.parsers.single_value_single_key_flower import (
|
|
11
|
+
SingleValueSingleKeyFlower,
|
|
12
|
+
)
|
|
13
|
+
from dkist_processing_common.parsers.task import PolcalTaskFlower
|
|
14
|
+
from dkist_processing_common.parsers.unique_bud import UniqueBud
|
|
15
|
+
from dkist_processing_common.tasks import ParseL0InputDataBase
|
|
16
|
+
|
|
17
|
+
from dkist_processing_trend.models.constants import TrendBudName
|
|
18
|
+
from dkist_processing_trend.models.fits_access import TrendMetadataKey
|
|
19
|
+
from dkist_processing_trend.models.instrument import InstrumentName
|
|
20
|
+
from dkist_processing_trend.models.parameters import TrendParsingParameters
|
|
21
|
+
from dkist_processing_trend.models.tags import TrendStemName
|
|
22
|
+
from dkist_processing_trend.parsers.arm_id import ArmIdLookupBud
|
|
23
|
+
from dkist_processing_trend.parsers.arm_id import InstrumentArmIdLookupBud
|
|
24
|
+
from dkist_processing_trend.parsers.arm_id import TrendArmIdListBud
|
|
25
|
+
from dkist_processing_trend.parsers.time import EarliestIpStartTime
|
|
26
|
+
from dkist_processing_trend.parsers.trend_l0_fits_access import TrendL0FitsAccess
|
|
27
|
+
|
|
28
|
+
__all__ = ["ParseL0TrendInputData"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ParseL0TrendInputData(ParseL0InputDataBase):
|
|
32
|
+
"""
|
|
33
|
+
Parse either LINEARIZED data (IR instruments) or INPUT data (VIS instruments).
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
recipe_run_id
|
|
38
|
+
id of the recipe run used to identify the workflow run this task is part of
|
|
39
|
+
|
|
40
|
+
workflow_name
|
|
41
|
+
name of the workflow to which this instance of the task belongs
|
|
42
|
+
|
|
43
|
+
workflow_version
|
|
44
|
+
version of the workflow to which this instance of the task belongs
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
recipe_run_id: int,
|
|
50
|
+
workflow_name: str,
|
|
51
|
+
workflow_version: str,
|
|
52
|
+
):
|
|
53
|
+
super().__init__(
|
|
54
|
+
recipe_run_id=recipe_run_id,
|
|
55
|
+
workflow_name=workflow_name,
|
|
56
|
+
workflow_version=workflow_version,
|
|
57
|
+
)
|
|
58
|
+
self.parameters = TrendParsingParameters(scratch=self.scratch)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def fits_parsing_class(self):
|
|
62
|
+
"""FITS access class to use in this task."""
|
|
63
|
+
return TrendL0FitsAccess
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def constant_buds(self) -> list[Stem]:
|
|
67
|
+
"""Return the list of Buds to use when parsing constants."""
|
|
68
|
+
return [
|
|
69
|
+
EarliestIpStartTime(),
|
|
70
|
+
UniqueBud(constant_name=BudName.instrument, metadata_key=MetadataKey.instrument),
|
|
71
|
+
NumCSStepBud(max_cs_step_time_sec=self.parameters.max_cs_step_time_sec),
|
|
72
|
+
UniqueBud(
|
|
73
|
+
constant_name=BudName.num_modstates,
|
|
74
|
+
metadata_key=TrendMetadataKey.number_of_modulator_states,
|
|
75
|
+
),
|
|
76
|
+
ArmIdLookupBud(
|
|
77
|
+
constant_name=TrendBudName.wavelength_for_arm, metadata_key=MetadataKey.wavelength
|
|
78
|
+
),
|
|
79
|
+
TrendArmIdListBud(),
|
|
80
|
+
ContributingIdsBud(
|
|
81
|
+
constant_name=TrendBudName.observing_program_id_list,
|
|
82
|
+
metadata_key=MetadataKey.observing_program_execution_id,
|
|
83
|
+
),
|
|
84
|
+
ContributingIdsBud(
|
|
85
|
+
constant_name=TrendBudName.experiment_id_list,
|
|
86
|
+
metadata_key=MetadataKey.experiment_id,
|
|
87
|
+
),
|
|
88
|
+
ContributingIdsBud(
|
|
89
|
+
constant_name=TrendBudName.proposal_id_list, metadata_key=MetadataKey.proposal_id
|
|
90
|
+
),
|
|
91
|
+
ContributingIdsBud(
|
|
92
|
+
constant_name=TrendBudName.hls_version_list, metadata_key=MetadataKey.hls_version
|
|
93
|
+
),
|
|
94
|
+
# ViSP
|
|
95
|
+
InstrumentArmIdLookupBud(
|
|
96
|
+
constant_name=TrendBudName.visp_grating_angle_for_arm,
|
|
97
|
+
metadata_key=TrendMetadataKey.visp_grating_angle,
|
|
98
|
+
instruments=InstrumentName.visp,
|
|
99
|
+
),
|
|
100
|
+
InstrumentArmIdLookupBud(
|
|
101
|
+
constant_name=TrendBudName.visp_arm_position_for_arm,
|
|
102
|
+
metadata_key=TrendMetadataKey.visp_arm_position,
|
|
103
|
+
instruments=InstrumentName.visp,
|
|
104
|
+
),
|
|
105
|
+
InstrumentArmIdLookupBud(
|
|
106
|
+
constant_name=TrendBudName.visp_grating_constant_for_arm,
|
|
107
|
+
metadata_key=TrendMetadataKey.visp_grating_constant,
|
|
108
|
+
instruments=InstrumentName.visp,
|
|
109
|
+
),
|
|
110
|
+
# CryoNIRSP
|
|
111
|
+
InstrumentArmIdLookupBud(
|
|
112
|
+
constant_name=TrendBudName.cryonirsp_od_filter_name_for_arm,
|
|
113
|
+
metadata_key=TrendMetadataKey.cryonirsp_od_filter_name,
|
|
114
|
+
instruments=InstrumentName.cryonirsp,
|
|
115
|
+
),
|
|
116
|
+
InstrumentArmIdLookupBud(
|
|
117
|
+
constant_name=TrendBudName.cryonirsp_grating_angle_for_arm,
|
|
118
|
+
metadata_key=TrendMetadataKey.cryonirsp_grating_angle,
|
|
119
|
+
instruments=InstrumentName.cryonirsp,
|
|
120
|
+
),
|
|
121
|
+
InstrumentArmIdLookupBud(
|
|
122
|
+
constant_name=TrendBudName.cryonirsp_grating_littrow_angle_for_arm,
|
|
123
|
+
metadata_key=TrendMetadataKey.cryonirsp_grating_littrow_angle,
|
|
124
|
+
instruments=InstrumentName.cryonirsp,
|
|
125
|
+
),
|
|
126
|
+
InstrumentArmIdLookupBud(
|
|
127
|
+
constant_name=TrendBudName.cryonirsp_grating_constant_for_arm,
|
|
128
|
+
metadata_key=TrendMetadataKey.cryonirsp_grating_constant,
|
|
129
|
+
instruments=InstrumentName.cryonirsp,
|
|
130
|
+
),
|
|
131
|
+
InstrumentArmIdLookupBud(
|
|
132
|
+
constant_name=TrendBudName.cryonirsp_slit_width_for_arm,
|
|
133
|
+
metadata_key=TrendMetadataKey.cryonirsp_slit_width,
|
|
134
|
+
instruments=InstrumentName.cryonirsp,
|
|
135
|
+
),
|
|
136
|
+
# DLNIRSP
|
|
137
|
+
InstrumentArmIdLookupBud(
|
|
138
|
+
constant_name=TrendBudName.dlnirsp_grating_angle_for_arm,
|
|
139
|
+
metadata_key=TrendMetadataKey.dlnirsp_grating_angle,
|
|
140
|
+
instruments=InstrumentName.dlnirsp,
|
|
141
|
+
),
|
|
142
|
+
InstrumentArmIdLookupBud(
|
|
143
|
+
constant_name=TrendBudName.dlnirsp_arm_position_for_arm,
|
|
144
|
+
metadata_key=TrendMetadataKey.dlnirsp_arm_position,
|
|
145
|
+
instruments=InstrumentName.dlnirsp,
|
|
146
|
+
),
|
|
147
|
+
InstrumentArmIdLookupBud(
|
|
148
|
+
constant_name=TrendBudName.dlnirsp_grating_constant_for_arm,
|
|
149
|
+
metadata_key=TrendMetadataKey.dlnirsp_grating_constant,
|
|
150
|
+
instruments=InstrumentName.dlnirsp,
|
|
151
|
+
),
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def tag_flowers(self) -> list[Stem]:
|
|
156
|
+
"""Return the list of Flowers to use when parsing tags."""
|
|
157
|
+
return [
|
|
158
|
+
SingleValueSingleKeyFlower(
|
|
159
|
+
tag_stem_name=StemName.task, metadata_key=MetadataKey.ip_task_type
|
|
160
|
+
),
|
|
161
|
+
CSStepFlower(max_cs_step_time_sec=self.parameters.max_cs_step_time_sec),
|
|
162
|
+
SingleValueSingleKeyFlower(
|
|
163
|
+
tag_stem_name=StemName.modstate, metadata_key=TrendMetadataKey.modulator_state
|
|
164
|
+
),
|
|
165
|
+
SingleValueSingleKeyFlower(
|
|
166
|
+
tag_stem_name=TrendStemName.arm_id, metadata_key=TrendMetadataKey.arm_id
|
|
167
|
+
),
|
|
168
|
+
PolcalTaskFlower(),
|
|
169
|
+
]
|