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
@@ -2,7 +2,6 @@
2
2
 
3
3
  import logging
4
4
  from decimal import Decimal
5
- from typing import Optional
6
5
 
7
6
  import numpy as np
8
7
  import pandas as pd
@@ -10,13 +9,12 @@ import xarray as xr
10
9
  from scipy.optimize import curve_fit
11
10
  from scipy.special import erf
12
11
 
13
- from imap_processing import imap_module_directory
14
12
  from imap_processing.ialirt.constants import IalirtSwapiConstants as Consts
15
13
  from imap_processing.ialirt.utils.grouping import find_groups
16
14
  from imap_processing.ialirt.utils.time import calculate_time
17
15
  from imap_processing.spice.time import met_to_ttj2000ns, met_to_utc
18
16
  from imap_processing.swapi.l1.swapi_l1 import process_sweep_data
19
- from imap_processing.swapi.l2.swapi_l2 import TIME_PER_BIN
17
+ from imap_processing.swapi.l2.swapi_l2 import SWAPI_LIVETIME
20
18
 
21
19
  logger = logging.getLogger(__name__)
22
20
 
@@ -70,7 +68,7 @@ def count_rate(
70
68
  def optimize_pseudo_parameters(
71
69
  count_rates: np.ndarray,
72
70
  count_rate_error: np.ndarray,
73
- energy_passbands: Optional[np.ndarray] = None,
71
+ energy_passbands: np.ndarray,
74
72
  ) -> (dict)[str, list[float]]:
75
73
  """
76
74
  Find the pseudo speed (u), density (n) and temperature (T) of solar wind particles.
@@ -84,7 +82,7 @@ def optimize_pseudo_parameters(
84
82
  count_rate_error : np.ndarray
85
83
  Standard deviation of the coincidence count rates parameter.
86
84
  energy_passbands : np.ndarray, default None
87
- Energy passbands, passed in only for testing purposes.
85
+ Energy values, taken from the SWAPI lookup table.
88
86
 
89
87
  Returns
90
88
  -------
@@ -92,21 +90,6 @@ def optimize_pseudo_parameters(
92
90
  Dictionary containing the optimized speed, density, and temperature values for
93
91
  each sweep included in the input count_rates array.
94
92
  """
95
- if not energy_passbands:
96
- # Read in energy passbands
97
- energy_data = pd.read_csv(
98
- f"{imap_module_directory}/tests/swapi/lut/imap_swapi_esa-unit"
99
- f"-conversion_20250211_v000.csv"
100
- )
101
- energy_passbands = (
102
- energy_data["Energy"][0:63]
103
- .replace(",", "", regex=True)
104
- .to_numpy()
105
- .astype(float)
106
- )
107
-
108
- # Initial guess pulled from page 52 of the IMAP SWAPI Instrument Algorithms Document
109
- initial_param_guess = np.array([550, 5.27, 1e5])
110
93
  solution_dict = { # type: ignore
111
94
  "pseudo_speed": [],
112
95
  "pseudo_density": [],
@@ -116,8 +99,16 @@ def optimize_pseudo_parameters(
116
99
  for sweep in np.arange(count_rates.shape[0]):
117
100
  current_sweep_count_rates = count_rates[sweep, :]
118
101
  current_sweep_count_rate_errors = count_rate_error[sweep, :]
119
- # Find the max count rate, and use the 6 points surrounding it (inclusive)
102
+ # Find the max count rate, and use the 5 points surrounding it
120
103
  max_index = np.argmax(current_sweep_count_rates)
104
+ initial_speed_guess = np.sqrt(energy_passbands[max_index]) * Consts.speed_coeff
105
+ initial_param_guess = np.array(
106
+ [
107
+ initial_speed_guess,
108
+ 5 * (400 / initial_speed_guess) ** 2,
109
+ 60000 * (initial_speed_guess / 400) ** 2,
110
+ ]
111
+ )
121
112
  sol = curve_fit(
122
113
  f=count_rate,
123
114
  xdata=energy_passbands.take(
@@ -138,7 +129,9 @@ def optimize_pseudo_parameters(
138
129
  return solution_dict
139
130
 
140
131
 
141
- def process_swapi_ialirt(unpacked_data: xr.Dataset) -> list[dict]:
132
+ def process_swapi_ialirt(
133
+ unpacked_data: xr.Dataset, calibration_lut_table: pd.DataFrame
134
+ ) -> list[dict]:
142
135
  """
143
136
  Extract I-ALiRT variables and calculate coincidence count rate.
144
137
 
@@ -146,6 +139,8 @@ def process_swapi_ialirt(unpacked_data: xr.Dataset) -> list[dict]:
146
139
  ----------
147
140
  unpacked_data : xr.Dataset
148
141
  SWAPI I-ALiRT data that has been parsed from the spacecraft packet.
142
+ calibration_lut_table : pd.DataFrame
143
+ DataFrame containing the contents of the SWAPI esa-unit-conversion lookup table.
149
144
 
150
145
  Returns
151
146
  -------
@@ -191,10 +186,31 @@ def process_swapi_ialirt(unpacked_data: xr.Dataset) -> list[dict]:
191
186
  continue
192
187
 
193
188
  raw_coin_count = process_sweep_data(grouped_dataset, "swapi_coin_cnt")
194
- raw_coin_rate = raw_coin_count / TIME_PER_BIN
195
- count_rate_error = np.sqrt(raw_coin_count) / TIME_PER_BIN
189
+ raw_coin_rate = raw_coin_count / SWAPI_LIVETIME
190
+ count_rate_error = np.sqrt(raw_coin_count) / SWAPI_LIVETIME
196
191
 
197
- solution = optimize_pseudo_parameters(raw_coin_rate, count_rate_error)
192
+ # Extract energy values from the calibration lookup table file
193
+ calibration_lut_table["timestamp"] = pd.to_datetime(
194
+ calibration_lut_table["timestamp"], format="%m/%d/%Y %H:%M"
195
+ )
196
+ calibration_lut_table["timestamp"] = calibration_lut_table["timestamp"].to_numpy(
197
+ dtype="datetime64[ns]"
198
+ )
199
+
200
+ # Find the sweep's energy data for the latest time, where sweep_id == 2
201
+ subset = calibration_lut_table[
202
+ (calibration_lut_table["timestamp"] == calibration_lut_table["timestamp"].max())
203
+ & (calibration_lut_table["Sweep #"] == 2)
204
+ ]
205
+ if subset.empty:
206
+ energy_passbands = np.full(63, np.nan, dtype=np.float64)
207
+ else:
208
+ subset = subset.sort_values(["timestamp", "ESA Step #"])
209
+ energy_passbands = subset["Energy"][0:63].to_numpy().astype(float)
210
+
211
+ solution = optimize_pseudo_parameters(
212
+ raw_coin_rate, count_rate_error, energy_passbands
213
+ )
198
214
 
199
215
  swapi_data = []
200
216
 
@@ -542,16 +542,32 @@ def process_swe(accumulated_data: xr.Dataset, in_flight_cal_files: list) -> list
542
542
  summed_first = normalized_first_half.sum(axis=(1, 2))
543
543
  summed_second = normalized_second_half.sum(axis=(1, 2))
544
544
 
545
+ met_first_half = int(
546
+ grouped["met"].where(grouped["swe_seq"] == 0, drop=True).values[0]
547
+ )
548
+ met_second_half = int(
549
+ grouped["met"].where(grouped["swe_seq"] == 30, drop=True).values[0]
550
+ )
551
+
552
+ swe_data.append(
553
+ {
554
+ "apid": 478,
555
+ "met": met_first_half,
556
+ "met_in_utc": met_to_utc(met_first_half).split(".")[0],
557
+ "ttj2000ns": int(met_to_ttj2000ns(met_first_half)),
558
+ "swe_normalized_counts": [int(val) for val in summed_first],
559
+ "swe_counterstreaming_electrons": bde_first_half,
560
+ },
561
+ )
545
562
  swe_data.append(
546
563
  {
547
564
  "apid": 478,
548
- "met": int(grouped["met"].min()),
549
- "met_in_utc": met_to_utc(grouped["met"].min()).split(".")[0],
550
- "ttj2000ns": int(met_to_ttj2000ns(grouped["met"].min())),
551
- "swe_normalized_counts_half_1_esa": [int(val) for val in summed_first],
552
- "swe_normalized_counts_half_2_esa": [int(val) for val in summed_second],
553
- "swe_counterstreaming_electrons": max(bde_first_half, bde_second_half),
554
- }
565
+ "met": met_second_half,
566
+ "met_in_utc": met_to_utc(met_second_half).split(".")[0],
567
+ "ttj2000ns": int(met_to_ttj2000ns(met_second_half)),
568
+ "swe_normalized_counts": [int(val) for val in summed_second],
569
+ "swe_counterstreaming_electrons": bde_second_half,
570
+ },
555
571
  )
556
572
 
557
573
  return swe_data
@@ -8,22 +8,20 @@ Reference: https://spiceypy.readthedocs.io/en/main/documentation.html.
8
8
 
9
9
  import logging
10
10
  import typing
11
- from typing import Union
11
+ from datetime import datetime, timedelta
12
12
 
13
13
  import numpy as np
14
14
  import spiceypy
15
15
  from numpy import ndarray
16
16
 
17
+ from imap_processing.ialirt.constants import STATIONS
17
18
  from imap_processing.spice.geometry import SpiceBody, SpiceFrame, imap_state
18
- from imap_processing.spice.kernels import ensure_spice
19
19
  from imap_processing.spice.time import et_to_utc, str_to_et
20
20
 
21
21
  # Logger setup
22
22
  logger = logging.getLogger(__name__)
23
23
 
24
24
 
25
- @typing.no_type_check
26
- @ensure_spice
27
25
  def latitude_longitude_to_ecef(
28
26
  longitude: float, latitude: float, altitude: float
29
27
  ) -> ndarray:
@@ -68,12 +66,11 @@ def latitude_longitude_to_ecef(
68
66
 
69
67
 
70
68
  @typing.no_type_check
71
- @ensure_spice
72
69
  def calculate_azimuth_and_elevation(
73
70
  longitude: float,
74
71
  latitude: float,
75
72
  altitude: float,
76
- observation_time: Union[float, np.ndarray],
73
+ observation_time: float | np.ndarray,
77
74
  target: str = SpiceBody.IMAP.name,
78
75
  ) -> tuple:
79
76
  """
@@ -137,8 +134,8 @@ def calculate_doppler(
137
134
  longitude: float,
138
135
  latitude: float,
139
136
  altitude: float,
140
- observation_time: Union[float, np.ndarray],
141
- ) -> Union[float, ndarray[float]]:
137
+ observation_time: float | np.ndarray,
138
+ ) -> float | ndarray[float]:
142
139
  """
143
140
  Calculate the doppler velocity.
144
141
 
@@ -194,7 +191,7 @@ def build_output(
194
191
  latitude: float,
195
192
  altitude: float,
196
193
  time_endpoints: tuple[str, str],
197
- time_step: float,
194
+ time_step: float = 60,
198
195
  ) -> dict[str, np.ndarray]:
199
196
  """
200
197
  Build the output dictionary containing time, azimuth, elevation, and doppler.
@@ -210,7 +207,7 @@ def build_output(
210
207
  time_endpoints : tuple[str, str]
211
208
  Start and stop times in UTC.
212
209
  time_step : float
213
- Seconds between data points.
210
+ Seconds between data points. Default is 60.
214
211
 
215
212
  Returns
216
213
  -------
@@ -230,10 +227,10 @@ def build_output(
230
227
  )
231
228
 
232
229
  output_dict["time"] = et_to_utc(time_range, format_str="ISOC")
233
- output_dict["azimuth"] = azimuth
234
- output_dict["elevation"] = elevation
235
- output_dict["doppler"] = calculate_doppler(
236
- longitude, latitude, altitude, time_range
230
+ output_dict["azimuth"] = np.round(azimuth, 6)
231
+ output_dict["elevation"] = np.round(elevation, 6)
232
+ output_dict["doppler"] = np.round(
233
+ calculate_doppler(longitude, latitude, altitude, time_range), 6
237
234
  )
238
235
 
239
236
  logger.info(
@@ -242,3 +239,62 @@ def build_output(
242
239
  )
243
240
 
244
241
  return output_dict
242
+
243
+
244
+ def generate_text_files(station: str, day: str) -> list[str]:
245
+ """
246
+ Generate a pointing schedule text file and return it as a list of strings.
247
+
248
+ Parameters
249
+ ----------
250
+ station : str
251
+ Station name.
252
+ day : str
253
+ The day for which to generate a pointing schedule, in ISO format.
254
+ Ex: "2025-08-11".
255
+
256
+ Returns
257
+ -------
258
+ lines : list[str]
259
+ A list of strings that makeup the lines of a pointing schedule file.
260
+ """
261
+ station_properties = STATIONS[station]
262
+
263
+ day_as_datetime = datetime.fromisoformat(day)
264
+ time_endpoints = (
265
+ datetime.strftime(day_as_datetime, "%Y-%m-%d %H:%M:%S"),
266
+ datetime.strftime(day_as_datetime + timedelta(days=1), "%Y-%m-%d %H:%M:%S"),
267
+ )
268
+ output_dict = build_output(
269
+ station_properties[0],
270
+ station_properties[1],
271
+ station_properties[2],
272
+ time_endpoints,
273
+ )
274
+
275
+ lines = [
276
+ f"Station: {station}\n",
277
+ "Target: IMAP\n",
278
+ f"Creation date (UTC): {datetime.utcnow()}\n",
279
+ f"Start time: {time_endpoints[0]}\n",
280
+ f"End time: {time_endpoints[1]}\n",
281
+ "Cadence (sec): 60\n\n",
282
+ "Date/Time"
283
+ + "Azimuth".rjust(29)
284
+ + "Elevation".rjust(17)
285
+ + "Doppler".rjust(15)
286
+ + "\n",
287
+ "(UTC)" + "(deg.)".rjust(33) + "(deg.)".rjust(16) + "(km/s)".rjust(16) + "\n",
288
+ ]
289
+
290
+ length = len(output_dict["time"])
291
+ for i in range(length):
292
+ lines.append(
293
+ f"{output_dict['time'][i]}"
294
+ + f"{output_dict['azimuth'][i]}".rjust(16)
295
+ + f"{output_dict['elevation'][i]}".rjust(16)
296
+ + f"{output_dict['doppler'][i]}".rjust(15)
297
+ + "\n"
298
+ )
299
+
300
+ return lines
@@ -2,19 +2,19 @@
2
2
 
3
3
  IALIRT_KEYS = [
4
4
  # H intensities in 15 energy ranges and binned into 4 azimuths and 4 spin angle bins
5
- "codicehi_h",
5
+ "codice_hi_h",
6
6
  # C/O abundance ratio
7
- "codicelo_c_over_o_abundance",
7
+ "codice_lo_c_over_o_abundance",
8
8
  # Mg/O abundance ratio
9
- "codicelo_mg_over_o_abundance",
9
+ "codice_lo_mg_over_o_abundance",
10
10
  # Fe/O abundance ratio
11
- "codicelo_fe_over_o_abundance",
11
+ "codice_lo_fe_over_o_abundance",
12
12
  # C+6/C+5 charge state ratio
13
- "codicelo_c_plus_6_over_c_plus_5_ratio",
13
+ "codice_lo_c_plus_6_over_c_plus_5_ratio",
14
14
  # O+7/O+6 charge state ratio
15
- "codicelo_o_plus_7_over_o_plus_6_ratio",
15
+ "codice_lo_o_plus_7_over_o_plus_6_ratio",
16
16
  # Fe low/Fe high charge state ratio
17
- "codicelo_fe_low_over_fe_high_ratio",
17
+ "codice_lo_fe_low_over_fe_high_ratio",
18
18
  # Low energy (~300 keV) electrons (A-side)
19
19
  "hit_e_a_side_low_en",
20
20
  # Medium energy (~3 MeV) electrons (A-side)
@@ -37,26 +37,32 @@ IALIRT_KEYS = [
37
37
  "hit_he_omni_low_en",
38
38
  # High energy (15 to 70 MeV/nuc) He (Omnidirectional)
39
39
  "hit_he_omni_high_en",
40
+ # MAG instrument epoch
41
+ "mag_epoch",
40
42
  # Magnetic field vector in GSE coordinates
41
- "mag_4s_b_gse",
43
+ "mag_B_GSE",
42
44
  # Magnetic field vector in GSM coordinates
43
- "mag_4s_b_gsm",
45
+ "mag_B_GSM",
44
46
  # Magnetic field vector in RTN coordinates
45
- "mag_4s_b_rtn",
47
+ "mag_B_RTN",
48
+ # Magnitude of the magnetic field vector
49
+ "mag_B_magnitude",
46
50
  # Azimuth angle (φ) of the magnetic field in GSM coordinates
47
- "mag_phi_4s_b_gsm",
51
+ "mag_phi_B_GSM",
48
52
  # Elevation angle (θ) of the magnetic field in GSM coordinates
49
- "mag_theta_4s_b_gsm",
53
+ "mag_theta_B_GSM",
54
+ # Azimuth angle (φ) of the magnetic field in GSE coordinates
55
+ "mag_phi_B_GSE",
56
+ # Elevation angle (θ) of the magnetic field in GSE coordinates
57
+ "mag_theta_B_GSE",
50
58
  # Pseudo density of solar wind protons
51
59
  "swapi_pseudo_proton_density",
52
60
  # Pseudo speed of solar wind protons in solar inertial frame
53
61
  "swapi_pseudo_proton_speed",
54
62
  # Pseudo temperature of solar wind protons in plasma frame
55
63
  "swapi_pseudo_proton_temperature",
56
- # SWE Normalized Counts - Half Cycle 1
57
- "swe_normalized_counts_half_1",
58
- # SWE Normalized Counts - Half Cycle 2
59
- "swe_normalized_counts_half_2",
64
+ # SWE Normalized Counts
65
+ "swe_normalized_counts",
60
66
  # SWE Counterstreaming flag
61
67
  "swe_counterstreaming_electrons",
62
68
  ]
@@ -48,6 +48,13 @@ def create_xarray_from_records(records: list[dict]) -> xr.Dataset: # noqa: PLR0
48
48
  attrs=cdf_manager.get_variable_attributes("component", check_schema=False),
49
49
  )
50
50
 
51
+ rtn_component = xr.DataArray(
52
+ ["radial", "tangential", "normal"],
53
+ name="RTN_component",
54
+ dims=["RTN_component"],
55
+ attrs=cdf_manager.get_variable_attributes("RTN_componentt", check_schema=False),
56
+ )
57
+
51
58
  esa_step = xr.DataArray(
52
59
  data=np.arange(8, dtype=np.uint8),
53
60
  name="esa_step",
@@ -57,32 +64,39 @@ def create_xarray_from_records(records: list[dict]) -> xr.Dataset: # noqa: PLR0
57
64
 
58
65
  energy_ranges = xr.DataArray(
59
66
  data=np.arange(15, dtype=np.uint8),
60
- name="energy_ranges",
61
- dims=["energy_ranges"],
62
- attrs=cdf_manager.get_variable_attributes("energy_ranges", check_schema=False),
67
+ name="codice_hi_h_energy_ranges",
68
+ dims=["codice_hi_h_energy_ranges"],
69
+ attrs=cdf_manager.get_variable_attributes(
70
+ "codice_hi_h_energy_ranges", check_schema=False
71
+ ),
63
72
  )
64
73
 
65
- azimuth = xr.DataArray(
74
+ elevation = xr.DataArray(
66
75
  data=np.arange(4, dtype=np.uint8),
67
- name="azimuth",
68
- dims=["azimuth"],
69
- attrs=cdf_manager.get_variable_attributes("azimuth", check_schema=False),
76
+ name="codice_hi_h_elevation",
77
+ dims=["codice_hi_h_elevation"],
78
+ attrs=cdf_manager.get_variable_attributes(
79
+ "codice_hi_h_elevation", check_schema=False
80
+ ),
70
81
  )
71
82
 
72
- spin_angle_bin = xr.DataArray(
83
+ spin_angle = xr.DataArray(
73
84
  data=np.arange(4, dtype=np.uint8),
74
- name="spin_angle_bin",
75
- dims=["spin_angle_bin"],
76
- attrs=cdf_manager.get_variable_attributes("spin_angle_bin", check_schema=False),
85
+ name="codice_hi_h_spin_angle",
86
+ dims=["codice_hi_h_spin_angle"],
87
+ attrs=cdf_manager.get_variable_attributes(
88
+ "codice_hi_h_spin_anglen", check_schema=False
89
+ ),
77
90
  )
78
91
 
79
92
  coords = {
80
93
  "epoch": epoch,
81
94
  "component": component,
95
+ "RTN_component": rtn_component,
82
96
  "esa_step": esa_step,
83
- "energy_ranges": energy_ranges,
84
- "azimuth": azimuth,
85
- "spin_angle_bin": spin_angle_bin,
97
+ "codice_hi_h_energy_ranges": energy_ranges,
98
+ "codice_hi_h_elevation": elevation,
99
+ "codice_hi_h_spin_angle": spin_angle,
86
100
  }
87
101
  dataset = xr.Dataset(
88
102
  coords=coords,
@@ -93,13 +107,22 @@ def create_xarray_from_records(records: list[dict]) -> xr.Dataset: # noqa: PLR0
93
107
  for key in instrument_keys:
94
108
  attrs = cdf_manager.get_variable_attributes(key, check_schema=False)
95
109
  fillval = attrs.get("FILLVAL")
96
- if key.startswith("mag"):
110
+ if key in ["mag_B_GSE", "mag_B_GSM"]:
97
111
  data = np.full((n, 3), fillval, dtype=np.float32)
98
112
  dims = ["epoch", "component"]
99
113
  dataset[key] = xr.DataArray(data, dims=dims, attrs=attrs)
100
- elif key.startswith("codicehi"):
114
+ elif key == "mag_B_RTN":
115
+ data = np.full((n, 3), fillval, dtype=np.float32)
116
+ dims = ["epoch", "RTN_component"]
117
+ dataset[key] = xr.DataArray(data, dims=dims, attrs=attrs)
118
+ elif key.startswith("codice_hi"):
101
119
  data = np.full((n, 15, 4, 4), fillval, dtype=np.float32)
102
- dims = ["epoch", "energy", "azimuth", "spin_angle_bin"]
120
+ dims = [
121
+ "epoch",
122
+ "codice_hi_h_energy_ranges",
123
+ "codice_hi_h_elevation",
124
+ "codice_hi_h_spin_angle",
125
+ ]
103
126
  dataset[key] = xr.DataArray(data, dims=dims, attrs=attrs)
104
127
  elif key == "swe_counterstreaming_electrons":
105
128
  data = np.full(n, fillval, dtype=np.uint8)
@@ -123,11 +146,11 @@ def create_xarray_from_records(records: list[dict]) -> xr.Dataset: # noqa: PLR0
123
146
  for key, val in record.items():
124
147
  if key in ["apid", "met", "met_in_utc", "ttj2000ns"]:
125
148
  continue
126
- elif key.startswith("mag"):
149
+ elif key in ["mag_B_GSE", "mag_B_GSM", "mag_B_RTN"]:
127
150
  dataset[key].data[i, :] = val
128
151
  elif key.startswith("swe_normalized_counts"):
129
152
  dataset[key].data[i, :] = val
130
- elif key.startswith("codicehi"):
153
+ elif key.startswith("codice_hi"):
131
154
  dataset[key].data[i, :, :, :] = val
132
155
  else:
133
156
  dataset[key].data[i] = val
@@ -82,13 +82,9 @@ SPICE_ARRAYS = [
82
82
  "spin_phase",
83
83
  ]
84
84
 
85
- # Default IDEX Healpix parameters
86
- # Used in IDEX l2c processing
87
- IDEX_HEALPIX_NSIDE = 8
88
- IDEX_HEALPIX_NESTED = False
89
85
  # Default IDEX Rectangular parameters
90
86
  # Used in IDEX l2c processing
91
- IDEX_SPACING_DEG = 4 # TODO
87
+ IDEX_SPACING_DEG = 6
92
88
 
93
89
  # Define the pointing reference frame for IDEX
94
90
  IDEX_EVENT_REFERENCE_FRAME = SpiceFrame.ECLIPJ2000
@@ -2,7 +2,7 @@
2
2
 
3
3
  import logging
4
4
  from pathlib import Path
5
- from typing import Any, Union
5
+ from typing import Any
6
6
 
7
7
  from xarray import Dataset
8
8
 
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
13
13
 
14
14
 
15
15
  def decom_packets(
16
- packet_file: Union[str, Path],
16
+ packet_file: str | Path,
17
17
  ) -> tuple[list[Any], dict[int, Dataset], dict[int, Dataset]]:
18
18
  """
19
19
  Decom IDEX data packets using IDEX packet definition.
@@ -17,7 +17,6 @@ Examples
17
17
  import logging
18
18
  from enum import IntEnum
19
19
  from pathlib import Path
20
- from typing import Union
21
20
 
22
21
  import numpy as np
23
22
  import numpy.typing as npt
@@ -61,7 +60,7 @@ class PacketParser:
61
60
  The path and filename to the L0 file to read.
62
61
  """
63
62
 
64
- def __init__(self, packet_file: Union[str, Path]) -> None:
63
+ def __init__(self, packet_file: str | Path) -> None:
65
64
  """
66
65
  Read a L0 pkts file and perform all of the decom work.
67
66
 
@@ -250,7 +249,7 @@ def _read_waveform_bits(waveform_raw: str, high_sample: bool = True) -> list[int
250
249
 
251
250
 
252
251
  def calculate_idex_epoch_time(
253
- shcoarse_time: Union[float, np.ndarray], shfine_time: Union[float, np.ndarray]
252
+ shcoarse_time: float | np.ndarray, shfine_time: float | np.ndarray
254
253
  ) -> npt.NDArray[np.int64]:
255
254
  """
256
255
  Calculate the epoch time from the FPGA header time variables.
@@ -16,7 +16,6 @@ Examples
16
16
 
17
17
  import logging
18
18
  from enum import Enum
19
- from typing import Union
20
19
 
21
20
  import pandas as pd
22
21
  import xarray as xr
@@ -226,7 +225,7 @@ def convert_waveforms(
226
225
 
227
226
  def get_trigger_mode_and_level(
228
227
  l1a_dataset: xr.Dataset,
229
- ) -> Union[dict[str, xr.DataArray], dict]:
228
+ ) -> dict[str, xr.DataArray] | dict:
230
229
  """
231
230
  Determine the trigger mode and threshold level for each event.
232
231
 
@@ -249,7 +248,7 @@ def get_trigger_mode_and_level(
249
248
 
250
249
  def compute_trigger_values(
251
250
  trigger_mode: int, trigger_controls: int, gain_channel: str
252
- ) -> Union[tuple[str, Union[int, float]], tuple[None, None]]:
251
+ ) -> tuple[str, int | float] | tuple[None, None]:
253
252
  """
254
253
  Compute the trigger mode label and threshold level.
255
254