imap-processing 0.18.0__py3-none-any.whl → 0.19.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.

Potentially problematic release.


This version of imap-processing might be problematic. Click here for more details.

Files changed (104) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/ancillary/ancillary_dataset_combiner.py +161 -1
  3. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +301 -274
  4. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +28 -28
  5. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1044 -203
  6. imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
  7. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +12 -0
  8. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
  9. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
  10. imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
  11. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +8 -91
  12. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +106 -16
  13. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
  14. imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
  15. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +85 -2
  16. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +12 -4
  18. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
  19. imap_processing/cli.py +95 -41
  20. imap_processing/codice/codice_l1a.py +131 -31
  21. imap_processing/codice/codice_l2.py +118 -10
  22. imap_processing/codice/constants.py +740 -595
  23. imap_processing/decom.py +1 -4
  24. imap_processing/ena_maps/ena_maps.py +32 -25
  25. imap_processing/ena_maps/utils/naming.py +8 -2
  26. imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
  27. imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
  28. imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
  29. imap_processing/glows/ancillary/imap_glows_pipeline_settings_20250923_v002.json +54 -0
  30. imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
  31. imap_processing/glows/l1b/glows_l1b.py +99 -9
  32. imap_processing/glows/l1b/glows_l1b_data.py +350 -38
  33. imap_processing/glows/l2/glows_l2.py +11 -0
  34. imap_processing/hi/hi_l1a.py +124 -3
  35. imap_processing/hi/hi_l1b.py +154 -71
  36. imap_processing/hi/hi_l2.py +84 -51
  37. imap_processing/hi/utils.py +153 -8
  38. imap_processing/hit/l0/constants.py +3 -0
  39. imap_processing/hit/l0/decom_hit.py +3 -6
  40. imap_processing/hit/l1a/hit_l1a.py +311 -21
  41. imap_processing/hit/l1b/hit_l1b.py +54 -126
  42. imap_processing/hit/l2/hit_l2.py +6 -6
  43. imap_processing/ialirt/calculate_ingest.py +219 -0
  44. imap_processing/ialirt/constants.py +12 -2
  45. imap_processing/ialirt/generate_coverage.py +15 -2
  46. imap_processing/ialirt/l0/ialirt_spice.py +5 -2
  47. imap_processing/ialirt/l0/parse_mag.py +293 -42
  48. imap_processing/ialirt/l0/process_hit.py +5 -3
  49. imap_processing/ialirt/l0/process_swapi.py +41 -25
  50. imap_processing/ialirt/process_ephemeris.py +70 -14
  51. imap_processing/idex/idex_l0.py +2 -2
  52. imap_processing/idex/idex_l1a.py +2 -3
  53. imap_processing/idex/idex_l1b.py +2 -3
  54. imap_processing/idex/idex_l2a.py +130 -4
  55. imap_processing/idex/idex_l2b.py +158 -143
  56. imap_processing/idex/idex_utils.py +1 -3
  57. imap_processing/lo/l0/lo_science.py +25 -24
  58. imap_processing/lo/l1b/lo_l1b.py +3 -3
  59. imap_processing/lo/l1c/lo_l1c.py +116 -50
  60. imap_processing/lo/l2/lo_l2.py +29 -29
  61. imap_processing/lo/lo_ancillary.py +55 -0
  62. imap_processing/mag/l1a/mag_l1a.py +1 -0
  63. imap_processing/mag/l1a/mag_l1a_data.py +26 -0
  64. imap_processing/mag/l1b/mag_l1b.py +3 -2
  65. imap_processing/mag/l1c/interpolation_methods.py +14 -15
  66. imap_processing/mag/l1c/mag_l1c.py +23 -6
  67. imap_processing/mag/l1d/mag_l1d.py +57 -14
  68. imap_processing/mag/l1d/mag_l1d_data.py +167 -30
  69. imap_processing/mag/l2/mag_l2_data.py +10 -2
  70. imap_processing/quality_flags.py +9 -1
  71. imap_processing/spice/geometry.py +76 -33
  72. imap_processing/spice/pointing_frame.py +0 -6
  73. imap_processing/spice/repoint.py +29 -2
  74. imap_processing/spice/spin.py +28 -8
  75. imap_processing/spice/time.py +12 -22
  76. imap_processing/swapi/l1/swapi_l1.py +10 -4
  77. imap_processing/swapi/l2/swapi_l2.py +15 -17
  78. imap_processing/swe/l1b/swe_l1b.py +1 -2
  79. imap_processing/ultra/constants.py +1 -24
  80. imap_processing/ultra/l0/ultra_utils.py +9 -11
  81. imap_processing/ultra/l1a/ultra_l1a.py +1 -2
  82. imap_processing/ultra/l1b/cullingmask.py +6 -3
  83. imap_processing/ultra/l1b/de.py +81 -23
  84. imap_processing/ultra/l1b/extendedspin.py +13 -10
  85. imap_processing/ultra/l1b/lookup_utils.py +281 -28
  86. imap_processing/ultra/l1b/quality_flag_filters.py +10 -1
  87. imap_processing/ultra/l1b/ultra_l1b_culling.py +161 -3
  88. imap_processing/ultra/l1b/ultra_l1b_extended.py +253 -47
  89. imap_processing/ultra/l1c/helio_pset.py +97 -24
  90. imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
  91. imap_processing/ultra/l1c/spacecraft_pset.py +83 -16
  92. imap_processing/ultra/l1c/ultra_l1c.py +6 -2
  93. imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
  94. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +385 -277
  95. imap_processing/ultra/l2/ultra_l2.py +0 -1
  96. imap_processing/ultra/utils/ultra_l1_utils.py +28 -3
  97. imap_processing/utils.py +3 -4
  98. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +2 -2
  99. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +102 -95
  100. imap_processing/idex/idex_l2c.py +0 -84
  101. imap_processing/spice/kernels.py +0 -187
  102. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/LICENSE +0 -0
  103. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
  104. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/entry_points.txt +0 -0
@@ -11,15 +11,12 @@ Paradigms for developing this module:
11
11
 
12
12
  import typing
13
13
  from enum import IntEnum
14
- from typing import Union
15
14
 
16
15
  import numpy as np
17
16
  import numpy.typing as npt
18
17
  import spiceypy
19
18
  from numpy.typing import NDArray
20
19
 
21
- from imap_processing.spice.kernels import ensure_spice
22
-
23
20
 
24
21
  class SpiceBody(IntEnum):
25
22
  """Enum containing SPICE IDs for bodies that we use."""
@@ -100,10 +97,8 @@ BORESIGHT_LOOKUP = {
100
97
  }
101
98
 
102
99
 
103
- @typing.no_type_check
104
- @ensure_spice
105
100
  def imap_state(
106
- et: Union[np.ndarray, float],
101
+ et: np.ndarray | float,
107
102
  ref_frame: SpiceFrame = SpiceFrame.ECLIPJ2000,
108
103
  abcorr: str = "NONE",
109
104
  observer: SpiceBody = SpiceBody.SUN,
@@ -137,14 +132,65 @@ def imap_state(
137
132
  return np.asarray(state)
138
133
 
139
134
 
135
+ def get_instrument_mounting_az_el(instrument: SpiceFrame) -> np.ndarray:
136
+ """
137
+ Calculate the azimuth and elevation angle of instrument mounting.
138
+
139
+ Azimuth and elevation to instrument mounting in the spacecraft frame.
140
+ Azimuth is measured in degrees from the spacecraft x-axis. Elevation is measured
141
+ in degrees from the spacecraft x-y plane.
142
+
143
+ Parameters
144
+ ----------
145
+ instrument : SpiceFrame
146
+ Instrument to get the azimuth and elevation angles for.
147
+
148
+ Returns
149
+ -------
150
+ instrument_mounting_az_el : np.ndarray
151
+ 2-element array containing azimuth and elevation of the instrument
152
+ mounting in the spacecraft frame. Azimuth is measured in degrees from
153
+ the spacecraft x-axis. Elevation is measured in degrees from the
154
+ spacecraft x-y plane.
155
+ """
156
+ # Each instrument can have a unique basis vector in the instrument
157
+ # frame that is used to compute the s/c to instrument mounting.
158
+ # Most of these vectors are the same as the instrument boresight vector.
159
+ mounting_normal_vector = {
160
+ SpiceFrame.IMAP_LO_BASE: np.array([0, -1, 0]),
161
+ SpiceFrame.IMAP_HI_45: np.array([0, 1, 0]),
162
+ SpiceFrame.IMAP_HI_90: np.array([0, 1, 0]),
163
+ SpiceFrame.IMAP_ULTRA_45: np.array([0, 0, 1]),
164
+ SpiceFrame.IMAP_ULTRA_90: np.array([0, 0, 1]),
165
+ SpiceFrame.IMAP_MAG: np.array([-1, 0, 0]),
166
+ SpiceFrame.IMAP_SWE: np.array([-1, 0, 0]),
167
+ SpiceFrame.IMAP_SWAPI: np.array([0, 0, -1]),
168
+ SpiceFrame.IMAP_CODICE: np.array([-1, 0, 0]),
169
+ SpiceFrame.IMAP_HIT: np.array([0, 1, 0]),
170
+ SpiceFrame.IMAP_IDEX: np.array([0, 1, 0]),
171
+ SpiceFrame.IMAP_GLOWS: np.array([0, 0, -1]),
172
+ }
173
+
174
+ # Get the instrument mounting normal vector expressed in the spacecraft frame
175
+ # The reference frames are fixed, so the et argument can be fixed at 0
176
+ instrument_normal_sc = frame_transform(
177
+ 0, mounting_normal_vector[instrument], instrument, SpiceFrame.IMAP_SPACECRAFT
178
+ )
179
+ # Convert the cartesian coordinate to azimuth/elevation angles in degrees
180
+ return np.rad2deg(
181
+ spiceypy.recazl(instrument_normal_sc, azccw=True, elplsz=True)[1:]
182
+ )
183
+
184
+
140
185
  def get_spacecraft_to_instrument_spin_phase_offset(instrument: SpiceFrame) -> float:
141
186
  """
142
187
  Get the spin phase offset from the spacecraft to the instrument.
143
188
 
144
189
  For now, the offset is a fixed lookup based on `Table 1: Nominal Instrument
145
- to S/C CS Transformations` in document `7516-0011_drw.pdf`. These fixed
146
- values will need to be updated based on calibration data or retrieved using
147
- SPICE and the latest IMAP frame kernel.
190
+ to S/C CS Transformations` in document `7516-0011_drw.pdf`. That Table
191
+ defines the angle from the spacecraft y-axis. We add 90 and take the modulous
192
+ with 360 in order to get the angle from the spacecraft x-axis. These fixed
193
+ values will need to be updated based on calibration data.
148
194
 
149
195
  Parameters
150
196
  ----------
@@ -156,26 +202,25 @@ def get_spacecraft_to_instrument_spin_phase_offset(instrument: SpiceFrame) -> fl
156
202
  spacecraft_to_instrument_spin_phase_offset : float
157
203
  The spin phase offset from the spacecraft to the instrument.
158
204
  """
159
- # TODO: Implement retrieval from SPICE?
160
- offset_lookup = {
161
- SpiceFrame.IMAP_LO_BASE: 330 / 360,
162
- SpiceFrame.IMAP_HI_45: 255 / 360,
163
- SpiceFrame.IMAP_HI_90: 285 / 360,
164
- SpiceFrame.IMAP_ULTRA_45: 33 / 360,
165
- SpiceFrame.IMAP_ULTRA_90: 210 / 360,
166
- SpiceFrame.IMAP_SWAPI: 168 / 360,
167
- SpiceFrame.IMAP_IDEX: 90 / 360,
168
- SpiceFrame.IMAP_CODICE: 136 / 360,
169
- SpiceFrame.IMAP_HIT: 30 / 360,
170
- SpiceFrame.IMAP_SWE: 153 / 360,
171
- SpiceFrame.IMAP_GLOWS: 127 / 360,
172
- SpiceFrame.IMAP_MAG: 0 / 360,
205
+ phase_offset_lookup = {
206
+ SpiceFrame.IMAP_LO_BASE: 60 / 360, # (330 + 90) % 360 = 60
207
+ SpiceFrame.IMAP_HI_45: 345 / 360, # 255 + 90 = 345
208
+ SpiceFrame.IMAP_HI_90: 15 / 360, # (285 + 90) % 360 = 15
209
+ SpiceFrame.IMAP_ULTRA_45: 123 / 360, # 33 + 90 = 123
210
+ SpiceFrame.IMAP_ULTRA_90: 300 / 360, # 210 + 90 = 300
211
+ SpiceFrame.IMAP_SWAPI: 258 / 360, # 168 + 90 = 258
212
+ SpiceFrame.IMAP_IDEX: 180 / 360, # 90 + 90 = 180
213
+ SpiceFrame.IMAP_CODICE: 226 / 360, # 136 + 90 = 226
214
+ SpiceFrame.IMAP_HIT: 120 / 360, # 30 + 90 = 120
215
+ SpiceFrame.IMAP_SWE: 243 / 360, # 153 + 90 = 243
216
+ SpiceFrame.IMAP_GLOWS: 217 / 360, # 127 + 90 = 217
217
+ SpiceFrame.IMAP_MAG: 90 / 360, # 0 + 90 = 90
173
218
  }
174
- return offset_lookup[instrument]
219
+ return phase_offset_lookup[instrument]
175
220
 
176
221
 
177
222
  def frame_transform(
178
- et: Union[float, npt.NDArray],
223
+ et: float | npt.NDArray,
179
224
  position: npt.NDArray,
180
225
  from_frame: SpiceFrame,
181
226
  to_frame: SpiceFrame,
@@ -249,7 +294,7 @@ def frame_transform(
249
294
 
250
295
 
251
296
  def frame_transform_az_el(
252
- et: Union[float, npt.NDArray],
297
+ et: float | npt.NDArray,
253
298
  az_el: npt.NDArray,
254
299
  from_frame: SpiceFrame,
255
300
  to_frame: SpiceFrame,
@@ -298,10 +343,8 @@ def frame_transform_az_el(
298
343
  return to_frame_az_el[..., 1:3]
299
344
 
300
345
 
301
- @typing.no_type_check
302
- @ensure_spice
303
346
  def get_rotation_matrix(
304
- et: Union[float, npt.NDArray],
347
+ et: float | npt.NDArray,
305
348
  from_frame: SpiceFrame,
306
349
  to_frame: SpiceFrame,
307
350
  ) -> npt.NDArray:
@@ -339,7 +382,7 @@ def get_rotation_matrix(
339
382
 
340
383
 
341
384
  def instrument_pointing(
342
- et: Union[float, npt.NDArray],
385
+ et: float | npt.NDArray,
343
386
  instrument: SpiceFrame,
344
387
  to_frame: SpiceFrame,
345
388
  cartesian: bool = False,
@@ -377,7 +420,7 @@ def instrument_pointing(
377
420
 
378
421
 
379
422
  def basis_vectors(
380
- et: Union[float, npt.NDArray],
423
+ et: float | npt.NDArray,
381
424
  from_frame: SpiceFrame,
382
425
  to_frame: SpiceFrame,
383
426
  ) -> npt.NDArray:
@@ -550,9 +593,9 @@ def cartesian_to_latitudinal(coords: NDArray, degrees: bool = True) -> NDArray:
550
593
 
551
594
 
552
595
  def solar_longitude(
553
- et: Union[np.ndarray, float],
596
+ et: np.ndarray | float,
554
597
  degrees: bool = True,
555
- ) -> Union[float, npt.NDArray]:
598
+ ) -> float | npt.NDArray:
556
599
  """
557
600
  Compute the solar longitude of the Imap Spacecraft.
558
601
 
@@ -1,7 +1,6 @@
1
1
  """Functions for retrieving repointing table data."""
2
2
 
3
3
  import logging
4
- import typing
5
4
  from collections.abc import Generator
6
5
  from contextlib import contextmanager
7
6
  from datetime import datetime, timezone
@@ -13,7 +12,6 @@ from imap_data_access import SPICEFilePath
13
12
  from numpy.typing import NDArray
14
13
 
15
14
  from imap_processing.spice.geometry import SpiceFrame
16
- from imap_processing.spice.kernels import ensure_spice
17
15
  from imap_processing.spice.repoint import get_repoint_data
18
16
  from imap_processing.spice.time import (
19
17
  TICK_DURATION,
@@ -164,8 +162,6 @@ def write_pointing_frame_ck(
164
162
  )
165
163
 
166
164
 
167
- @typing.no_type_check
168
- @ensure_spice
169
165
  def calculate_pointing_attitude_segments(
170
166
  ck_path: Path,
171
167
  ) -> NDArray:
@@ -292,8 +288,6 @@ def calculate_pointing_attitude_segments(
292
288
  return pointing_segments
293
289
 
294
290
 
295
- @typing.no_type_check
296
- @ensure_spice
297
291
  def _average_quaternions(et_times: np.ndarray) -> NDArray:
298
292
  """
299
293
  Average the quaternions.
@@ -3,7 +3,6 @@
3
3
  import functools
4
4
  import logging
5
5
  from pathlib import Path
6
- from typing import Union
7
6
 
8
7
  import numpy as np
9
8
  import pandas as pd
@@ -113,7 +112,7 @@ def _load_repoint_data_with_cache(csv_path: Path) -> pd.DataFrame:
113
112
 
114
113
 
115
114
  def interpolate_repoint_data(
116
- query_met_times: Union[float, npt.NDArray],
115
+ query_met_times: float | npt.NDArray,
117
116
  ) -> pd.DataFrame:
118
117
  """
119
118
  Interpolate repointing data to the queried MET times.
@@ -194,3 +193,31 @@ def interpolate_repoint_data(
194
193
  out_df["repoint_in_progress"] = query_met_times < out_df["repoint_end_met"].values
195
194
 
196
195
  return out_df
196
+
197
+
198
+ def get_pointing_times(met_time: float) -> tuple[float, float]:
199
+ """
200
+ Get the start and end MET times for the pointing that contains the query MET time.
201
+
202
+ Parameters
203
+ ----------
204
+ met_time : float
205
+ The MET time in a pointing.
206
+
207
+ Returns
208
+ -------
209
+ pointing_start_time : float
210
+ The MET time of the repoint maneuver that ends before the query MET time.
211
+ pointing_end_time : float
212
+ The MET time of the repoint maneuver that starts after the query MET time.
213
+ """
214
+ # Find the pointing start time by finding the repoint end time
215
+ repoint_df = interpolate_repoint_data(met_time)
216
+ pointing_start_met = repoint_df["repoint_end_met"].item()
217
+ # Find the pointing end time by finding the next repoint start time
218
+ repoint_df = get_repoint_data()
219
+ pointing_idx = repoint_df.index[
220
+ repoint_df["repoint_end_met"] == pointing_start_met
221
+ ][0]
222
+ pointing_end_met = repoint_df["repoint_start_met"].iloc[pointing_idx + 1].item()
223
+ return pointing_start_met, pointing_end_met
@@ -4,7 +4,6 @@ import functools
4
4
  import logging
5
5
  from functools import reduce
6
6
  from pathlib import Path
7
- from typing import Union
8
7
 
9
8
  import numpy as np
10
9
  import pandas as pd
@@ -129,7 +128,7 @@ def _load_spin_data_with_cache(csv_paths: tuple[Path]) -> pd.DataFrame:
129
128
  return combined_df
130
129
 
131
130
 
132
- def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.DataFrame:
131
+ def interpolate_spin_data(query_met_times: float | npt.NDArray) -> pd.DataFrame:
133
132
  """
134
133
  Interpolate spin table data to the queried MET times.
135
134
 
@@ -213,10 +212,31 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
213
212
  return out_df
214
213
 
215
214
 
215
+ def get_spin_number(met_time: float) -> int:
216
+ """
217
+ Get the spin number for the input query time.
218
+
219
+ The spin number is the index of the spin table row that contains the
220
+ spin data for the input query time.
221
+
222
+ Parameters
223
+ ----------
224
+ met_time : float
225
+ Query time in Mission Elapsed Time (MET).
226
+
227
+ Returns
228
+ -------
229
+ spin_number : int
230
+ Spin number for the input query time.
231
+ """
232
+ spin_df = interpolate_spin_data(met_time)
233
+ return spin_df["spin_number"].item()
234
+
235
+
216
236
  def get_spin_angle(
217
- spin_phases: Union[float, npt.NDArray],
237
+ spin_phases: float | npt.NDArray,
218
238
  degrees: bool = False,
219
- ) -> Union[float, npt.NDArray]:
239
+ ) -> float | npt.NDArray:
220
240
  """
221
241
  Convert spin_phases to radians or degrees.
222
242
 
@@ -249,8 +269,8 @@ def get_spin_angle(
249
269
 
250
270
 
251
271
  def get_spacecraft_spin_phase(
252
- query_met_times: Union[float, npt.NDArray],
253
- ) -> Union[float, npt.NDArray]:
272
+ query_met_times: float | npt.NDArray,
273
+ ) -> float | npt.NDArray:
254
274
  """
255
275
  Get the spacecraft spin phase for the input query times.
256
276
 
@@ -274,8 +294,8 @@ def get_spacecraft_spin_phase(
274
294
 
275
295
 
276
296
  def get_instrument_spin_phase(
277
- query_met_times: Union[float, npt.NDArray], instrument: SpiceFrame
278
- ) -> Union[float, npt.NDArray]:
297
+ query_met_times: float | npt.NDArray, instrument: SpiceFrame
298
+ ) -> float | npt.NDArray:
279
299
  """
280
300
  Get the instrument spin phase for the input query times.
281
301
 
@@ -3,14 +3,12 @@
3
3
  import typing
4
4
  from collections.abc import Collection, Iterable
5
5
  from datetime import datetime
6
- from typing import Union
7
6
 
8
7
  import numpy as np
9
8
  import numpy.typing as npt
10
9
  import spiceypy
11
10
 
12
11
  from imap_processing.spice import IMAP_SC_ID
13
- from imap_processing.spice.kernels import ensure_spice
14
12
 
15
13
  TICK_DURATION = 2e-5 # 20 microseconds as defined in imap_sclk_0000.tsc
16
14
 
@@ -103,7 +101,6 @@ def met_to_ttj2000ns(
103
101
 
104
102
 
105
103
  @typing.no_type_check
106
- @ensure_spice
107
104
  def ttj2000ns_to_et(tt_ns: npt.ArrayLike) -> npt.NDArray[float]:
108
105
  """
109
106
  Convert TT J2000 epoch nanoseconds to TDB J2000 epoch seconds.
@@ -131,7 +128,6 @@ def ttj2000ns_to_et(tt_ns: npt.ArrayLike) -> npt.NDArray[float]:
131
128
 
132
129
 
133
130
  @typing.no_type_check
134
- @ensure_spice
135
131
  def et_to_ttj2000ns(et: npt.ArrayLike) -> npt.NDArray[float]:
136
132
  """
137
133
  Convert TDB J2000 epoch seconds to TT J2000 epoch nanoseconds.
@@ -157,7 +153,6 @@ def et_to_ttj2000ns(et: npt.ArrayLike) -> npt.NDArray[float]:
157
153
 
158
154
 
159
155
  @typing.no_type_check
160
- @ensure_spice(time_kernels_only=True)
161
156
  def met_to_utc(met: npt.ArrayLike, precision: int = 9) -> npt.NDArray[str]:
162
157
  """
163
158
  Convert mission elapsed time (MET) to UTC.
@@ -184,7 +179,7 @@ def met_to_utc(met: npt.ArrayLike, precision: int = 9) -> npt.NDArray[str]:
184
179
 
185
180
  def met_to_datetime64(
186
181
  met: npt.ArrayLike,
187
- ) -> Union[np.datetime64, npt.NDArray[np.datetime64]]:
182
+ ) -> np.datetime64 | npt.NDArray[np.datetime64]:
188
183
  """
189
184
  Convert mission elapsed time (MET) to datetime.datetime.
190
185
 
@@ -203,7 +198,7 @@ def met_to_datetime64(
203
198
 
204
199
  def et_to_datetime64(
205
200
  et: npt.ArrayLike,
206
- ) -> Union[np.datetime64, npt.NDArray[np.datetime64]]:
201
+ ) -> np.datetime64 | npt.NDArray[np.datetime64]:
207
202
  """
208
203
  Convert ET to numpy datetime64.
209
204
 
@@ -221,10 +216,9 @@ def et_to_datetime64(
221
216
 
222
217
 
223
218
  @typing.no_type_check
224
- @ensure_spice
225
219
  def et_to_met(
226
- et: Union[float, Collection[float]],
227
- ) -> Union[float, np.ndarray]:
220
+ et: float | Collection[float],
221
+ ) -> float | np.ndarray:
228
222
  """
229
223
  Convert ephemeris time to mission elapsed time (MET).
230
224
 
@@ -272,10 +266,9 @@ def ttj2000ns_to_met(
272
266
 
273
267
 
274
268
  @typing.no_type_check
275
- @ensure_spice
276
269
  def sct_to_et(
277
- sclk_ticks: Union[float, Collection[float]],
278
- ) -> Union[float, np.ndarray]:
270
+ sclk_ticks: float | Collection[float],
271
+ ) -> float | np.ndarray:
279
272
  """
280
273
  Convert encoded spacecraft clock "ticks" to ephemeris time.
281
274
 
@@ -298,10 +291,9 @@ def sct_to_et(
298
291
 
299
292
 
300
293
  @typing.no_type_check
301
- @ensure_spice
302
294
  def sct_to_ttj2000s(
303
- sclk_ticks: Union[float, Iterable[float]],
304
- ) -> Union[float, np.ndarray]:
295
+ sclk_ticks: float | Iterable[float],
296
+ ) -> float | np.ndarray:
305
297
  """
306
298
  Convert encoded spacecraft clock "ticks" to terrestrial time (TT).
307
299
 
@@ -330,10 +322,9 @@ def sct_to_ttj2000s(
330
322
 
331
323
 
332
324
  @typing.no_type_check
333
- @ensure_spice
334
325
  def str_to_et(
335
- time_str: Union[str, Iterable[str]],
336
- ) -> Union[float, np.ndarray]:
326
+ time_str: str | Iterable[str],
327
+ ) -> float | np.ndarray:
337
328
  """
338
329
  Convert string to ephemeris time.
339
330
 
@@ -356,13 +347,12 @@ def str_to_et(
356
347
 
357
348
 
358
349
  @typing.no_type_check
359
- @ensure_spice
360
350
  def et_to_utc(
361
- et: Union[float, Iterable[float]],
351
+ et: float | Iterable[float],
362
352
  format_str: str = "ISOC",
363
353
  precision: int = 3,
364
354
  utclen: int = 24,
365
- ) -> Union[str, np.ndarray]:
355
+ ) -> str | np.ndarray:
366
356
  """
367
357
  Convert ephemeris time to UTC.
368
358
 
@@ -138,7 +138,7 @@ def decompress_count(
138
138
 
139
139
  # SWAPI suggested using big value to indicate overflow.
140
140
  new_count[compressed_indices & (count_data == 0xFFFF)] = np.iinfo(np.int32).max
141
- return new_count
141
+ return (new_count).astype(np.float32)
142
142
 
143
143
 
144
144
  def find_sweep_starts(packets: xr.Dataset) -> npt.NDArray:
@@ -474,6 +474,12 @@ def process_swapi_science(
474
474
  swp_scem_counts = decompress_count(raw_scem_count, scem_compression_flags)
475
475
  swp_coin_counts = decompress_count(raw_coin_count, coin_compression_flags)
476
476
 
477
+ # Fill first index of 72 steps with nan value per
478
+ # SWAPI team's instruction. nan helps with plotting.
479
+ swp_pcem_counts[:, 0] = np.nan
480
+ swp_scem_counts[:, 0] = np.nan
481
+ swp_coin_counts[:, 0] = np.nan
482
+
477
483
  # ====================================================
478
484
  # Load the CDF attributes
479
485
  # ====================================================
@@ -600,17 +606,17 @@ def process_swapi_science(
600
606
  )
601
607
 
602
608
  dataset["swp_pcem_counts"] = xr.DataArray(
603
- np.array(swp_pcem_counts, dtype=np.uint16),
609
+ swp_pcem_counts,
604
610
  dims=["epoch", "esa_step"],
605
611
  attrs=cdf_manager.get_variable_attributes("pcem_counts"),
606
612
  )
607
613
  dataset["swp_scem_counts"] = xr.DataArray(
608
- np.array(swp_scem_counts, dtype=np.uint16),
614
+ swp_scem_counts,
609
615
  dims=["epoch", "esa_step"],
610
616
  attrs=cdf_manager.get_variable_attributes("scem_counts"),
611
617
  )
612
618
  dataset["swp_coin_counts"] = xr.DataArray(
613
- np.array(swp_coin_counts, dtype=np.uint16),
619
+ swp_coin_counts,
614
620
  dims=["epoch", "esa_step"],
615
621
  attrs=cdf_manager.get_variable_attributes("coin_counts"),
616
622
  )
@@ -12,7 +12,7 @@ from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
12
12
  logger = logging.getLogger(__name__)
13
13
 
14
14
 
15
- TIME_PER_BIN = 0.167 # seconds
15
+ SWAPI_LIVETIME = 0.145 # seconds
16
16
 
17
17
 
18
18
  def solve_full_sweep_energy(
@@ -66,7 +66,7 @@ def solve_full_sweep_energy(
66
66
 
67
67
  first_63_energies = []
68
68
 
69
- for time, sweep_id in zip(data_time, sweep_table):
69
+ for time, sweep_id in zip(data_time, sweep_table, strict=False):
70
70
  # Find the sweep's ESA data for the given time and sweep_id
71
71
  subset = esa_table_df[
72
72
  (esa_table_df["timestamp"] <= time) & (esa_table_df["Sweep #"] == sweep_id)
@@ -159,14 +159,12 @@ def swapi_l2(
159
159
 
160
160
  To process science data to L2, we need to:
161
161
  - convert counts to rates. This is done by dividing the counts by the
162
- TIME_PER_BIN time. TIME_PER_BIN is the exposure time per energy bin which is
163
- obtained by dividing the time for one complete sweep
164
- (12 s, coarse + fine sweep) by the total energy steps (72),
165
- i.e., TIME_PER_BIN = 12/72 = 0.167 s. This will be constant.
162
+ SWAPI_LIVETIME time. LIVETIME is data acquisition time. It will
163
+ be constant, SWAPI_LIVETIME = 0.145 s.
166
164
 
167
165
  - update uncertainty. Calculate new uncertainty value using
168
- SWP_PCEM_ERR data from level one and divide by TIME_PER_BIN. Eg.
169
- SWP_PCEM_UNC = SWP_PCEM_ERR / TIME_PER_BIN
166
+ SWP_PCEM_ERR data from level one and divide by SWAPI_LIVETIME. Eg.
167
+ SWP_PCEM_UNC = SWP_PCEM_ERR / SWAPI_LIVETIME
170
168
  Do the same for SCEM and COIN data.
171
169
 
172
170
  Parameters
@@ -233,9 +231,9 @@ def swapi_l2(
233
231
  ]
234
232
 
235
233
  # convert counts to rate
236
- l2_dataset["swp_pcem_rate"] = l1_dataset["swp_pcem_counts"] / TIME_PER_BIN
237
- l2_dataset["swp_scem_rate"] = l1_dataset["swp_scem_counts"] / TIME_PER_BIN
238
- l2_dataset["swp_coin_rate"] = l1_dataset["swp_coin_counts"] / TIME_PER_BIN
234
+ l2_dataset["swp_pcem_rate"] = l1_dataset["swp_pcem_counts"] / SWAPI_LIVETIME
235
+ l2_dataset["swp_scem_rate"] = l1_dataset["swp_scem_counts"] / SWAPI_LIVETIME
236
+ l2_dataset["swp_coin_rate"] = l1_dataset["swp_coin_counts"] / SWAPI_LIVETIME
239
237
  # update attrs
240
238
  l2_dataset["swp_pcem_rate"].attrs = cdf_manager.get_variable_attributes("pcem_rate")
241
239
  l2_dataset["swp_scem_rate"].attrs = cdf_manager.get_variable_attributes("scem_rate")
@@ -243,22 +241,22 @@ def swapi_l2(
243
241
 
244
242
  # update uncertainty
245
243
  l2_dataset["swp_pcem_rate_stat_uncert_plus"] = (
246
- l1_dataset["swp_pcem_counts_stat_uncert_plus"] / TIME_PER_BIN
244
+ l1_dataset["swp_pcem_counts_stat_uncert_plus"] / SWAPI_LIVETIME
247
245
  )
248
246
  l2_dataset["swp_pcem_rate_stat_uncert_minus"] = (
249
- l1_dataset["swp_pcem_counts_stat_uncert_minus"] / TIME_PER_BIN
247
+ l1_dataset["swp_pcem_counts_stat_uncert_minus"] / SWAPI_LIVETIME
250
248
  )
251
249
  l2_dataset["swp_scem_rate_stat_uncert_plus"] = (
252
- l1_dataset["swp_scem_counts_stat_uncert_plus"] / TIME_PER_BIN
250
+ l1_dataset["swp_scem_counts_stat_uncert_plus"] / SWAPI_LIVETIME
253
251
  )
254
252
  l2_dataset["swp_scem_rate_stat_uncert_minus"] = (
255
- l1_dataset["swp_scem_counts_stat_uncert_minus"] / TIME_PER_BIN
253
+ l1_dataset["swp_scem_counts_stat_uncert_minus"] / SWAPI_LIVETIME
256
254
  )
257
255
  l2_dataset["swp_coin_rate_stat_uncert_plus"] = (
258
- l1_dataset["swp_coin_counts_stat_uncert_plus"] / TIME_PER_BIN
256
+ l1_dataset["swp_coin_counts_stat_uncert_plus"] / SWAPI_LIVETIME
259
257
  )
260
258
  l2_dataset["swp_coin_rate_stat_uncert_minus"] = (
261
- l1_dataset["swp_coin_counts_stat_uncert_minus"] / TIME_PER_BIN
259
+ l1_dataset["swp_coin_counts_stat_uncert_minus"] / SWAPI_LIVETIME
262
260
  )
263
261
  # update attrs
264
262
  l2_dataset[
@@ -2,7 +2,6 @@
2
2
 
3
3
  import logging
4
4
  from pathlib import Path
5
- from typing import Union
6
5
 
7
6
  import numpy as np
8
7
  import numpy.typing as npt
@@ -51,7 +50,7 @@ def get_esa_dataframe(esa_table_number: int) -> pd.DataFrame:
51
50
 
52
51
 
53
52
  def deadtime_correction(
54
- counts: np.ndarray, acq_duration: Union[int, npt.NDArray]
53
+ counts: np.ndarray, acq_duration: int | npt.NDArray
55
54
  ) -> npt.NDArray:
56
55
  """
57
56
  Calculate deadtime correction.
@@ -51,7 +51,6 @@ class UltraConstants:
51
51
  Z_DSTOP: float = 2.6 / 2 # Position of stop foil on Z axis [mm]
52
52
  Z_DS: float = 46.19 - (2.6 / 2) # Position of slit on Z axis [mm]
53
53
  DF: float = 3.39 # Distance from slit to foil [mm]
54
-
55
54
  # Derived constants
56
55
  DMIN_PH_CTOF: float = (
57
56
  Z_DS - (2**0.5) * DF
@@ -79,52 +78,30 @@ class UltraConstants:
79
78
  CULLING_RPM_MIN = 2.0
80
79
  CULLING_RPM_MAX = 6.0
81
80
 
82
- # Thresholds for culling based on counts.
81
+ # Thresholds for culling based on counts (keV).
83
82
  CULLING_ENERGY_BIN_EDGES: ClassVar[list] = [
84
83
  3.385,
85
84
  4.13722222222222,
86
- 4.13722222222222,
87
- 5.05660493827161,
88
85
  5.05660493827161,
89
86
  6.18029492455419,
90
- 6.18029492455419,
91
- 7.55369379667734,
92
87
  7.55369379667734,
93
88
  9.23229241816119,
94
- 9.23229241816119,
95
89
  11.2839129555303,
96
- 11.2839129555303,
97
- 13.7914491678704,
98
90
  13.7914491678704,
99
91
  16.8562156496194,
100
- 16.8562156496194,
101
92
  20.6020413495348,
102
- 20.6020413495348,
103
- 25.1802727605426,
104
93
  25.1802727605426,
105
94
  30.775888929552,
106
- 30.775888929552,
107
95
  37.6149753583414,
108
- 37.6149753583414,
109
- 45.9738587713061,
110
96
  45.9738587713061,
111
97
  56.1902718315964,
112
- 56.1902718315964,
113
98
  68.6769989052845,
114
- 68.6769989052845,
115
- 83.93855421757,
116
99
  83.93855421757,
117
100
  102.591566265919,
118
- 102.591566265919,
119
101
  125.38969210279,
120
- 125.38969210279,
121
- 153.254068125632,
122
102
  153.254068125632,
123
103
  187.310527709106,
124
- 187.310527709106,
125
104
  228.93508942224,
126
- 228.93508942224,
127
- 279.809553738294,
128
105
  279.809553738294,
129
106
  341.989454569026,
130
107
  1e5,