imap-processing 0.18.0__py3-none-any.whl → 0.19.2__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 (122) 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_global_cdf_attrs.yaml +6 -0
  4. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +221 -1057
  5. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +307 -283
  6. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1044 -203
  7. imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
  8. imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +11 -0
  9. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +15 -1
  10. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
  11. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
  12. imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
  13. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +8 -91
  14. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +106 -16
  15. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +5 -4
  16. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
  17. imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
  18. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +85 -2
  19. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
  20. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +20 -8
  21. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +45 -35
  22. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +110 -7
  23. imap_processing/cli.py +138 -93
  24. imap_processing/codice/codice_l0.py +2 -1
  25. imap_processing/codice/codice_l1a.py +167 -69
  26. imap_processing/codice/codice_l1b.py +42 -32
  27. imap_processing/codice/codice_l2.py +215 -9
  28. imap_processing/codice/constants.py +790 -603
  29. imap_processing/codice/data/lo_stepping_values.csv +1 -1
  30. imap_processing/decom.py +1 -4
  31. imap_processing/ena_maps/ena_maps.py +71 -43
  32. imap_processing/ena_maps/utils/corrections.py +291 -0
  33. imap_processing/ena_maps/utils/map_utils.py +20 -4
  34. imap_processing/ena_maps/utils/naming.py +8 -2
  35. imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
  36. imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
  37. imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
  38. imap_processing/glows/ancillary/imap_glows_pipeline-settings_20250923_v002.json +54 -0
  39. imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
  40. imap_processing/glows/l1b/glows_l1b.py +123 -18
  41. imap_processing/glows/l1b/glows_l1b_data.py +358 -47
  42. imap_processing/glows/l2/glows_l2.py +11 -0
  43. imap_processing/hi/hi_l1a.py +124 -3
  44. imap_processing/hi/hi_l1b.py +154 -71
  45. imap_processing/hi/hi_l1c.py +4 -109
  46. imap_processing/hi/hi_l2.py +104 -60
  47. imap_processing/hi/utils.py +262 -8
  48. imap_processing/hit/l0/constants.py +3 -0
  49. imap_processing/hit/l0/decom_hit.py +3 -6
  50. imap_processing/hit/l1a/hit_l1a.py +311 -21
  51. imap_processing/hit/l1b/hit_l1b.py +54 -126
  52. imap_processing/hit/l2/hit_l2.py +6 -6
  53. imap_processing/ialirt/calculate_ingest.py +219 -0
  54. imap_processing/ialirt/constants.py +12 -2
  55. imap_processing/ialirt/generate_coverage.py +15 -2
  56. imap_processing/ialirt/l0/ialirt_spice.py +6 -2
  57. imap_processing/ialirt/l0/parse_mag.py +293 -42
  58. imap_processing/ialirt/l0/process_hit.py +5 -3
  59. imap_processing/ialirt/l0/process_swapi.py +41 -25
  60. imap_processing/ialirt/process_ephemeris.py +70 -14
  61. imap_processing/ialirt/utils/create_xarray.py +1 -1
  62. imap_processing/idex/idex_l0.py +2 -2
  63. imap_processing/idex/idex_l1a.py +2 -3
  64. imap_processing/idex/idex_l1b.py +2 -3
  65. imap_processing/idex/idex_l2a.py +130 -4
  66. imap_processing/idex/idex_l2b.py +158 -143
  67. imap_processing/idex/idex_utils.py +1 -3
  68. imap_processing/lo/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
  69. imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
  70. imap_processing/lo/l0/lo_science.py +25 -24
  71. imap_processing/lo/l1b/lo_l1b.py +93 -19
  72. imap_processing/lo/l1c/lo_l1c.py +273 -93
  73. imap_processing/lo/l2/lo_l2.py +949 -135
  74. imap_processing/lo/lo_ancillary.py +55 -0
  75. imap_processing/mag/l1a/mag_l1a.py +1 -0
  76. imap_processing/mag/l1a/mag_l1a_data.py +26 -0
  77. imap_processing/mag/l1b/mag_l1b.py +3 -2
  78. imap_processing/mag/l1c/interpolation_methods.py +14 -15
  79. imap_processing/mag/l1c/mag_l1c.py +23 -6
  80. imap_processing/mag/l1d/mag_l1d.py +57 -14
  81. imap_processing/mag/l1d/mag_l1d_data.py +202 -32
  82. imap_processing/mag/l2/mag_l2.py +2 -0
  83. imap_processing/mag/l2/mag_l2_data.py +14 -5
  84. imap_processing/quality_flags.py +23 -1
  85. imap_processing/spice/geometry.py +89 -39
  86. imap_processing/spice/pointing_frame.py +4 -8
  87. imap_processing/spice/repoint.py +78 -2
  88. imap_processing/spice/spin.py +28 -8
  89. imap_processing/spice/time.py +12 -22
  90. imap_processing/swapi/l1/swapi_l1.py +10 -4
  91. imap_processing/swapi/l2/swapi_l2.py +15 -17
  92. imap_processing/swe/l1b/swe_l1b.py +1 -2
  93. imap_processing/ultra/constants.py +30 -24
  94. imap_processing/ultra/l0/ultra_utils.py +9 -11
  95. imap_processing/ultra/l1a/ultra_l1a.py +1 -2
  96. imap_processing/ultra/l1b/badtimes.py +35 -11
  97. imap_processing/ultra/l1b/de.py +95 -31
  98. imap_processing/ultra/l1b/extendedspin.py +31 -16
  99. imap_processing/ultra/l1b/goodtimes.py +112 -0
  100. imap_processing/ultra/l1b/lookup_utils.py +281 -28
  101. imap_processing/ultra/l1b/quality_flag_filters.py +10 -1
  102. imap_processing/ultra/l1b/ultra_l1b.py +7 -7
  103. imap_processing/ultra/l1b/ultra_l1b_culling.py +169 -7
  104. imap_processing/ultra/l1b/ultra_l1b_extended.py +311 -69
  105. imap_processing/ultra/l1c/helio_pset.py +139 -37
  106. imap_processing/ultra/l1c/l1c_lookup_utils.py +289 -0
  107. imap_processing/ultra/l1c/spacecraft_pset.py +140 -29
  108. imap_processing/ultra/l1c/ultra_l1c.py +33 -24
  109. imap_processing/ultra/l1c/ultra_l1c_culling.py +92 -0
  110. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +400 -292
  111. imap_processing/ultra/l2/ultra_l2.py +54 -11
  112. imap_processing/ultra/utils/ultra_l1_utils.py +37 -7
  113. imap_processing/utils.py +3 -4
  114. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/METADATA +2 -2
  115. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/RECORD +118 -109
  116. imap_processing/idex/idex_l2c.py +0 -84
  117. imap_processing/spice/kernels.py +0 -187
  118. imap_processing/ultra/l1b/cullingmask.py +0 -87
  119. imap_processing/ultra/l1c/histogram.py +0 -36
  120. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/LICENSE +0 -0
  121. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/WHEEL +0 -0
  122. {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/entry_points.txt +0 -0
@@ -11,20 +11,17 @@ 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."""
26
23
 
27
- # A subset of IMAP Specific bodies as defined in imap_wkcp.tf
24
+ # A subset of IMAP Specific bodies as defined in imap_001.tf
28
25
  IMAP = -43
29
26
  IMAP_SPACECRAFT = -43000
30
27
  # IMAP Pointing Frame (Despun) as defined in imap_science_xxx.tf
@@ -36,7 +33,7 @@ class SpiceBody(IntEnum):
36
33
 
37
34
 
38
35
  class SpiceFrame(IntEnum):
39
- """SPICE IDs for reference frames in imap_wkcp.tf and imap_science_xxx.tf."""
36
+ """SPICE IDs for reference frames in imap_###.tf and imap_science_xxx.tf."""
40
37
 
41
38
  # Standard SPICE Frames
42
39
  J2000 = spiceypy.irfnum("J2000")
@@ -44,7 +41,7 @@ class SpiceFrame(IntEnum):
44
41
  ITRF93 = 13000
45
42
  # IMAP Pointing Frame (Despun) as defined in imap_science_xxx.tf
46
43
  IMAP_DPS = -43901
47
- # IMAP specific as defined in imap_wkcp.tf
44
+ # IMAP specific as defined in imap_###.tf
48
45
  IMAP_SPACECRAFT = -43000
49
46
  IMAP_LO_BASE = -43100
50
47
  IMAP_LO_STAR_SENSOR = -43103
@@ -53,13 +50,17 @@ class SpiceFrame(IntEnum):
53
50
  IMAP_HI_90 = -43160
54
51
  IMAP_ULTRA_45 = -43200
55
52
  IMAP_ULTRA_90 = -43210
56
- IMAP_MAG = -43250
53
+ # TODO: remove IMAP_MAG frame once all usages have been removed
54
+ IMAP_MAG = -43999
55
+ IMAP_MAG_BOOM = -43250
56
+ IMAP_MAG_I = -43251
57
+ IMAP_MAG_O = -43252
57
58
  IMAP_SWE = -43300
58
59
  IMAP_SWAPI = -43350
59
60
  IMAP_CODICE = -43400
60
61
  IMAP_HIT = -43500
61
62
  IMAP_IDEX = -43700
62
- IMAP_GLOWS = -43750
63
+ IMAP_GLOWS = -43751
63
64
 
64
65
  # IMAP Science Frames (new additions from imap_science_xxx.tf)
65
66
  IMAP_OMD = -43900
@@ -90,7 +91,8 @@ BORESIGHT_LOOKUP = {
90
91
  SpiceFrame.IMAP_HI_90: np.array([0, 1, 0]),
91
92
  SpiceFrame.IMAP_ULTRA_45: np.array([0, 0, 1]),
92
93
  SpiceFrame.IMAP_ULTRA_90: np.array([0, 0, 1]),
93
- SpiceFrame.IMAP_MAG: np.array([0, 0, 1]),
94
+ SpiceFrame.IMAP_MAG_I: np.array([0, 0, 1]),
95
+ SpiceFrame.IMAP_MAG_O: np.array([0, 0, 1]),
94
96
  SpiceFrame.IMAP_SWE: np.array([-1, 0, 0]),
95
97
  SpiceFrame.IMAP_SWAPI: np.array([0, 1, 0]),
96
98
  SpiceFrame.IMAP_CODICE: np.array([0, 0, 1]),
@@ -100,10 +102,8 @@ BORESIGHT_LOOKUP = {
100
102
  }
101
103
 
102
104
 
103
- @typing.no_type_check
104
- @ensure_spice
105
105
  def imap_state(
106
- et: Union[np.ndarray, float],
106
+ et: np.ndarray | float,
107
107
  ref_frame: SpiceFrame = SpiceFrame.ECLIPJ2000,
108
108
  abcorr: str = "NONE",
109
109
  observer: SpiceBody = SpiceBody.SUN,
@@ -137,14 +137,66 @@ def imap_state(
137
137
  return np.asarray(state)
138
138
 
139
139
 
140
+ def get_instrument_mounting_az_el(instrument: SpiceFrame) -> np.ndarray:
141
+ """
142
+ Calculate the azimuth and elevation angle of instrument mounting.
143
+
144
+ Azimuth and elevation to instrument mounting in the spacecraft frame.
145
+ Azimuth is measured in degrees from the spacecraft x-axis. Elevation is measured
146
+ in degrees from the spacecraft x-y plane.
147
+
148
+ Parameters
149
+ ----------
150
+ instrument : SpiceFrame
151
+ Instrument to get the azimuth and elevation angles for.
152
+
153
+ Returns
154
+ -------
155
+ instrument_mounting_az_el : np.ndarray
156
+ 2-element array containing azimuth and elevation of the instrument
157
+ mounting in the spacecraft frame. Azimuth is measured in degrees from
158
+ the spacecraft x-axis. Elevation is measured in degrees from the
159
+ spacecraft x-y plane.
160
+ """
161
+ # Each instrument can have a unique basis vector in the instrument
162
+ # frame that is used to compute the s/c to instrument mounting.
163
+ # Most of these vectors are the same as the instrument boresight vector.
164
+ mounting_normal_vector = {
165
+ SpiceFrame.IMAP_LO_BASE: np.array([0, -1, 0]),
166
+ SpiceFrame.IMAP_HI_45: np.array([0, 1, 0]),
167
+ SpiceFrame.IMAP_HI_90: np.array([0, 1, 0]),
168
+ SpiceFrame.IMAP_ULTRA_45: np.array([0, 0, 1]),
169
+ SpiceFrame.IMAP_ULTRA_90: np.array([0, 0, 1]),
170
+ SpiceFrame.IMAP_MAG_I: np.array([-1, 0, 0]),
171
+ SpiceFrame.IMAP_MAG_O: np.array([-1, 0, 0]),
172
+ SpiceFrame.IMAP_SWE: np.array([-1, 0, 0]),
173
+ SpiceFrame.IMAP_SWAPI: np.array([0, 0, -1]),
174
+ SpiceFrame.IMAP_CODICE: np.array([-1, 0, 0]),
175
+ SpiceFrame.IMAP_HIT: np.array([0, 1, 0]),
176
+ SpiceFrame.IMAP_IDEX: np.array([0, 1, 0]),
177
+ SpiceFrame.IMAP_GLOWS: np.array([0, 0, -1]),
178
+ }
179
+
180
+ # Get the instrument mounting normal vector expressed in the spacecraft frame
181
+ # The reference frames are fixed, so the et argument can be fixed at 0
182
+ instrument_normal_sc = frame_transform(
183
+ 0, mounting_normal_vector[instrument], instrument, SpiceFrame.IMAP_SPACECRAFT
184
+ )
185
+ # Convert the cartesian coordinate to azimuth/elevation angles in degrees
186
+ return np.rad2deg(
187
+ spiceypy.recazl(instrument_normal_sc, azccw=True, elplsz=True)[1:]
188
+ )
189
+
190
+
140
191
  def get_spacecraft_to_instrument_spin_phase_offset(instrument: SpiceFrame) -> float:
141
192
  """
142
193
  Get the spin phase offset from the spacecraft to the instrument.
143
194
 
144
195
  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.
196
+ to S/C CS Transformations` in document `7516-0011_drw.pdf`. That Table
197
+ defines the angle from the spacecraft y-axis. We add 90 and take the modulous
198
+ with 360 in order to get the angle from the spacecraft x-axis. These fixed
199
+ values will need to be updated based on calibration data.
148
200
 
149
201
  Parameters
150
202
  ----------
@@ -156,26 +208,26 @@ def get_spacecraft_to_instrument_spin_phase_offset(instrument: SpiceFrame) -> fl
156
208
  spacecraft_to_instrument_spin_phase_offset : float
157
209
  The spin phase offset from the spacecraft to the instrument.
158
210
  """
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,
211
+ phase_offset_lookup = {
212
+ SpiceFrame.IMAP_LO_BASE: 60 / 360, # (330 + 90) % 360 = 60
213
+ SpiceFrame.IMAP_HI_45: 345 / 360, # 255 + 90 = 345
214
+ SpiceFrame.IMAP_HI_90: 15 / 360, # (285 + 90) % 360 = 15
215
+ SpiceFrame.IMAP_ULTRA_45: 123 / 360, # 33 + 90 = 123
216
+ SpiceFrame.IMAP_ULTRA_90: 300 / 360, # 210 + 90 = 300
217
+ SpiceFrame.IMAP_SWAPI: 258 / 360, # 168 + 90 = 258
218
+ SpiceFrame.IMAP_IDEX: 180 / 360, # 90 + 90 = 180
219
+ SpiceFrame.IMAP_CODICE: 226 / 360, # 136 + 90 = 226
220
+ SpiceFrame.IMAP_HIT: 120 / 360, # 30 + 90 = 120
221
+ SpiceFrame.IMAP_SWE: 243 / 360, # 153 + 90 = 243
222
+ SpiceFrame.IMAP_GLOWS: 217 / 360, # 127 + 90 = 217
223
+ SpiceFrame.IMAP_MAG_I: 90 / 360, # 0 + 90 = 90
224
+ SpiceFrame.IMAP_MAG_O: 90 / 360, # 0 + 90 = 90
173
225
  }
174
- return offset_lookup[instrument]
226
+ return phase_offset_lookup[instrument]
175
227
 
176
228
 
177
229
  def frame_transform(
178
- et: Union[float, npt.NDArray],
230
+ et: float | npt.NDArray,
179
231
  position: npt.NDArray,
180
232
  from_frame: SpiceFrame,
181
233
  to_frame: SpiceFrame,
@@ -249,7 +301,7 @@ def frame_transform(
249
301
 
250
302
 
251
303
  def frame_transform_az_el(
252
- et: Union[float, npt.NDArray],
304
+ et: float | npt.NDArray,
253
305
  az_el: npt.NDArray,
254
306
  from_frame: SpiceFrame,
255
307
  to_frame: SpiceFrame,
@@ -298,10 +350,8 @@ def frame_transform_az_el(
298
350
  return to_frame_az_el[..., 1:3]
299
351
 
300
352
 
301
- @typing.no_type_check
302
- @ensure_spice
303
353
  def get_rotation_matrix(
304
- et: Union[float, npt.NDArray],
354
+ et: float | npt.NDArray,
305
355
  from_frame: SpiceFrame,
306
356
  to_frame: SpiceFrame,
307
357
  ) -> npt.NDArray:
@@ -339,7 +389,7 @@ def get_rotation_matrix(
339
389
 
340
390
 
341
391
  def instrument_pointing(
342
- et: Union[float, npt.NDArray],
392
+ et: float | npt.NDArray,
343
393
  instrument: SpiceFrame,
344
394
  to_frame: SpiceFrame,
345
395
  cartesian: bool = False,
@@ -377,7 +427,7 @@ def instrument_pointing(
377
427
 
378
428
 
379
429
  def basis_vectors(
380
- et: Union[float, npt.NDArray],
430
+ et: float | npt.NDArray,
381
431
  from_frame: SpiceFrame,
382
432
  to_frame: SpiceFrame,
383
433
  ) -> npt.NDArray:
@@ -550,9 +600,9 @@ def cartesian_to_latitudinal(coords: NDArray, degrees: bool = True) -> NDArray:
550
600
 
551
601
 
552
602
  def solar_longitude(
553
- et: Union[np.ndarray, float],
603
+ et: np.ndarray | float,
554
604
  degrees: bool = True,
555
- ) -> Union[float, npt.NDArray]:
605
+ ) -> float | npt.NDArray:
556
606
  """
557
607
  Compute the solar longitude of the Imap Spacecraft.
558
608
 
@@ -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:
@@ -199,7 +195,7 @@ def calculate_pointing_attitude_segments(
199
195
 
200
196
  - Latest NAIF leapseconds kernel (naif0012.tls)
201
197
  - The latest IMAP sclk (imap_sclk_NNNN.tsc)
202
- - The latest IMAP frame kernel (imap_wkcp.tf)
198
+ - The latest IMAP frame kernel (imap_###.tf)
203
199
  - IMAP DPS frame kernel (imap_science_100.tf)
204
200
  - IMAP historical attitude kernel from which the pointing frame kernel will
205
201
  be generated.
@@ -214,7 +210,9 @@ def calculate_pointing_attitude_segments(
214
210
  count = spiceypy.ktotal("ck")
215
211
  loaded_ck_kernel, _, _, _ = spiceypy.kdata(count - 1, "ck")
216
212
  if str(ck_path) != loaded_ck_kernel:
217
- raise ValueError(f"Error: Expected CK kernel {ck_path}")
213
+ raise ValueError(
214
+ f"Error: Expected CK kernel {ck_path} but loaded {loaded_ck_kernel}"
215
+ )
218
216
 
219
217
  id_imap_spacecraft = spiceypy.gipool("FRAME_IMAP_SPACECRAFT", 0, 1)
220
218
 
@@ -292,8 +290,6 @@ def calculate_pointing_attitude_segments(
292
290
  return pointing_segments
293
291
 
294
292
 
295
- @typing.no_type_check
296
- @ensure_spice
297
293
  def _average_quaternions(et_times: np.ndarray) -> NDArray:
298
294
  """
299
295
  Average the quaternions.
@@ -3,13 +3,14 @@
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
10
9
  from numpy import typing as npt
11
10
 
12
11
  from imap_processing.spice import config
12
+ from imap_processing.spice.geometry import imap_state
13
+ from imap_processing.spice.time import met_to_sclkticks, sct_to_et
13
14
 
14
15
  logger = logging.getLogger(__name__)
15
16
 
@@ -113,7 +114,7 @@ def _load_repoint_data_with_cache(csv_path: Path) -> pd.DataFrame:
113
114
 
114
115
 
115
116
  def interpolate_repoint_data(
116
- query_met_times: Union[float, npt.NDArray],
117
+ query_met_times: float | npt.NDArray,
117
118
  ) -> pd.DataFrame:
118
119
  """
119
120
  Interpolate repointing data to the queried MET times.
@@ -194,3 +195,78 @@ def interpolate_repoint_data(
194
195
  out_df["repoint_in_progress"] = query_met_times < out_df["repoint_end_met"].values
195
196
 
196
197
  return out_df
198
+
199
+
200
+ def get_pointing_times(met_time: float) -> tuple[float, float]:
201
+ """
202
+ Get the start and end MET times for the pointing that contains the query MET time.
203
+
204
+ Parameters
205
+ ----------
206
+ met_time : float
207
+ The MET time in a pointing.
208
+
209
+ Returns
210
+ -------
211
+ pointing_start_time : float
212
+ The MET time of the repoint maneuver that ends before the query MET time.
213
+ pointing_end_time : float
214
+ The MET time of the repoint maneuver that starts after the query MET time.
215
+ """
216
+ # Find the pointing start time by finding the repoint end time
217
+ repoint_df = interpolate_repoint_data(met_time)
218
+ pointing_start_met = repoint_df["repoint_end_met"].item()
219
+ # Find the pointing end time by finding the next repoint start time
220
+ repoint_df = get_repoint_data()
221
+ pointing_idx = repoint_df.index[
222
+ repoint_df["repoint_end_met"] == pointing_start_met
223
+ ][0]
224
+ pointing_end_met = repoint_df["repoint_start_met"].iloc[pointing_idx + 1].item()
225
+ return pointing_start_met, pointing_end_met
226
+
227
+
228
+ def get_pointing_mid_time(met_time: float) -> float:
229
+ """
230
+ Get mid-point of the pointing for the given MET time.
231
+
232
+ Get the mid-point time between the end of one repoint and
233
+ start of the next. Input could be a MET time.
234
+
235
+ Parameters
236
+ ----------
237
+ met_time : float
238
+ The MET time in a repoint.
239
+
240
+ Returns
241
+ -------
242
+ repoint_mid_time : float
243
+ The mid MET time of the repoint maneuver.
244
+ """
245
+ pointing_start_met, pointing_end_met = get_pointing_times(met_time)
246
+ return (pointing_start_met + pointing_end_met) / 2
247
+
248
+
249
+ def get_mid_point_state(met_time: float) -> npt.NDArray:
250
+ """
251
+ Get IMAP state for the mid-point.
252
+
253
+ Get IMAP state for the mid-point of the pointing in
254
+ reference frame, ECLIPJ2000 and observer, SUN.
255
+
256
+ Parameters
257
+ ----------
258
+ met_time : float
259
+ The MET time in a pointing.
260
+
261
+ Returns
262
+ -------
263
+ mid_point_state : numpy.ndarray
264
+ The mid state of the pointing maneuver.
265
+ """
266
+ # Get mid point time in ET
267
+ mid_point_time = get_pointing_mid_time(met_time)
268
+ mid_point_time_et = sct_to_et(met_to_sclkticks(mid_point_time))
269
+
270
+ # Convert mid point time to state
271
+ pointing_state = imap_state(mid_point_time_et)
272
+ return pointing_state
@@ -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
  )