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,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
+ ]
@@ -0,0 +1,257 @@
1
+ """Base task for preparing input data for the PAC fitter."""
2
+
3
+ from abc import ABC
4
+ from abc import abstractmethod
5
+ from collections import defaultdict
6
+
7
+ import numpy as np
8
+ from dkist_processing_common.codecs.asdf import asdf_encoder
9
+ from dkist_processing_common.codecs.fits import fits_array_decoder
10
+ from dkist_processing_common.codecs.fits import fits_header_decoder
11
+ from dkist_processing_math.arithmetic import divide_arrays_by_array
12
+ from dkist_processing_math.arithmetic import subtract_array_from_arrays
13
+ from dkist_processing_math.statistics import average_numpy_arrays
14
+ from dkist_service_configuration.logging import logger
15
+
16
+ from dkist_processing_trend.models.instrument_options import InstrumentOptionsBase
17
+ from dkist_processing_trend.models.tags import TrendTag
18
+ from dkist_processing_trend.parsers.trend_l0_fits_access import TrendL0FitsAccess
19
+ from dkist_processing_trend.tasks.trend_base import TrendArmTaskBase
20
+
21
+
22
+ class PrepareFitDataBase(TrendArmTaskBase, ABC):
23
+ """
24
+ Base class for processing INPUT data to prepare it for fitting by `~dkist_processing_trend.tasks.run_pac_fitter.RunPacFitter`.
25
+
26
+ This class provides a populated `run` method that does steps common to all instruments (e.g., dark and gain correction).
27
+
28
+ Instrument-specific subclasses need to define the following methods:
29
+
30
+ * `extract_beam` - For getting a certain beam from a full-FOV array
31
+ * `apply_global_instrument_options` - Use any `*InstrumentOptions` to produce the "global" PAC input data for a single cs step, beam, and modstate
32
+ * `apply_local_instrument_options` - Use any `*InstrumentOptions` to produce the "local" PAC input data for a single cs step, beam, and modstate
33
+
34
+ Parameters
35
+ ----------
36
+ arm_id
37
+ id of the instrument arm to operate on
38
+
39
+ recipe_run_id
40
+ id of the recipe run used to identify the workflow run this task is part of
41
+
42
+ workflow_name
43
+ name of the workflow to which this instance of the task belongs
44
+
45
+ workflow_version
46
+ version of the workflow to which this instance of the task belongs
47
+ """
48
+
49
+ record_provenance = True
50
+
51
+ def run(self):
52
+ """
53
+ Prepare input data for fitting with `dkist_processing_pac`.
54
+
55
+ At a high level this involves three steps:
56
+
57
+ #. Perform dark and gain corrections to all input frames
58
+ #. Apply any instrument-specific processing (e.g., binning) to the resulting frames
59
+ #. Organize the results into the structure expected by `~dkist_processing_trend.tasks.run_pac_fitter.RunPacFitter`
60
+
61
+ This method should not be overloaded by instrument-specific subclasses. Rather they should define `extract_beam`,
62
+ `apply_global_instrument_options`, and `apply_local_instrument_options`, which are called from this method.
63
+ """
64
+ logger.info("Loading INTERMEDIATE dark frame")
65
+ avg_dark = next(
66
+ self.read(
67
+ tags=[
68
+ TrendTag.intermediate(),
69
+ TrendTag.frame(),
70
+ TrendTag.arm_id(self.arm_id),
71
+ TrendTag.task_dark(),
72
+ ],
73
+ decoder=fits_array_decoder,
74
+ )
75
+ )
76
+
77
+ logger.info("Loading INTERMEDIATE gain frame")
78
+ avg_gain = next(
79
+ self.read(
80
+ tags=[
81
+ TrendTag.intermediate(),
82
+ TrendTag.frame(),
83
+ TrendTag.arm_id(self.arm_id),
84
+ TrendTag.task_gain(),
85
+ ],
86
+ decoder=fits_array_decoder,
87
+ )
88
+ )
89
+
90
+ beam_dark_dict = {
91
+ beam: self.extract_beam(array=avg_dark, beam=beam)
92
+ for beam in range(1, self.num_beams + 1)
93
+ }
94
+ beam_gain_dict = {
95
+ beam: self.extract_beam(array=avg_gain, beam=beam)
96
+ for beam in range(1, self.num_beams + 1)
97
+ }
98
+
99
+ with self.telemetry_span("Prepare polcal fit inputs"):
100
+ for inst_options in self.parameters.instrument_processing_options:
101
+ for beam in range(1, self.num_beams + 1):
102
+ global_obj_dict = defaultdict(list)
103
+ local_obj_dict = defaultdict(list)
104
+ beam_dark = beam_dark_dict[beam]
105
+ beam_gain = beam_gain_dict[beam]
106
+ for cs_step in range(self.constants.num_cs_steps):
107
+ for modstate in range(1, self.constants.num_modstates + 1):
108
+ logger.info(
109
+ f"Processing instrument options = {inst_options.name}, {beam = }, {cs_step = }, and {modstate = }"
110
+ )
111
+ global_obj, local_obj = self.process_single_modstate(
112
+ instrument_options=inst_options,
113
+ cs_step=cs_step,
114
+ modstate=modstate,
115
+ beam=beam,
116
+ avg_dark=beam_dark,
117
+ avg_gain=beam_gain,
118
+ )
119
+ global_obj_dict[cs_step].append(global_obj)
120
+ local_obj_dict[cs_step].append(local_obj)
121
+
122
+ logger.info(
123
+ f"Writing PAC inputs for instrument options = {inst_options.name} and {beam = }"
124
+ )
125
+ self.write_pac_inputs(
126
+ global_obj_dict=global_obj_dict,
127
+ local_obj_dict=local_obj_dict,
128
+ instrument_options=inst_options,
129
+ beam=beam,
130
+ )
131
+
132
+ @property
133
+ def num_beams(self) -> int:
134
+ """Return the number of beams associated with this instance's arm_id."""
135
+ if self.arm_id == "CI":
136
+ return 1
137
+ return 2
138
+
139
+ def process_single_modstate(
140
+ self,
141
+ instrument_options: InstrumentOptionsBase,
142
+ cs_step: int,
143
+ modstate: int,
144
+ beam: int,
145
+ avg_dark: np.ndarray,
146
+ avg_gain: np.ndarray,
147
+ ) -> tuple[TrendL0FitsAccess, TrendL0FitsAccess]:
148
+ """
149
+ Process data for a single cs step, beam, and modstate.
150
+
151
+ This involves doing dark and gain correction before passing the result to the instrument-defined methods used to
152
+ extract global and local PAC input data.
153
+
154
+ If more than one file is found for the given ``(cs_step, beam, modstate)`` tuple then they are averaged together.
155
+ In this case, the header in the returned `~dkist_processing_trend.parsers.trend_l0_fits_access.TrendL0FitsAccess`
156
+ objects is randomly chosen from the input frames.
157
+ """
158
+ tags = [
159
+ TrendTag.task_polcal(),
160
+ TrendTag.arm_id(self.arm_id),
161
+ TrendTag.cs_step(cs_step),
162
+ TrendTag.modstate(modstate),
163
+ ]
164
+ logger.info(f"Found {self.count(tags)} frame(s)")
165
+
166
+ arrays = self.read(tags=tags, decoder=fits_array_decoder)
167
+ avg_array = average_numpy_arrays(arrays)
168
+
169
+ beam_array = self.extract_beam(array=avg_array, beam=beam)
170
+
171
+ dark_corrected_array = subtract_array_from_arrays(
172
+ arrays=beam_array, array_to_subtract=avg_dark
173
+ )
174
+ gain_corrected_array = next(
175
+ divide_arrays_by_array(arrays=dark_corrected_array, array_to_divide_by=avg_gain)
176
+ )
177
+
178
+ global_array = self.apply_global_instrument_options(
179
+ array=gain_corrected_array, instrument_options=instrument_options
180
+ )
181
+ local_array = self.apply_local_instrument_options(
182
+ array=gain_corrected_array, instrument_options=instrument_options
183
+ )
184
+
185
+ # Any header from the set of input data will do
186
+ header = next(self.read(tags=tags, decoder=fits_header_decoder))
187
+
188
+ return TrendL0FitsAccess(header=header, data=global_array), TrendL0FitsAccess(
189
+ header=header, data=local_array
190
+ )
191
+
192
+ def write_pac_inputs(
193
+ self,
194
+ global_obj_dict: dict[int, list[TrendL0FitsAccess]],
195
+ local_obj_dict: dict[int, list[TrendL0FitsAccess]],
196
+ instrument_options: InstrumentOptionsBase,
197
+ beam: int,
198
+ ) -> None:
199
+ """
200
+ Write the dicts containing the global and local data structures to disk.
201
+
202
+ Each of these data structures is essentially a `~dkist_processing_pac.input_data.drawer.Drawer`, but saved as
203
+ dicts in an ASDF file for easier serialization. This method converts each
204
+ `~dkist_processing_trend.parsers.trend_l0_fits_access.TrendL0FitsAccess` object into a dict of "header" and
205
+ "data" prior to writing.
206
+ """
207
+ global_output_dict = {
208
+ cs_step: [
209
+ {"header": fits_obj.header_dict, "data": fits_obj.data}
210
+ for fits_obj in modstate_list
211
+ ]
212
+ for cs_step, modstate_list in global_obj_dict.items()
213
+ }
214
+ local_output_dict = {
215
+ cs_step: [
216
+ {"header": fits_obj.header_dict, "data": fits_obj.data}
217
+ for fits_obj in modstate_list
218
+ ]
219
+ for cs_step, modstate_list in local_obj_dict.items()
220
+ }
221
+
222
+ base_tags = [
223
+ TrendTag.intermediate(),
224
+ TrendTag.arm_id(self.arm_id),
225
+ TrendTag.instrument_processing_options(instrument_options.name),
226
+ TrendTag.beam(beam),
227
+ ]
228
+ self.write(
229
+ data=global_output_dict,
230
+ tags=base_tags + [TrendTag.task_global_pac_input()],
231
+ encoder=asdf_encoder,
232
+ )
233
+ self.write(
234
+ data=local_output_dict,
235
+ tags=base_tags + [TrendTag.task_local_pac_input()],
236
+ encoder=asdf_encoder,
237
+ )
238
+
239
+ @abstractmethod
240
+ def extract_beam(self, array: np.ndarray, beam: int) -> np.ndarray:
241
+ """Extract the given beam from the input array."""
242
+
243
+ @abstractmethod
244
+ def apply_global_instrument_options(
245
+ self, array: np.ndarray, instrument_options: InstrumentOptionsBase
246
+ ) -> np.ndarray:
247
+ """
248
+ Do whatever instrument-specific processing is needed to produce a global PAC data point.
249
+
250
+ The shape of the final output should be (1, 1).
251
+ """
252
+
253
+ @abstractmethod
254
+ def apply_local_instrument_options(
255
+ self, array: np.ndarray, instrument_options: InstrumentOptionsBase
256
+ ) -> np.ndarray:
257
+ """Do whatever instrument-specific processing is needed to produce a local PAC data point."""