dkist-processing-visp 2.20.14__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 (73) 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 +61 -20
  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 -24
  7. dkist_processing_visp/models/tags.py +22 -1
  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 +4 -2
  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 +24 -14
  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 +128 -18
  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 +50 -17
  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 +4 -3
  34. dkist_processing_visp/tasks/write_l1.py +38 -10
  35. dkist_processing_visp/tests/conftest.py +145 -47
  36. dkist_processing_visp/tests/header_models.py +157 -20
  37. dkist_processing_visp/tests/local_trial_workflows/l0_cals_only.py +21 -78
  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 +387 -0
  40. dkist_processing_visp/tests/local_trial_workflows/l0_to_l1.py +18 -75
  41. dkist_processing_visp/tests/local_trial_workflows/local_trial_helpers.py +346 -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 +51 -44
  45. dkist_processing_visp/tests/test_dark.py +4 -3
  46. dkist_processing_visp/tests/test_downsample.py +1 -0
  47. dkist_processing_visp/tests/test_fits_access.py +43 -0
  48. dkist_processing_visp/tests/test_geometric.py +45 -4
  49. dkist_processing_visp/tests/test_instrument_polarization.py +72 -9
  50. dkist_processing_visp/tests/test_lamp.py +22 -26
  51. dkist_processing_visp/tests/test_make_movie_frames.py +4 -4
  52. dkist_processing_visp/tests/test_map_repeats.py +3 -1
  53. dkist_processing_visp/tests/test_parameters.py +122 -21
  54. dkist_processing_visp/tests/test_parse.py +164 -18
  55. dkist_processing_visp/tests/test_quality.py +3 -4
  56. dkist_processing_visp/tests/test_science.py +113 -15
  57. dkist_processing_visp/tests/test_solar.py +318 -99
  58. dkist_processing_visp/tests/test_visp_constants.py +38 -8
  59. dkist_processing_visp/tests/test_workflows.py +1 -0
  60. dkist_processing_visp/tests/test_write_l1.py +22 -3
  61. dkist_processing_visp/workflows/__init__.py +1 -0
  62. dkist_processing_visp/workflows/l0_processing.py +10 -3
  63. dkist_processing_visp/workflows/trial_workflows.py +8 -2
  64. dkist_processing_visp-5.1.1.dist-info/METADATA +552 -0
  65. dkist_processing_visp-5.1.1.dist-info/RECORD +94 -0
  66. {dkist_processing_visp-2.20.14.dist-info → dkist_processing_visp-5.1.1.dist-info}/WHEEL +1 -1
  67. docs/conf.py +5 -1
  68. docs/gain_correction.rst +52 -44
  69. docs/science_calibration.rst +7 -0
  70. dkist_processing_visp/tasks/mixin/line_zones.py +0 -115
  71. dkist_processing_visp-2.20.14.dist-info/METADATA +0 -196
  72. dkist_processing_visp-2.20.14.dist-info/RECORD +0 -89
  73. {dkist_processing_visp-2.20.14.dist-info → dkist_processing_visp-5.1.1.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,14 @@
1
1
  """ViSP base class."""
2
+
2
3
  from abc import ABC
3
4
 
4
5
  from dkist_processing_common.tasks import WorkflowTaskBase
5
- from dkist_processing_common.tasks.mixin.input_dataset import InputDatasetMixin
6
6
 
7
7
  from dkist_processing_visp.models.constants import VispConstants
8
8
  from dkist_processing_visp.models.parameters import VispParameters
9
9
 
10
10
 
11
- class VispTaskBase(WorkflowTaskBase, InputDatasetMixin, ABC):
11
+ class VispTaskBase(WorkflowTaskBase, ABC):
12
12
  """
13
13
  Task class for base ViSP tasks.
14
14
 
@@ -42,7 +42,8 @@ class VispTaskBase(WorkflowTaskBase, InputDatasetMixin, ABC):
42
42
  workflow_version=workflow_version,
43
43
  )
44
44
  self.parameters = VispParameters(
45
- self.input_dataset_parameters,
45
+ scratch=self.scratch,
46
46
  obs_ip_start_time=self.constants.obs_ip_start_time,
47
47
  wavelength=self.constants.wavelength,
48
+ arm_id=self.constants.arm_id,
48
49
  )
@@ -1,14 +1,17 @@
1
1
  """Visp write L1 task."""
2
+
2
3
  from functools import cache
3
4
  from typing import Literal
4
5
 
5
6
  import astropy.units as u
6
7
  from astropy.io import fits
8
+ from dkist_processing_common.models.fits_access import MetadataKey
7
9
  from dkist_processing_common.tasks import WriteL1Frame
8
10
  from dkist_processing_common.tasks.write_l1 import WavelengthRange
9
11
  from dkist_service_configuration.logging import logger
10
12
 
11
13
  from dkist_processing_visp.models.constants import VispConstants
14
+ from dkist_processing_visp.models.fits_access import VispMetadataKey
12
15
 
13
16
  cached_info_logger = cache(logger.info)
14
17
  __all__ = ["VispWriteL1Frame"]
@@ -54,7 +57,7 @@ class VispWriteL1Frame(WriteL1Frame):
54
57
  """
55
58
  # Correct the headers for the number of map and scan steps per map due to potential observation aborts
56
59
  header["VSPNMAPS"] = self.constants.num_map_scans
57
- header["VSPNSTP"] = self.constants.num_raster_steps
60
+ header[VispMetadataKey.total_raster_steps] = self.constants.num_raster_steps
58
61
 
59
62
  if stokes.upper() not in self.constants.stokes_params:
60
63
  raise ValueError("The stokes parameter must be one of I, Q, U, V")
@@ -71,24 +74,27 @@ class VispWriteL1Frame(WriteL1Frame):
71
74
  header[f"DTYPE{i}"] = "SPATIAL"
72
75
  header[f"DPNAME{i}"] = "spatial along slit"
73
76
  header[f"DWNAME{i}"] = "helioprojective latitude"
74
- header[f"CNAME{i}"] = "helioprojective latitude"
77
+ if i < header["NAXIS"]:
78
+ header[f"CNAME{i}"] = "helioprojective latitude"
75
79
  header[f"DUNIT{i}"] = header[f"CUNIT{i}"]
76
80
  elif axis_type == "AWAV":
77
81
  header[f"DNAXIS{i}"] = header[f"NAXIS{i}"]
78
82
  header[f"DTYPE{i}"] = "SPECTRAL"
79
83
  header[f"DPNAME{i}"] = "dispersion axis"
80
84
  header[f"DWNAME{i}"] = "wavelength"
81
- header[f"CNAME{i}"] = "wavelength"
85
+ if i < header["NAXIS"]:
86
+ header[f"CNAME{i}"] = "wavelength"
82
87
  header[f"DUNIT{i}"] = header[f"CUNIT{i}"]
83
88
  elif axis_type == "HPLN-TAN":
84
89
  header[f"DNAXIS{i}"] = self.constants.num_raster_steps
85
90
  header[f"DTYPE{i}"] = "SPATIAL"
86
91
  header[f"DPNAME{i}"] = "raster scan step number"
87
92
  header[f"DWNAME{i}"] = "helioprojective longitude"
88
- header[f"CNAME{i}"] = "helioprojective longitude"
93
+ if i < header["NAXIS"]:
94
+ header[f"CNAME{i}"] = "helioprojective longitude"
89
95
  header[f"DUNIT{i}"] = header[f"CUNIT{i}"]
90
96
  # Current position in raster scan which counts from zero
91
- header[f"DINDEX{i}"] = header["VSPSTP"] + 1
97
+ header[f"DINDEX{i}"] = header[VispMetadataKey.raster_scan_step] + 1
92
98
  else:
93
99
  raise ValueError(
94
100
  f"Unexpected axis type. Expected ['HPLT-TAN', 'AWAV', 'HPLN-TAN']. Got {axis_type}"
@@ -101,13 +107,14 @@ class VispWriteL1Frame(WriteL1Frame):
101
107
  if self.constants.num_map_scans > 1:
102
108
  cached_info_logger("Adding map scan dataset axis")
103
109
  num_axis += 1
104
- header[
105
- f"DNAXIS{num_axis}"
106
- ] = self.constants.num_map_scans # total number of raster scans in the dataset
110
+ header[f"DNAXIS{num_axis}"] = (
111
+ self.constants.num_map_scans
112
+ ) # total number of raster scans in the dataset
107
113
  header[f"DTYPE{num_axis}"] = "TEMPORAL"
108
114
  header[f"DPNAME{num_axis}"] = "raster map repeat number"
109
115
  header[f"DWNAME{num_axis}"] = "time"
110
- header[f"CNAME{num_axis}"] = "time"
116
+ if num_axis < header["NAXIS"]:
117
+ header[f"CNAME{num_axis}"] = "time"
111
118
  header[f"DUNIT{num_axis}"] = "s"
112
119
  # Temporal position in dataset
113
120
  header[f"DINDEX{num_axis}"] = header["VSPMAP"] # Current raster scan
@@ -120,7 +127,8 @@ class VispWriteL1Frame(WriteL1Frame):
120
127
  header[f"DTYPE{num_axis}"] = "STOKES"
121
128
  header[f"DPNAME{num_axis}"] = "polarization state"
122
129
  header[f"DWNAME{num_axis}"] = "polarization state"
123
- header[f"CNAME{num_axis}"] = "polarization state"
130
+ if num_axis < header["NAXIS"]:
131
+ header[f"CNAME{num_axis}"] = "polarization state"
124
132
  header[f"DUNIT{num_axis}"] = ""
125
133
  # Stokes position in dataset - stokes axis goes from 1-4
126
134
  header[f"DINDEX{num_axis}"] = self.constants.stokes_params.index(stokes.upper()) + 1
@@ -140,6 +148,9 @@ class VispWriteL1Frame(WriteL1Frame):
140
148
  header["NBIN3"] = 1
141
149
  header["NBIN"] = header["NBIN1"] * header["NBIN2"] * header["NBIN3"]
142
150
 
151
+ # Values don't have any units because they are relative to disk center
152
+ header["BUNIT"] = ("", "Values are relative to disk center. See calibration docs.")
153
+
143
154
  return header
144
155
 
145
156
  def calculate_date_end(self, header: fits.Header) -> str:
@@ -185,3 +196,20 @@ class VispWriteL1Frame(WriteL1Frame):
185
196
  min=u.Quantity(minimum, unit=wavelength_unit),
186
197
  max=u.Quantity(maximum, unit=wavelength_unit),
187
198
  )
199
+
200
+ def add_timing_headers(self, header: fits.Header) -> fits.Header:
201
+ """
202
+ Add timing headers to the FITS header.
203
+
204
+ This method adds or updates headers related to frame timings.
205
+ """
206
+ # The source data is based on L0 data but L1 data takes L0 cadence * modstates to obtain.
207
+ # This causes both the cadence and the full exposure time to be num_modstates times longer.
208
+ header["CADENCE"] = self.constants.average_cadence * self.constants.num_modstates
209
+ header["CADMIN"] = self.constants.minimum_cadence * self.constants.num_modstates
210
+ header["CADMAX"] = self.constants.maximum_cadence * self.constants.num_modstates
211
+ header["CADVAR"] = self.constants.variance_cadence * self.constants.num_modstates
212
+ header[MetadataKey.fpa_exposure_time_ms] = (
213
+ header[MetadataKey.fpa_exposure_time_ms] * self.constants.num_modstates
214
+ )
215
+ return header
@@ -1,8 +1,10 @@
1
1
  import json
2
2
  from dataclasses import asdict
3
3
  from dataclasses import dataclass
4
+ from dataclasses import field
4
5
  from dataclasses import is_dataclass
5
6
  from random import randint
7
+ from typing import Any
6
8
  from typing import Callable
7
9
  from typing import Type
8
10
 
@@ -12,10 +14,15 @@ from astropy.io import fits
12
14
  from dkist_data_simulator.spec122 import Spec122Dataset
13
15
  from dkist_header_validator.translator import sanitize_to_spec214_level1
14
16
  from dkist_header_validator.translator import translate_spec122_to_spec214_l0
17
+ from dkist_processing_common.codecs.basemodel import basemodel_encoder
15
18
  from dkist_processing_common.codecs.fits import fits_array_encoder
16
19
  from dkist_processing_common.codecs.fits import fits_hdulist_encoder
20
+ from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
17
21
  from dkist_processing_common.tasks import WorkflowTaskBase
18
22
 
23
+ # Don't remove this; tests will break
24
+ from dkist_processing_common.tests.mock_metadata_store import fake_gql_client
25
+
19
26
  from dkist_processing_visp.models.constants import VispConstants
20
27
  from dkist_processing_visp.models.parameters import VispParameters
21
28
  from dkist_processing_visp.models.tags import VispTag
@@ -38,6 +45,7 @@ def init_visp_constants_db():
38
45
 
39
46
  @dataclass
40
47
  class VispConstantsDb:
48
+ ARM_ID: int = 1
41
49
  POLARIMETER_MODE: str = "observe_polarimetric"
42
50
  OBS_IP_START_TIME: str = "2022-11-28T13:54:00"
43
51
  NUM_MODSTATES: int = 10
@@ -61,8 +69,12 @@ class VispConstantsDb:
61
69
  LAMP_READOUT_EXP_TIMES: tuple[float] = (200.0,)
62
70
  SOLAR_READOUT_EXP_TIMES: tuple[float] = (2.0,)
63
71
  OBSERVE_READOUT_EXP_TIMES: tuple[float] = (0.02,)
64
- POLCAL_READOUT_EXP_TIMES: tuple[float] = ()
72
+ POLCAL_READOUT_EXP_TIMES: tuple[float] = (0.02,)
65
73
  SPECTRAL_LINE: str = "VISP Ca II H"
74
+ INCIDENT_LIGHT_ANGLE_DEG: float = 73.22
75
+ REFLECTED_LIGHT_ANGLE_DEG: float = 64.92
76
+ GRATING_CONSTANT_INVERSE_MM: float = 316.0
77
+ SOLAR_GAIN_IP_START_TIME: str = "2025-09-24T20:00:00"
66
78
  STOKES_PARAMS: tuple[str] = (
67
79
  "I",
68
80
  "Q",
@@ -81,6 +93,7 @@ class VispConstantsDb:
81
93
  AXIS_1_TYPE: str = "HPLT-TAN"
82
94
  AXIS_2_TYPE: str = "AWAV"
83
95
  AXIS_3_TYPE: str = "HPLN-TAN"
96
+ RETARDER_NAME: str = "SiO2 OC"
84
97
 
85
98
 
86
99
  @pytest.fixture()
@@ -124,19 +137,59 @@ class VispInputDatasetParameterValues:
124
137
  visp_geo_upsample_factor: float = 10.0
125
138
  visp_geo_max_shift: float = 40.0
126
139
  visp_geo_poly_fit_order: int = 3
127
- visp_solar_spectral_avg_window: WavelengthParameter = WavelengthParameter(
128
- values=(800, 800, 800, 800)
140
+ visp_geo_zone_prominence: WavelengthParameter = WavelengthParameter(values=(0.2, 0.2, 0.3, 0.2))
141
+ visp_geo_zone_width: WavelengthParameter = WavelengthParameter(values=(7, 2, 3, 2))
142
+ visp_geo_zone_bg_order: WavelengthParameter = WavelengthParameter(values=(21, 22, 11, 22))
143
+ visp_geo_zone_normalization_percentile: WavelengthParameter = WavelengthParameter(
144
+ values=(90, 99, 90, 90)
145
+ )
146
+ visp_geo_zone_rel_height: float = 0.97
147
+ visp_solar_spatial_median_filter_width_px: WavelengthParameter = WavelengthParameter(
148
+ values=(250, 250, 250, 250)
129
149
  )
130
150
  visp_solar_characteristic_spatial_normalization_percentile: float = 90.0
131
- visp_solar_zone_prominence: WavelengthParameter = WavelengthParameter(
132
- values=(0.2, 0.2, 0.3, 0.2)
151
+ visp_solar_vignette_initial_continuum_poly_fit_order: int = 6
152
+ visp_solar_vignette_crval_bounds_px: float = 7
153
+ visp_solar_vignette_dispersion_bounds_fraction: float = 0.02
154
+ visp_solar_vignette_wavecal_fit_kwargs: dict[str, Any] = field(
155
+ default_factory=lambda: {
156
+ "method": "differential_evolution",
157
+ "init": "halton",
158
+ "popsize": 1,
159
+ "tol": 1e-10,
160
+ }
133
161
  )
134
- visp_solar_zone_width: WavelengthParameter = WavelengthParameter(values=(7, 2, 3, 2))
135
- visp_solar_zone_bg_order: WavelengthParameter = WavelengthParameter(values=(21, 22, 11, 22))
136
- visp_solar_zone_normalization_percentile: WavelengthParameter = WavelengthParameter(
137
- values=(90, 99, 90, 90)
162
+ visp_solar_vignette_spectral_poly_fit_order: int = 12
163
+ visp_solar_vignette_min_samples: float = 0.9
164
+ visp_wavecal_camera_lens_parameters_1: tuple[float, float, float] = (
165
+ 0.7613,
166
+ 1.720e-4,
167
+ -8.139e-8,
168
+ )
169
+ visp_wavecal_camera_lens_parameters_2: tuple[float, float, float] = (
170
+ 0.9512,
171
+ 2.141e-4,
172
+ -1.014e-7,
173
+ )
174
+ visp_wavecal_camera_lens_parameters_3: tuple[float, float, float] = (
175
+ 0.1153e1,
176
+ 2.595e-4,
177
+ -1.230e-7,
178
+ )
179
+ visp_wavecal_pixel_pitch_micron_per_pix: float = 6.5
180
+ visp_wavecal_atlas_download_config: dict[str, str] = field(
181
+ default_factory=lambda: {
182
+ "base_url": "doi:10.5281/zenodo.14646787/",
183
+ "telluric_reference_atlas_file_name": "telluric_reference_atlas.npy",
184
+ "telluric_reference_atlas_hash_id": "md5:8db5e12508b293bca3495d81a0747447",
185
+ "solar_reference_atlas_file_name": "solar_reference_atlas.npy",
186
+ "solar_reference_atlas_hash_id": "md5:84ab4c50689ef235fe5ed4f7ee905ca0",
187
+ }
138
188
  )
139
- visp_solar_zone_rel_height: float = 0.97
189
+ visp_wavecal_init_crval_guess_normalization_percentile: float = 95
190
+ visp_wavecal_init_resolving_power: int = 150000
191
+ visp_wavecal_init_straylight_fraction: float = 0.2
192
+ visp_wavecal_init_opacity_factor: float = 5.0
140
193
  visp_polcal_spatial_median_filter_width_px: int = 10
141
194
  visp_polcal_num_spatial_bins: int = 10
142
195
  visp_polcal_demod_spatial_smooth_fit_order: int = 17
@@ -144,7 +197,6 @@ class VispInputDatasetParameterValues:
144
197
  visp_polcal_demod_upsample_order: int = 3
145
198
  visp_pac_remove_linear_I_trend: bool = True
146
199
  visp_pac_fit_mode: str = "use_M12_I_sys_per_step"
147
- visp_pac_init_set: str = "OCCal_VIS"
148
200
 
149
201
 
150
202
  @pytest.fixture(scope="session")
@@ -157,11 +209,37 @@ def testing_obs_ip_start_time() -> str:
157
209
  return "1946-11-20T12:34:56"
158
210
 
159
211
 
212
+ @pytest.fixture(scope="session")
213
+ def testing_grating_constant() -> float:
214
+ # Just make it different than the defaults in header_models.py
215
+ return 317.2
216
+
217
+
218
+ @pytest.fixture(scope="session")
219
+ def testing_grating_angle() -> float:
220
+ return -43.2
221
+
222
+
223
+ @pytest.fixture(scope="session")
224
+ def testing_arm_position() -> float:
225
+ return -5.3
226
+
227
+
228
+ @pytest.fixture(scope="session")
229
+ def testing_solar_ip_start_time() -> str:
230
+ return "1946-11-21T12:34:56"
231
+
232
+
233
+ @pytest.fixture(scope="session")
234
+ def testing_arm_id() -> int:
235
+ return 2
236
+
237
+
160
238
  @pytest.fixture(scope="session")
161
239
  def input_dataset_document_simple_parameters_part():
162
240
  """Convert a dataclass of parameterValues into an actual input dataset parameters part."""
163
241
 
164
- def get_input_dataset_parameters_part(parameter_values: dataclass):
242
+ def make_input_dataset_parameters_part(parameter_values: dataclass):
165
243
  parameters_list = []
166
244
  value_id = randint(1000, 2000)
167
245
  for pn, pv in asdict(parameter_values).items():
@@ -178,35 +256,42 @@ def input_dataset_document_simple_parameters_part():
178
256
  parameters_list.append(parameter)
179
257
  return parameters_list
180
258
 
181
- return get_input_dataset_parameters_part
259
+ return make_input_dataset_parameters_part
182
260
 
183
261
 
184
262
  @pytest.fixture(scope="session")
185
263
  def assign_input_dataset_doc_to_task(
186
- input_dataset_document_simple_parameters_part, testing_obs_ip_start_time, testing_wavelength
264
+ input_dataset_document_simple_parameters_part,
265
+ testing_obs_ip_start_time,
266
+ testing_wavelength,
267
+ testing_arm_id,
187
268
  ):
188
269
  def update_task(
189
- task,
270
+ task: WorkflowTaskBase,
190
271
  parameter_values,
191
272
  parameter_class=VispParameters,
192
273
  obs_ip_start_time=testing_obs_ip_start_time,
274
+ arm_id=testing_arm_id,
193
275
  ):
194
- doc_path = task.scratch.workflow_base_path / "dataset_parameters.json"
195
- with open(doc_path, "w") as f:
196
- f.write(json.dumps(input_dataset_document_simple_parameters_part(parameter_values)))
197
- task.tag(doc_path, VispTag.input_dataset_parameters())
276
+ task.write(
277
+ data=InputDatasetPartDocumentList(
278
+ doc_list=input_dataset_document_simple_parameters_part(parameter_values)
279
+ ),
280
+ tags=VispTag.input_dataset_parameters(),
281
+ encoder=basemodel_encoder,
282
+ )
198
283
  task.parameters = parameter_class(
199
- task.input_dataset_parameters,
284
+ scratch=task.scratch,
200
285
  obs_ip_start_time=obs_ip_start_time,
201
286
  wavelength=testing_wavelength,
287
+ arm_id=str(arm_id),
202
288
  )
203
289
 
204
290
  return update_task
205
291
 
206
292
 
207
293
  def make_random_data(frame: Spec122Dataset) -> np.ndarray:
208
- shape = frame.array_shape[1:]
209
- data = np.random.random(shape)
294
+ data = np.random.random(frame.array_shape)
210
295
 
211
296
  return data
212
297
 
@@ -338,28 +423,14 @@ def write_intermediate_background_to_task(
338
423
  )
339
424
 
340
425
 
341
- def write_intermediate_lamp_to_task(
342
- task, *, lamp_signal: float, beam: int, modstate: int, data_shape: tuple[int, int]
343
- ):
344
- lamp_array = np.ones(data_shape) * lamp_signal
345
- task.write(
346
- data=lamp_array,
347
- tags=[
348
- VispTag.intermediate_frame(beam=beam, modstate=modstate),
349
- VispTag.task_lamp_gain(),
350
- ],
351
- encoder=fits_array_encoder,
352
- )
353
-
354
-
355
426
  def write_intermediate_solar_to_task(
356
- task, *, solar_signal: float, beam: int, modstate: int, data_shape: tuple[int, int]
427
+ task, *, solar_signal: float, beam: int, data_shape: tuple[int, int]
357
428
  ):
358
429
  solar_array = np.ones(data_shape) * solar_signal
359
430
  task.write(
360
431
  data=solar_array,
361
432
  tags=[
362
- VispTag.intermediate_frame(beam=beam, modstate=modstate),
433
+ VispTag.intermediate_frame(beam=beam),
363
434
  VispTag.task_solar_gain(),
364
435
  ],
365
436
  encoder=fits_array_encoder,
@@ -409,19 +480,46 @@ def write_intermediate_geometric_to_task(
409
480
  def write_dummy_intermediate_solar_cals_to_task(
410
481
  task,
411
482
  *,
412
- num_modstates: int,
413
483
  data_shape: tuple[int, int],
414
484
  ):
415
485
  solar_signal = 1.0
416
486
  for beam in [1, 2]:
417
- for modstate in range(1, num_modstates + 1):
418
- write_intermediate_solar_to_task(
419
- task=task,
420
- solar_signal=solar_signal,
421
- beam=beam,
422
- modstate=modstate,
423
- data_shape=data_shape,
424
- )
487
+ write_intermediate_solar_to_task(
488
+ task=task,
489
+ solar_signal=solar_signal,
490
+ beam=beam,
491
+ data_shape=data_shape,
492
+ )
493
+
494
+
495
+ def write_intermediate_polcal_darks_to_task(
496
+ task, *, dark_signal: float, readout_exp_time: float, data_shape: tuple[int, int]
497
+ ):
498
+ dark_cal = np.ones(data_shape) * dark_signal
499
+ # Need a dark for each beam
500
+ for beam in [1, 2]:
501
+ task.write(
502
+ data=dark_cal,
503
+ tags=VispTag.intermediate_frame_polcal_dark(
504
+ beam=beam, readout_exp_time=readout_exp_time
505
+ ),
506
+ encoder=fits_array_encoder,
507
+ )
508
+
509
+
510
+ def write_intermediate_polcal_gains_to_task(
511
+ task, *, gain_signal: float, readout_exp_time: float, data_shape: tuple[int, int]
512
+ ):
513
+ gain_cal = np.ones(data_shape) * gain_signal
514
+ # Need a dark for each beam
515
+ for beam in [1, 2]:
516
+ task.write(
517
+ data=gain_cal,
518
+ tags=VispTag.intermediate_frame_polcal_gain(
519
+ beam=beam, readout_exp_time=readout_exp_time
520
+ ),
521
+ encoder=fits_array_encoder,
522
+ )
425
523
 
426
524
 
427
525
  def tag_on_map_raster_stokes(frame: VispHeadersValidCalibratedFrames) -> list[str]: