dkist-processing-visp 3.3.0__py3-none-any.whl → 5.1.1__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 (71) hide show
  1. dkist_processing_visp/__init__.py +1 -0
  2. dkist_processing_visp/config.py +1 -0
  3. dkist_processing_visp/models/constants.py +52 -21
  4. dkist_processing_visp/models/fits_access.py +20 -0
  5. dkist_processing_visp/models/metric_code.py +10 -0
  6. dkist_processing_visp/models/parameters.py +129 -19
  7. dkist_processing_visp/models/tags.py +1 -0
  8. dkist_processing_visp/models/task_name.py +1 -0
  9. dkist_processing_visp/parsers/map_repeats.py +1 -0
  10. dkist_processing_visp/parsers/modulator_states.py +1 -0
  11. dkist_processing_visp/parsers/polarimeter_mode.py +3 -1
  12. dkist_processing_visp/parsers/raster_step.py +4 -1
  13. dkist_processing_visp/parsers/spectrograph_configuration.py +75 -0
  14. dkist_processing_visp/parsers/time.py +15 -7
  15. dkist_processing_visp/parsers/visp_l0_fits_access.py +19 -8
  16. dkist_processing_visp/parsers/visp_l1_fits_access.py +1 -0
  17. dkist_processing_visp/tasks/__init__.py +1 -0
  18. dkist_processing_visp/tasks/assemble_movie.py +1 -0
  19. dkist_processing_visp/tasks/background_light.py +2 -1
  20. dkist_processing_visp/tasks/dark.py +5 -4
  21. dkist_processing_visp/tasks/geometric.py +132 -20
  22. dkist_processing_visp/tasks/instrument_polarization.py +13 -12
  23. dkist_processing_visp/tasks/l1_output_data.py +203 -0
  24. dkist_processing_visp/tasks/lamp.py +53 -93
  25. dkist_processing_visp/tasks/make_movie_frames.py +8 -6
  26. dkist_processing_visp/tasks/mixin/beam_access.py +1 -0
  27. dkist_processing_visp/tasks/mixin/corrections.py +54 -4
  28. dkist_processing_visp/tasks/mixin/downsample.py +1 -0
  29. dkist_processing_visp/tasks/parse.py +34 -4
  30. dkist_processing_visp/tasks/quality_metrics.py +5 -4
  31. dkist_processing_visp/tasks/science.py +126 -46
  32. dkist_processing_visp/tasks/solar.py +896 -456
  33. dkist_processing_visp/tasks/visp_base.py +2 -0
  34. dkist_processing_visp/tasks/write_l1.py +25 -5
  35. dkist_processing_visp/tests/conftest.py +99 -35
  36. dkist_processing_visp/tests/header_models.py +92 -20
  37. dkist_processing_visp/tests/local_trial_workflows/l0_cals_only.py +4 -23
  38. dkist_processing_visp/tests/local_trial_workflows/l0_polcals_as_science.py +421 -0
  39. dkist_processing_visp/tests/local_trial_workflows/l0_solar_gain_as_science.py +10 -29
  40. dkist_processing_visp/tests/local_trial_workflows/l0_to_l1.py +1 -21
  41. dkist_processing_visp/tests/local_trial_workflows/local_trial_helpers.py +98 -14
  42. dkist_processing_visp/tests/test_assemble_movie.py +2 -3
  43. dkist_processing_visp/tests/test_assemble_quality.py +89 -4
  44. dkist_processing_visp/tests/test_background_light.py +8 -5
  45. dkist_processing_visp/tests/test_dark.py +4 -3
  46. dkist_processing_visp/tests/test_fits_access.py +43 -0
  47. dkist_processing_visp/tests/test_geometric.py +45 -4
  48. dkist_processing_visp/tests/test_instrument_polarization.py +4 -3
  49. dkist_processing_visp/tests/test_lamp.py +22 -26
  50. dkist_processing_visp/tests/test_make_movie_frames.py +4 -4
  51. dkist_processing_visp/tests/test_map_repeats.py +3 -1
  52. dkist_processing_visp/tests/test_parameters.py +122 -21
  53. dkist_processing_visp/tests/test_parse.py +98 -14
  54. dkist_processing_visp/tests/test_quality.py +2 -3
  55. dkist_processing_visp/tests/test_science.py +113 -15
  56. dkist_processing_visp/tests/test_solar.py +318 -99
  57. dkist_processing_visp/tests/test_visp_constants.py +36 -8
  58. dkist_processing_visp/tests/test_workflows.py +1 -0
  59. dkist_processing_visp/tests/test_write_l1.py +17 -3
  60. dkist_processing_visp/workflows/__init__.py +1 -0
  61. dkist_processing_visp/workflows/l0_processing.py +8 -2
  62. dkist_processing_visp/workflows/trial_workflows.py +8 -2
  63. dkist_processing_visp-5.1.1.dist-info/METADATA +552 -0
  64. dkist_processing_visp-5.1.1.dist-info/RECORD +94 -0
  65. docs/conf.py +5 -1
  66. docs/gain_correction.rst +50 -42
  67. dkist_processing_visp/tasks/mixin/line_zones.py +0 -115
  68. dkist_processing_visp-3.3.0.dist-info/METADATA +0 -459
  69. dkist_processing_visp-3.3.0.dist-info/RECORD +0 -90
  70. {dkist_processing_visp-3.3.0.dist-info → dkist_processing_visp-5.1.1.dist-info}/WHEEL +0 -0
  71. {dkist_processing_visp-3.3.0.dist-info → dkist_processing_visp-5.1.1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  """ViSP lamp calibration task. See :doc:`this page </gain_correction>` for more information."""
2
- import numpy as np
2
+
3
3
  from dkist_processing_common.codecs.fits import fits_access_decoder
4
4
  from dkist_processing_common.codecs.fits import fits_array_decoder
5
5
  from dkist_processing_common.codecs.fits import fits_array_encoder
@@ -29,13 +29,14 @@ class LampCalibration(
29
29
 
30
30
  Parameters
31
31
  ----------
32
- recipe_run_id : int
32
+ recipe_run_id
33
33
  id of the recipe run used to identify the workflow run this task is part of
34
- workflow_name : str
34
+
35
+ workflow_name
35
36
  name of the workflow to which this instance of the task belongs
36
- workflow_version : str
37
- version of the workflow to which this instance of the task belongs
38
37
 
38
+ workflow_version
39
+ version of the workflow to which this instance of the task belongs
39
40
  """
40
41
 
41
42
  record_provenance = True
@@ -44,9 +45,11 @@ class LampCalibration(
44
45
  """
45
46
  For each beam.
46
47
 
47
- - Gather input lamp gain and averaged dark arrays
48
- - Calculate master lamp
49
- - Write master lamp
48
+ - Normalize all input arrays by the number of frames per FPA
49
+ - Subtract the average dark frame corresponding to the matching readout exposure time
50
+ - Average all different readout exposure time arrays (if applicable)
51
+ - Interpolate over the hairlines
52
+ - Write final lamp gain to disk
50
53
  - Record quality metrics
51
54
 
52
55
  Returns
@@ -54,12 +57,14 @@ class LampCalibration(
54
57
  None
55
58
 
56
59
  """
57
- with self.apm_task_step(
60
+ with self.telemetry_span(
58
61
  f"Generate lamp gains for {self.constants.num_beams} beams and {len(self.constants.lamp_readout_exp_times)} exposure times"
59
62
  ):
60
- for readout_exp_time in self.constants.lamp_readout_exp_times:
61
- for beam in range(1, self.constants.num_beams + 1):
62
- logger.info(f"Load dark for beam {beam}")
63
+ for beam in range(1, self.constants.num_beams + 1):
64
+ all_exp_time_arrays = []
65
+ for readout_exp_time in self.constants.lamp_readout_exp_times:
66
+ apm_str = f"{beam = } and {readout_exp_time = }"
67
+ logger.info(f"Load dark for beam {apm_str}")
63
68
  dark_array = next(
64
69
  self.read(
65
70
  tags=VispTag.intermediate_frame_dark(
@@ -69,20 +74,45 @@ class LampCalibration(
69
74
  )
70
75
  )
71
76
 
72
- for state_num in range(
73
- 1, self.constants.num_modstates + 1
74
- ): # modulator states go from 1 to n
75
- logger.info(
76
- f"Calculating average lamp gain for beam {beam}, modulator state {state_num}"
77
+ with self.telemetry_span(f"Computing gain for {apm_str}"):
78
+ tags = [
79
+ VispTag.input(),
80
+ VispTag.frame(),
81
+ VispTag.task_lamp_gain(),
82
+ VispTag.readout_exp_time(readout_exp_time),
83
+ ]
84
+ input_lamp_gain_objs = self.read(
85
+ tags=tags,
86
+ decoder=fits_access_decoder,
87
+ fits_access_class=VispL0FitsAccess,
77
88
  )
78
- self.compute_and_write_master_lamp_gain_for_modstate(
79
- modstate=state_num,
80
- dark_array=dark_array,
81
- beam=beam,
82
- readout_exp_time=readout_exp_time,
89
+
90
+ readout_normalized_arrays = (
91
+ self.beam_access_get_beam(o.data, beam=beam) / o.num_raw_frames_per_fpa
92
+ for o in input_lamp_gain_objs
93
+ )
94
+ averaged_gain_data = average_numpy_arrays(readout_normalized_arrays)
95
+
96
+ dark_corrected_gain_data = next(
97
+ subtract_array_from_arrays(averaged_gain_data, dark_array)
83
98
  )
84
99
 
85
- with self.apm_processing_step("Computing and logging quality metrics"):
100
+ all_exp_time_arrays.append(dark_corrected_gain_data)
101
+
102
+ avg_gain_array = average_numpy_arrays(all_exp_time_arrays)
103
+ filtered_gain_data = self.corrections_mask_hairlines(avg_gain_array)
104
+
105
+ with self.telemetry_span(f"Writing gain array for {apm_str}"):
106
+ self.write(
107
+ data=filtered_gain_data,
108
+ tags=[
109
+ VispTag.intermediate_frame(beam=beam),
110
+ VispTag.task_lamp_gain(),
111
+ ],
112
+ encoder=fits_array_encoder,
113
+ )
114
+
115
+ with self.telemetry_span("Computing and logging quality metrics"):
86
116
  no_of_raw_lamp_frames: int = self.scratch.count_all(
87
117
  tags=[
88
118
  VispTag.input(),
@@ -94,73 +124,3 @@ class LampCalibration(
94
124
  self.quality_store_task_type_counts(
95
125
  task_type=TaskName.lamp_gain.value, total_frames=no_of_raw_lamp_frames
96
126
  )
97
-
98
- def compute_and_write_master_lamp_gain_for_modstate(
99
- self,
100
- modstate: int,
101
- dark_array: np.ndarray,
102
- beam: int,
103
- readout_exp_time: float,
104
- ) -> None:
105
- """
106
- Compute and write master lamp gain for a given modstate and beam.
107
-
108
- Generally the algorithm is:
109
- 1. Average input gain arrays
110
- 2. Subtract average dark to get the dark corrected gain data
111
- 3. Normalize each beam to unity mean
112
- 4. Write to disk
113
-
114
- Parameters
115
- ----------
116
- modstate : int
117
- The modulator state to calculate the master lamp gain for
118
-
119
- dark_array : np.ndarray
120
- The master dark to be subtracted from each lamp gain file
121
-
122
- beam : int
123
- The number of the beam
124
-
125
- readout_exp_time : float
126
- Exposure time of single readout
127
-
128
- Returns
129
- -------
130
- None
131
- """
132
- apm_str = f"{beam = }, {modstate = }, and {readout_exp_time = }"
133
- # Get the input lamp gain arrays
134
- tags = [
135
- VispTag.input(),
136
- VispTag.frame(),
137
- VispTag.task_lamp_gain(),
138
- VispTag.modstate(modstate),
139
- VispTag.readout_exp_time(readout_exp_time),
140
- ]
141
- input_lamp_gain_objs = self.read(
142
- tags=tags, decoder=fits_access_decoder, fits_access_class=VispL0FitsAccess
143
- )
144
-
145
- with self.apm_processing_step(f"Computing gain for {apm_str}"):
146
-
147
- readout_normalized_arrays = (
148
- self.beam_access_get_beam(o.data, beam=beam) / o.num_raw_frames_per_fpa
149
- for o in input_lamp_gain_objs
150
- )
151
- averaged_gain_data = average_numpy_arrays(readout_normalized_arrays)
152
-
153
- dark_corrected_gain_data = next(
154
- subtract_array_from_arrays(averaged_gain_data, dark_array)
155
- )
156
- filtered_gain_data = self.corrections_mask_hairlines(dark_corrected_gain_data)
157
-
158
- with self.apm_writing_step(f"Writing gain array for {apm_str}"):
159
- self.write(
160
- data=filtered_gain_data,
161
- tags=[
162
- VispTag.intermediate_frame(beam=beam, modstate=modstate),
163
- VispTag.task_lamp_gain(),
164
- ],
165
- encoder=fits_array_encoder,
166
- )
@@ -1,9 +1,11 @@
1
1
  """Visp make movie frames task."""
2
+
2
3
  import numpy as np
3
4
  from astropy.io import fits
4
5
  from astropy.visualization import ZScaleInterval
5
6
  from dkist_processing_common.codecs.fits import fits_access_decoder
6
7
  from dkist_processing_common.codecs.fits import fits_array_encoder
8
+ from dkist_processing_common.models.fits_access import MetadataKey
7
9
  from dkist_service_configuration.logging import logger
8
10
 
9
11
  from dkist_processing_visp.models.tags import VispTag
@@ -46,7 +48,7 @@ class MakeVispMovieFrames(VispTaskBase):
46
48
  stokes_states = ["I", "Q", "U", "V"]
47
49
  # Loop over the number of raster scans
48
50
  for map_scan in range(1, self.constants.num_map_scans + 1):
49
- with self.apm_processing_step(f"Making movie frame for {map_scan = }"):
51
+ with self.telemetry_span(f"Making movie frame for {map_scan = }"):
50
52
  instrument_set = set()
51
53
  wavelength_set = set()
52
54
  time_obs = []
@@ -77,7 +79,7 @@ class MakeVispMovieFrames(VispTaskBase):
77
79
  fits_access_class=VispL1FitsAccess,
78
80
  )
79
81
  )
80
- data = calibrated_frame.data
82
+ data = np.nan_to_num(calibrated_frame.data, nan=0)
81
83
  if self.constants.num_raster_steps == 1:
82
84
  logger.info(
83
85
  "Only a single raster step found. Making a spectral movie."
@@ -120,16 +122,16 @@ class MakeVispMovieFrames(VispTaskBase):
120
122
  f"There should only be one instrument value in the headers. "
121
123
  f"Found {len(instrument_set)}: {instrument_set=}"
122
124
  )
123
- header["INSTRUME"] = instrument_set.pop()
125
+ header[MetadataKey.instrument] = instrument_set.pop()
124
126
  # The timestamp of a movie frame will be the time of raster scan start
125
- header["DATE-BEG"] = time_obs[0]
127
+ header[MetadataKey.time_obs] = time_obs[0]
126
128
  # Make sure only one wavelength value was found
127
129
  if len(wavelength_set) != 1:
128
130
  raise ValueError(
129
131
  f"There should only be one wavelength value in the headers. "
130
132
  f"Found {len(wavelength_set)}: {wavelength_set=}"
131
133
  )
132
- header["LINEWAV"] = wavelength_set.pop()
134
+ header[MetadataKey.wavelength] = wavelength_set.pop()
133
135
  # Write the movie frame file to disk and tag it, normalizing across stokes intensities
134
136
  if is_polarized:
135
137
  i_norm = ZScaleInterval()(stokes_i_data)
@@ -146,7 +148,7 @@ class MakeVispMovieFrames(VispTaskBase):
146
148
  else:
147
149
  movie_frame_data = stokes_i_data
148
150
 
149
- with self.apm_writing_step(f"Writing movie frame for {map_scan = }"):
151
+ with self.telemetry_span(f"Writing movie frame for {map_scan = }"):
150
152
  self.write(
151
153
  data=np.asarray(movie_frame_data),
152
154
  tags=[
@@ -1,4 +1,5 @@
1
1
  """Helper to manage extract a given beam from input data."""
2
+
2
3
  import numpy as np
3
4
 
4
5
 
@@ -1,4 +1,5 @@
1
1
  """Helper for ViSP array corrections."""
2
+
2
3
  from typing import Generator
3
4
  from typing import Iterable
4
5
 
@@ -16,6 +17,9 @@ class CorrectionsMixin:
16
17
  arrays: Iterable[np.ndarray] | np.ndarray,
17
18
  shift: np.ndarray = np.zeros(2),
18
19
  angle: float = 0.0,
20
+ mode: str = "edge",
21
+ order: int = 5,
22
+ cval: float = np.nan,
19
23
  ) -> Generator[np.ndarray, None, None]:
20
24
  """
21
25
  Shift and then rotate data.
@@ -34,6 +38,24 @@ class CorrectionsMixin:
34
38
  angle : float
35
39
  The angle (in radians) between slit hairlines and pixel axes.
36
40
 
41
+ mode : {'constant', 'edge', 'symmetric', 'reflect', 'wrap'}
42
+ Points outside the boundaries of the input are filled according
43
+ to the given mode. Modes match the behaviour of `numpy.pad`.
44
+
45
+ order : int
46
+ The order of interpolation. The order has to be in the range 0-5:
47
+ - 0: Nearest-neighbor
48
+ - 1: Bi-linear (default)
49
+ - 2: Bi-quadratic
50
+ - 3: Bi-cubic
51
+ - 4: Bi-quartic
52
+ - 5: Bi-quintic
53
+
54
+ cval : float
55
+ Used in conjunction with mode 'constant', the value outside
56
+ the image boundaries.
57
+
58
+
37
59
  Returns
38
60
  -------
39
61
  Generator
@@ -45,12 +67,21 @@ class CorrectionsMixin:
45
67
  array[np.where(array == np.inf)] = np.max(array[np.isfinite(array)])
46
68
  array[np.where(array == -np.inf)] = np.min(array[np.isfinite(array)])
47
69
  array[np.isnan(array)] = np.nanmedian(array)
48
- translated = affine_transform_arrays(array, translation=-shift, mode="reflect", order=5)
49
- yield next(rotate_arrays_about_point(translated, angle=-angle, mode="reflect", order=5))
70
+ translated = affine_transform_arrays(
71
+ array, translation=-shift, mode=mode, order=order, cval=cval
72
+ )
73
+ yield next(
74
+ rotate_arrays_about_point(
75
+ translated, angle=-angle, mode=mode, order=order, cval=cval
76
+ )
77
+ )
50
78
 
51
79
  @staticmethod
52
80
  def corrections_remove_spec_geometry(
53
- arrays: Iterable[np.ndarray] | np.ndarray, spec_shift: np.ndarray
81
+ arrays: Iterable[np.ndarray] | np.ndarray,
82
+ spec_shift: np.ndarray,
83
+ cval: float | None = None,
84
+ order: int = 3,
54
85
  ) -> Generator[np.ndarray, None, None]:
55
86
  """
56
87
  Remove spectral curvature.
@@ -66,6 +97,19 @@ class CorrectionsMixin:
66
97
  Array with shape (X), where X is the number of pixels in the spatial dimension.
67
98
  This dimension gives the spectral shift.
68
99
 
100
+ order : int
101
+ The order of interpolation. The order has to be in the range 0-5:
102
+ - 0: Nearest-neighbor
103
+ - 1: Bi-linear (default)
104
+ - 2: Bi-quadratic
105
+ - 3: Bi-cubic
106
+ - 4: Bi-quartic
107
+ - 5: Bi-quintic
108
+
109
+ cval : float
110
+ Used in conjunction with mode 'constant', the value outside
111
+ the image boundaries.
112
+
69
113
  Returns
70
114
  -------
71
115
  Generator
@@ -77,8 +121,14 @@ class CorrectionsMixin:
77
121
  numy = array.shape[1]
78
122
  array_output = np.zeros(array.shape)
79
123
  for j in range(numy):
124
+ if cval is None:
125
+ cval = np.nanmedian(array[:, j])
80
126
  array_output[:, j] = spnd.shift(
81
- array[:, j], -spec_shift[j], mode="constant", cval=np.nanmedian(array[:, j])
127
+ array[:, j],
128
+ -spec_shift[j],
129
+ mode="constant",
130
+ cval=cval,
131
+ order=order,
82
132
  )
83
133
  yield array_output
84
134
 
@@ -1,4 +1,5 @@
1
1
  """Helper mixin for downsampling data."""
2
+
2
3
  import numpy as np
3
4
 
4
5
 
@@ -1,24 +1,28 @@
1
1
  """ViSP parse task."""
2
+
2
3
  from typing import TypeVar
3
4
 
5
+ from dkist_processing_common.models.fits_access import MetadataKey
4
6
  from dkist_processing_common.models.flower_pot import Stem
5
7
  from dkist_processing_common.models.task_name import TaskName
6
8
  from dkist_processing_common.parsers.cs_step import CSStepFlower
7
9
  from dkist_processing_common.parsers.cs_step import NumCSStepBud
8
10
  from dkist_processing_common.parsers.retarder import RetarderNameBud
9
- from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
10
11
  from dkist_processing_common.parsers.task import PolcalTaskFlower
11
12
  from dkist_processing_common.parsers.task import TaskTypeFlower
13
+ from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
12
14
  from dkist_processing_common.parsers.time import ExposureTimeFlower
13
15
  from dkist_processing_common.parsers.time import ObsIpStartTimeBud
14
16
  from dkist_processing_common.parsers.time import ReadoutExpTimeFlower
15
17
  from dkist_processing_common.parsers.time import TaskExposureTimesBud
16
18
  from dkist_processing_common.parsers.time import TaskReadoutExpTimesBud
19
+ from dkist_processing_common.parsers.unique_bud import TaskUniqueBud
17
20
  from dkist_processing_common.parsers.unique_bud import UniqueBud
18
21
  from dkist_processing_common.parsers.wavelength import ObserveWavelengthBud
19
22
  from dkist_processing_common.tasks import ParseL0InputDataBase
20
23
 
21
24
  from dkist_processing_visp.models.constants import VispBudName
25
+ from dkist_processing_visp.models.fits_access import VispMetadataKey
22
26
  from dkist_processing_visp.models.parameters import VispParsingParameters
23
27
  from dkist_processing_visp.parsers.map_repeats import MapScanFlower
24
28
  from dkist_processing_visp.parsers.map_repeats import NumMapScansBud
@@ -27,6 +31,8 @@ from dkist_processing_visp.parsers.modulator_states import NumberModulatorStates
27
31
  from dkist_processing_visp.parsers.polarimeter_mode import PolarimeterModeBud
28
32
  from dkist_processing_visp.parsers.raster_step import RasterScanStepFlower
29
33
  from dkist_processing_visp.parsers.raster_step import TotalRasterStepsBud
34
+ from dkist_processing_visp.parsers.spectrograph_configuration import IncidentLightAngleBud
35
+ from dkist_processing_visp.parsers.spectrograph_configuration import ReflectedLightAngleBud
30
36
  from dkist_processing_visp.parsers.time import DarkReadoutExpTimePickyBud
31
37
  from dkist_processing_visp.parsers.time import NonDarkNonPolcalTaskReadoutExpTimesBud
32
38
  from dkist_processing_visp.parsers.visp_l0_fits_access import VispL0FitsAccess
@@ -72,6 +78,7 @@ class ParseL0VispInputData(ParseL0InputDataBase):
72
78
  def constant_buds(self) -> list[S]:
73
79
  """Add ViSP specific constants to common constants."""
74
80
  return super().constant_buds + [
81
+ UniqueBud(constant_name=VispBudName.arm_id.value, metadata_key=VispMetadataKey.arm_id),
75
82
  NumMapScansBud(),
76
83
  TotalRasterStepsBud(),
77
84
  NumCSStepBud(self.parameters.max_cs_step_time_sec),
@@ -82,6 +89,20 @@ class ParseL0VispInputData(ParseL0InputDataBase):
82
89
  RetarderNameBud(),
83
90
  NonDarkNonPolcalTaskReadoutExpTimesBud(),
84
91
  DarkReadoutExpTimePickyBud(),
92
+ IncidentLightAngleBud(),
93
+ ReflectedLightAngleBud(),
94
+ TaskUniqueBud(
95
+ constant_name=VispBudName.grating_constant_inverse_mm.value,
96
+ metadata_key=VispMetadataKey.grating_constant_inverse_mm,
97
+ ip_task_types=[TaskName.observe.value, TaskName.solar_gain.value],
98
+ task_type_parsing_function=parse_header_ip_task_with_gains,
99
+ ),
100
+ TaskUniqueBud(
101
+ constant_name=VispBudName.solar_gain_ip_start_time.value,
102
+ metadata_key=MetadataKey.ip_start_time,
103
+ ip_task_types=TaskName.solar_gain,
104
+ task_type_parsing_function=parse_header_ip_task_with_gains,
105
+ ),
85
106
  TaskExposureTimesBud(
86
107
  stem_name=VispBudName.lamp_exposure_times.value,
87
108
  ip_task_types=TaskName.lamp_gain.value,
@@ -122,9 +143,18 @@ class ParseL0VispInputData(ParseL0InputDataBase):
122
143
  ip_task_types=TaskName.polcal.value,
123
144
  header_task_parsing_func=parse_header_ip_task_with_gains,
124
145
  ),
125
- UniqueBud(constant_name=VispBudName.axis_1_type.value, metadata_key="axis_1_type"),
126
- UniqueBud(constant_name=VispBudName.axis_2_type.value, metadata_key="axis_2_type"),
127
- UniqueBud(constant_name=VispBudName.axis_3_type.value, metadata_key="axis_3_type"),
146
+ UniqueBud(
147
+ constant_name=VispBudName.axis_1_type.value,
148
+ metadata_key=VispMetadataKey.axis_1_type,
149
+ ),
150
+ UniqueBud(
151
+ constant_name=VispBudName.axis_2_type.value,
152
+ metadata_key=VispMetadataKey.axis_2_type,
153
+ ),
154
+ UniqueBud(
155
+ constant_name=VispBudName.axis_3_type.value,
156
+ metadata_key=VispMetadataKey.axis_3_type,
157
+ ),
128
158
  ]
129
159
 
130
160
  @property
@@ -1,4 +1,5 @@
1
1
  """ViSP quality metrics task."""
2
+
2
3
  from dataclasses import dataclass
3
4
  from dataclasses import field
4
5
  from typing import Iterable
@@ -105,7 +106,7 @@ class VispL1QualityMetrics(VispTaskBase, QualityMixin):
105
106
 
106
107
  def compute_sensitivity(self) -> None:
107
108
  """Compute RMS noise and sensitivity estimate for L1 Visp frames."""
108
- with self.apm_processing_step("Calculating polarization metrics"):
109
+ with self.telemetry_span("Calculating polarization metrics"):
109
110
  all_datetimes = []
110
111
  all_I_sensitivity = []
111
112
  all_Q_sensitivity = []
@@ -161,7 +162,7 @@ class VispL1QualityMetrics(VispTaskBase, QualityMixin):
161
162
  continue
162
163
 
163
164
  # compute sensitivity for this Stokes parameter
164
- data_list.append(np.std(stokes_frame.data) / stokesI_med)
165
+ data_list.append(np.nanstd(stokes_frame.data) / stokesI_med)
165
166
 
166
167
  all_datetimes.append(Time(np.mean(polarization_data.datetimes), format="mjd").isot)
167
168
  for target, source in zip(
@@ -173,7 +174,7 @@ class VispL1QualityMetrics(VispTaskBase, QualityMixin):
173
174
  continue
174
175
  target.append(np.mean(source))
175
176
 
176
- with self.apm_step("Sending lists for storage"):
177
+ with self.telemetry_span("Sending lists for storage"):
177
178
  for stokes_index, stokes_noise in zip(
178
179
  ("I", "Q", "U", "V"),
179
180
  (all_I_sensitivity, all_Q_sensitivity, all_U_sensitivity, all_V_sensitivity),
@@ -186,7 +187,7 @@ class VispL1QualityMetrics(VispTaskBase, QualityMixin):
186
187
 
187
188
  def compute_noise(self):
188
189
  """Compute noise in data."""
189
- with self.apm_processing_step("Calculating L1 ViSP noise metrics"):
190
+ with self.telemetry_span("Calculating L1 ViSP noise metrics"):
190
191
  for stokes in ["I", "Q", "U", "V"]:
191
192
  tags = [VispTag.calibrated(), VispTag.frame(), VispTag.stokes(stokes)]
192
193
  if self.scratch.count_all(tags=tags) > 0: