imap-processing 0.17.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 (141) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/ancillary/ancillary_dataset_combiner.py +161 -1
  3. imap_processing/ccsds/excel_to_xtce.py +12 -0
  4. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -6
  5. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +312 -274
  6. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +39 -28
  7. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1048 -183
  8. imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
  9. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +12 -0
  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_hit_l1a_variable_attrs.yaml +163 -100
  13. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +4 -4
  14. imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +97 -54
  15. imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
  16. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +44 -44
  17. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +77 -61
  18. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
  19. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
  20. imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
  21. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +99 -2
  22. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
  23. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +60 -0
  24. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +99 -11
  25. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
  26. imap_processing/cli.py +121 -44
  27. imap_processing/codice/codice_l1a.py +165 -77
  28. imap_processing/codice/codice_l1b.py +1 -1
  29. imap_processing/codice/codice_l2.py +118 -19
  30. imap_processing/codice/constants.py +1217 -1089
  31. imap_processing/decom.py +1 -4
  32. imap_processing/ena_maps/ena_maps.py +32 -25
  33. imap_processing/ena_maps/utils/naming.py +8 -2
  34. imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
  35. imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
  36. imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
  37. imap_processing/glows/ancillary/imap_glows_pipeline_settings_20250923_v002.json +54 -0
  38. imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
  39. imap_processing/glows/l1b/glows_l1b.py +99 -9
  40. imap_processing/glows/l1b/glows_l1b_data.py +350 -38
  41. imap_processing/glows/l2/glows_l2.py +11 -0
  42. imap_processing/hi/hi_l1a.py +124 -3
  43. imap_processing/hi/hi_l1b.py +154 -71
  44. imap_processing/hi/hi_l2.py +84 -51
  45. imap_processing/hi/utils.py +153 -8
  46. imap_processing/hit/l0/constants.py +3 -0
  47. imap_processing/hit/l0/decom_hit.py +5 -8
  48. imap_processing/hit/l1a/hit_l1a.py +375 -45
  49. imap_processing/hit/l1b/constants.py +5 -0
  50. imap_processing/hit/l1b/hit_l1b.py +61 -131
  51. imap_processing/hit/l2/constants.py +1 -1
  52. imap_processing/hit/l2/hit_l2.py +10 -11
  53. imap_processing/ialirt/calculate_ingest.py +219 -0
  54. imap_processing/ialirt/constants.py +32 -1
  55. imap_processing/ialirt/generate_coverage.py +201 -0
  56. imap_processing/ialirt/l0/ialirt_spice.py +5 -2
  57. imap_processing/ialirt/l0/parse_mag.py +337 -29
  58. imap_processing/ialirt/l0/process_hit.py +5 -3
  59. imap_processing/ialirt/l0/process_swapi.py +41 -25
  60. imap_processing/ialirt/l0/process_swe.py +23 -7
  61. imap_processing/ialirt/process_ephemeris.py +70 -14
  62. imap_processing/ialirt/utils/constants.py +22 -16
  63. imap_processing/ialirt/utils/create_xarray.py +42 -19
  64. imap_processing/idex/idex_constants.py +1 -5
  65. imap_processing/idex/idex_l0.py +2 -2
  66. imap_processing/idex/idex_l1a.py +2 -3
  67. imap_processing/idex/idex_l1b.py +2 -3
  68. imap_processing/idex/idex_l2a.py +130 -4
  69. imap_processing/idex/idex_l2b.py +313 -119
  70. imap_processing/idex/idex_utils.py +1 -3
  71. imap_processing/lo/l0/lo_apid.py +1 -0
  72. imap_processing/lo/l0/lo_science.py +25 -24
  73. imap_processing/lo/l1a/lo_l1a.py +44 -0
  74. imap_processing/lo/l1b/lo_l1b.py +3 -3
  75. imap_processing/lo/l1c/lo_l1c.py +116 -50
  76. imap_processing/lo/l2/lo_l2.py +29 -29
  77. imap_processing/lo/lo_ancillary.py +55 -0
  78. imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
  79. imap_processing/mag/constants.py +1 -0
  80. imap_processing/mag/l1a/mag_l1a.py +1 -0
  81. imap_processing/mag/l1a/mag_l1a_data.py +26 -0
  82. imap_processing/mag/l1b/mag_l1b.py +3 -2
  83. imap_processing/mag/l1c/interpolation_methods.py +14 -15
  84. imap_processing/mag/l1c/mag_l1c.py +23 -6
  85. imap_processing/mag/l1d/__init__.py +0 -0
  86. imap_processing/mag/l1d/mag_l1d.py +176 -0
  87. imap_processing/mag/l1d/mag_l1d_data.py +725 -0
  88. imap_processing/mag/l2/__init__.py +0 -0
  89. imap_processing/mag/l2/mag_l2.py +25 -20
  90. imap_processing/mag/l2/mag_l2_data.py +199 -130
  91. imap_processing/quality_flags.py +28 -2
  92. imap_processing/spice/geometry.py +101 -36
  93. imap_processing/spice/pointing_frame.py +1 -7
  94. imap_processing/spice/repoint.py +29 -2
  95. imap_processing/spice/spin.py +32 -8
  96. imap_processing/spice/time.py +60 -19
  97. imap_processing/swapi/l1/swapi_l1.py +10 -4
  98. imap_processing/swapi/l2/swapi_l2.py +66 -24
  99. imap_processing/swapi/swapi_utils.py +1 -1
  100. imap_processing/swe/l1b/swe_l1b.py +3 -6
  101. imap_processing/ultra/constants.py +28 -3
  102. imap_processing/ultra/l0/decom_tools.py +15 -8
  103. imap_processing/ultra/l0/decom_ultra.py +35 -11
  104. imap_processing/ultra/l0/ultra_utils.py +102 -12
  105. imap_processing/ultra/l1a/ultra_l1a.py +26 -6
  106. imap_processing/ultra/l1b/cullingmask.py +6 -3
  107. imap_processing/ultra/l1b/de.py +122 -26
  108. imap_processing/ultra/l1b/extendedspin.py +29 -2
  109. imap_processing/ultra/l1b/lookup_utils.py +424 -50
  110. imap_processing/ultra/l1b/quality_flag_filters.py +23 -0
  111. imap_processing/ultra/l1b/ultra_l1b_culling.py +356 -5
  112. imap_processing/ultra/l1b/ultra_l1b_extended.py +534 -90
  113. imap_processing/ultra/l1c/helio_pset.py +127 -7
  114. imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
  115. imap_processing/ultra/l1c/spacecraft_pset.py +90 -15
  116. imap_processing/ultra/l1c/ultra_l1c.py +6 -0
  117. imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
  118. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +446 -341
  119. imap_processing/ultra/l2/ultra_l2.py +0 -1
  120. imap_processing/ultra/utils/ultra_l1_utils.py +40 -3
  121. imap_processing/utils.py +3 -4
  122. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +3 -3
  123. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +126 -126
  124. imap_processing/idex/idex_l2c.py +0 -250
  125. imap_processing/spice/kernels.py +0 -187
  126. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +0 -526
  127. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +0 -526
  128. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +0 -526
  129. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +0 -524
  130. imap_processing/ultra/lookup_tables/EgyNorm.mem.csv +0 -32769
  131. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  132. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  133. imap_processing/ultra/lookup_tables/dps_grid45_compressed.cdf +0 -0
  134. imap_processing/ultra/lookup_tables/ultra45_back-pos-luts.csv +0 -4097
  135. imap_processing/ultra/lookup_tables/ultra45_tdc_norm.csv +0 -2050
  136. imap_processing/ultra/lookup_tables/ultra90_back-pos-luts.csv +0 -4097
  137. imap_processing/ultra/lookup_tables/ultra90_tdc_norm.csv +0 -2050
  138. imap_processing/ultra/lookup_tables/yadjust.csv +0 -257
  139. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/LICENSE +0 -0
  140. {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
  141. {imap_processing-0.17.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."""
@@ -27,7 +24,7 @@ class SpiceBody(IntEnum):
27
24
  # A subset of IMAP Specific bodies as defined in imap_wkcp.tf
28
25
  IMAP = -43
29
26
  IMAP_SPACECRAFT = -43000
30
- # IMAP Pointing Frame (Despun) as defined in imap_science_0001.tf
27
+ # IMAP Pointing Frame (Despun) as defined in imap_science_xxx.tf
31
28
  IMAP_DPS = -43901
32
29
  # Standard NAIF bodies
33
30
  SOLAR_SYSTEM_BARYCENTER = spiceypy.bodn2c("SOLAR_SYSTEM_BARYCENTER")
@@ -36,13 +33,13 @@ class SpiceBody(IntEnum):
36
33
 
37
34
 
38
35
  class SpiceFrame(IntEnum):
39
- """Enum containing SPICE IDs for reference frames, defined in imap_wkcp.tf."""
36
+ """SPICE IDs for reference frames in imap_wkcp.tf and imap_science_xxx.tf."""
40
37
 
41
38
  # Standard SPICE Frames
42
39
  J2000 = spiceypy.irfnum("J2000")
43
40
  ECLIPJ2000 = spiceypy.irfnum("ECLIPJ2000")
44
41
  ITRF93 = 13000
45
- # IMAP Pointing Frame (Despun) as defined in imap_science_0001.tf
42
+ # IMAP Pointing Frame (Despun) as defined in imap_science_xxx.tf
46
43
  IMAP_DPS = -43901
47
44
  # IMAP specific as defined in imap_wkcp.tf
48
45
  IMAP_SPACECRAFT = -43000
@@ -61,6 +58,28 @@ class SpiceFrame(IntEnum):
61
58
  IMAP_IDEX = -43700
62
59
  IMAP_GLOWS = -43750
63
60
 
61
+ # IMAP Science Frames (new additions from imap_science_xxx.tf)
62
+ IMAP_OMD = -43900
63
+ IMAP_EARTHFIXED = -43910
64
+ IMAP_ECLIPDATE = -43911
65
+ IMAP_MDI = -43912
66
+ IMAP_MDR = -43913
67
+ IMAP_GMC = -43914
68
+ IMAP_GEI = -43915
69
+ IMAP_GSE = -43916
70
+ IMAP_GSM = -43917
71
+ IMAP_SMD = -43918
72
+ IMAP_RTN = -43920
73
+ IMAP_HCI = -43921 # HGI_J2K
74
+ IMAP_HCD = -43922 # HGI_D
75
+ IMAP_HGC = -43923 # HGS_D
76
+ IMAP_HAE = -43924
77
+ IMAP_HAED = -43925
78
+ IMAP_HEE = -43926
79
+ IMAP_HRE = -43927
80
+ IMAP_HNU = -43928
81
+ IMAP_GCS = -43929
82
+
64
83
 
65
84
  BORESIGHT_LOOKUP = {
66
85
  SpiceFrame.IMAP_LO_BASE: np.array([0, -1, 0]),
@@ -78,10 +97,8 @@ BORESIGHT_LOOKUP = {
78
97
  }
79
98
 
80
99
 
81
- @typing.no_type_check
82
- @ensure_spice
83
100
  def imap_state(
84
- et: Union[np.ndarray, float],
101
+ et: np.ndarray | float,
85
102
  ref_frame: SpiceFrame = SpiceFrame.ECLIPJ2000,
86
103
  abcorr: str = "NONE",
87
104
  observer: SpiceBody = SpiceBody.SUN,
@@ -115,14 +132,65 @@ def imap_state(
115
132
  return np.asarray(state)
116
133
 
117
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
+
118
185
  def get_spacecraft_to_instrument_spin_phase_offset(instrument: SpiceFrame) -> float:
119
186
  """
120
187
  Get the spin phase offset from the spacecraft to the instrument.
121
188
 
122
189
  For now, the offset is a fixed lookup based on `Table 1: Nominal Instrument
123
- to S/C CS Transformations` in document `7516-0011_drw.pdf`. These fixed
124
- values will need to be updated based on calibration data or retrieved using
125
- 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.
126
194
 
127
195
  Parameters
128
196
  ----------
@@ -134,26 +202,25 @@ def get_spacecraft_to_instrument_spin_phase_offset(instrument: SpiceFrame) -> fl
134
202
  spacecraft_to_instrument_spin_phase_offset : float
135
203
  The spin phase offset from the spacecraft to the instrument.
136
204
  """
137
- # TODO: Implement retrieval from SPICE?
138
- offset_lookup = {
139
- SpiceFrame.IMAP_LO_BASE: 330 / 360,
140
- SpiceFrame.IMAP_HI_45: 255 / 360,
141
- SpiceFrame.IMAP_HI_90: 285 / 360,
142
- SpiceFrame.IMAP_ULTRA_45: 33 / 360,
143
- SpiceFrame.IMAP_ULTRA_90: 210 / 360,
144
- SpiceFrame.IMAP_SWAPI: 168 / 360,
145
- SpiceFrame.IMAP_IDEX: 90 / 360,
146
- SpiceFrame.IMAP_CODICE: 136 / 360,
147
- SpiceFrame.IMAP_HIT: 30 / 360,
148
- SpiceFrame.IMAP_SWE: 153 / 360,
149
- SpiceFrame.IMAP_GLOWS: 127 / 360,
150
- 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
151
218
  }
152
- return offset_lookup[instrument]
219
+ return phase_offset_lookup[instrument]
153
220
 
154
221
 
155
222
  def frame_transform(
156
- et: Union[float, npt.NDArray],
223
+ et: float | npt.NDArray,
157
224
  position: npt.NDArray,
158
225
  from_frame: SpiceFrame,
159
226
  to_frame: SpiceFrame,
@@ -227,7 +294,7 @@ def frame_transform(
227
294
 
228
295
 
229
296
  def frame_transform_az_el(
230
- et: Union[float, npt.NDArray],
297
+ et: float | npt.NDArray,
231
298
  az_el: npt.NDArray,
232
299
  from_frame: SpiceFrame,
233
300
  to_frame: SpiceFrame,
@@ -276,10 +343,8 @@ def frame_transform_az_el(
276
343
  return to_frame_az_el[..., 1:3]
277
344
 
278
345
 
279
- @typing.no_type_check
280
- @ensure_spice
281
346
  def get_rotation_matrix(
282
- et: Union[float, npt.NDArray],
347
+ et: float | npt.NDArray,
283
348
  from_frame: SpiceFrame,
284
349
  to_frame: SpiceFrame,
285
350
  ) -> npt.NDArray:
@@ -317,7 +382,7 @@ def get_rotation_matrix(
317
382
 
318
383
 
319
384
  def instrument_pointing(
320
- et: Union[float, npt.NDArray],
385
+ et: float | npt.NDArray,
321
386
  instrument: SpiceFrame,
322
387
  to_frame: SpiceFrame,
323
388
  cartesian: bool = False,
@@ -355,7 +420,7 @@ def instrument_pointing(
355
420
 
356
421
 
357
422
  def basis_vectors(
358
- et: Union[float, npt.NDArray],
423
+ et: float | npt.NDArray,
359
424
  from_frame: SpiceFrame,
360
425
  to_frame: SpiceFrame,
361
426
  ) -> npt.NDArray:
@@ -528,9 +593,9 @@ def cartesian_to_latitudinal(coords: NDArray, degrees: bool = True) -> NDArray:
528
593
 
529
594
 
530
595
  def solar_longitude(
531
- et: Union[np.ndarray, float],
596
+ et: np.ndarray | float,
532
597
  degrees: bool = True,
533
- ) -> Union[float, npt.NDArray]:
598
+ ) -> float | npt.NDArray:
534
599
  """
535
600
  Compute the solar longitude of the Imap Spacecraft.
536
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:
@@ -200,7 +196,7 @@ def calculate_pointing_attitude_segments(
200
196
  - Latest NAIF leapseconds kernel (naif0012.tls)
201
197
  - The latest IMAP sclk (imap_sclk_NNNN.tsc)
202
198
  - The latest IMAP frame kernel (imap_wkcp.tf)
203
- - IMAP DPS frame kernel (imap_science_0001.tf)
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.
206
202
  """
@@ -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
 
@@ -197,6 +196,10 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
197
196
  # spin_period_valid columns.
198
197
  invalid_spin_phase_range = (spin_phases < 0) | (spin_phases >= 1)
199
198
 
199
+ # TODO: add optional to filter this if this flag means
200
+ # that repointing is happening. otherwise, then keep it.
201
+ # This needs to be discussed and receive guidance at
202
+ # the project level.
200
203
  invalid_spins = (out_df["spin_phase_valid"].values == 0) | (
201
204
  out_df["spin_period_valid"].values == 0
202
205
  )
@@ -209,10 +212,31 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
209
212
  return out_df
210
213
 
211
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
+
212
236
  def get_spin_angle(
213
- spin_phases: Union[float, npt.NDArray],
237
+ spin_phases: float | npt.NDArray,
214
238
  degrees: bool = False,
215
- ) -> Union[float, npt.NDArray]:
239
+ ) -> float | npt.NDArray:
216
240
  """
217
241
  Convert spin_phases to radians or degrees.
218
242
 
@@ -245,8 +269,8 @@ def get_spin_angle(
245
269
 
246
270
 
247
271
  def get_spacecraft_spin_phase(
248
- query_met_times: Union[float, npt.NDArray],
249
- ) -> Union[float, npt.NDArray]:
272
+ query_met_times: float | npt.NDArray,
273
+ ) -> float | npt.NDArray:
250
274
  """
251
275
  Get the spacecraft spin phase for the input query times.
252
276
 
@@ -270,8 +294,8 @@ def get_spacecraft_spin_phase(
270
294
 
271
295
 
272
296
  def get_instrument_spin_phase(
273
- query_met_times: Union[float, npt.NDArray], instrument: SpiceFrame
274
- ) -> Union[float, npt.NDArray]:
297
+ query_met_times: float | npt.NDArray, instrument: SpiceFrame
298
+ ) -> float | npt.NDArray:
275
299
  """
276
300
  Get the instrument spin phase for the input query times.
277
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,59 @@ def et_to_datetime64(
221
216
 
222
217
 
223
218
  @typing.no_type_check
224
- @ensure_spice
219
+ def et_to_met(
220
+ et: float | Collection[float],
221
+ ) -> float | np.ndarray:
222
+ """
223
+ Convert ephemeris time to mission elapsed time (MET).
224
+
225
+ This function converts ET to spacecraft clock ticks and then to MET seconds.
226
+ This is the inverse of the MET to ET conversion process.
227
+
228
+ Parameters
229
+ ----------
230
+ et : Union[float, Collection[float]]
231
+ Input ephemeris time value(s) to be converted to MET.
232
+
233
+ Returns
234
+ -------
235
+ met: np.ndarray
236
+ Mission elapsed time in seconds.
237
+ """
238
+ vectorized_sce2c = _vectorize(spiceypy.sce2c, otypes=[float], excluded=[0])
239
+ sclk_ticks = vectorized_sce2c(IMAP_SC_ID, et)
240
+ met = np.asarray(sclk_ticks, dtype=float) * TICK_DURATION
241
+ return met
242
+
243
+
244
+ def ttj2000ns_to_met(
245
+ tt_ns: npt.ArrayLike,
246
+ ) -> npt.NDArray[float]:
247
+ """
248
+ Convert terrestrial time nanoseconds since J2000 to mission elapsed time (MET).
249
+
250
+ This is the inverse of met_to_ttj2000ns. The conversion process is:
251
+ TTJ2000ns -> ET -> MET
252
+
253
+ Parameters
254
+ ----------
255
+ tt_ns : float, numpy.ndarray
256
+ Number of nanoseconds since the J2000 epoch in the TT timescale.
257
+
258
+ Returns
259
+ -------
260
+ numpy.ndarray[float]
261
+ The mission elapsed time in seconds.
262
+ """
263
+ et = ttj2000ns_to_et(tt_ns)
264
+ met = et_to_met(et)
265
+ return met
266
+
267
+
268
+ @typing.no_type_check
225
269
  def sct_to_et(
226
- sclk_ticks: Union[float, Collection[float]],
227
- ) -> Union[float, np.ndarray]:
270
+ sclk_ticks: float | Collection[float],
271
+ ) -> float | np.ndarray:
228
272
  """
229
273
  Convert encoded spacecraft clock "ticks" to ephemeris time.
230
274
 
@@ -247,10 +291,9 @@ def sct_to_et(
247
291
 
248
292
 
249
293
  @typing.no_type_check
250
- @ensure_spice
251
294
  def sct_to_ttj2000s(
252
- sclk_ticks: Union[float, Iterable[float]],
253
- ) -> Union[float, np.ndarray]:
295
+ sclk_ticks: float | Iterable[float],
296
+ ) -> float | np.ndarray:
254
297
  """
255
298
  Convert encoded spacecraft clock "ticks" to terrestrial time (TT).
256
299
 
@@ -279,10 +322,9 @@ def sct_to_ttj2000s(
279
322
 
280
323
 
281
324
  @typing.no_type_check
282
- @ensure_spice
283
325
  def str_to_et(
284
- time_str: Union[str, Iterable[str]],
285
- ) -> Union[float, np.ndarray]:
326
+ time_str: str | Iterable[str],
327
+ ) -> float | np.ndarray:
286
328
  """
287
329
  Convert string to ephemeris time.
288
330
 
@@ -305,13 +347,12 @@ def str_to_et(
305
347
 
306
348
 
307
349
  @typing.no_type_check
308
- @ensure_spice
309
350
  def et_to_utc(
310
- et: Union[float, Iterable[float]],
351
+ et: float | Iterable[float],
311
352
  format_str: str = "ISOC",
312
353
  precision: int = 3,
313
354
  utclen: int = 24,
314
- ) -> Union[str, np.ndarray]:
355
+ ) -> str | np.ndarray:
315
356
  """
316
357
  Convert ephemeris time to UTC.
317
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
  )