imap-processing 0.16.2__py3-none-any.whl → 0.18.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 (110) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/ccsds/excel_to_xtce.py +12 -0
  3. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -6
  4. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +35 -0
  5. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +35 -0
  6. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +24 -0
  7. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +8 -8
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +1 -1
  9. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +163 -100
  10. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +398 -415
  11. imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +97 -54
  12. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +9 -9
  13. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +233 -57
  14. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +16 -90
  15. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
  16. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +15 -1
  17. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +19 -0
  18. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +20 -0
  19. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +39 -0
  20. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +168 -0
  21. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +103 -2
  22. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +91 -11
  23. imap_processing/cdf/utils.py +7 -1
  24. imap_processing/cli.py +42 -13
  25. imap_processing/codice/codice_l1a.py +125 -78
  26. imap_processing/codice/codice_l1b.py +1 -1
  27. imap_processing/codice/codice_l2.py +0 -9
  28. imap_processing/codice/constants.py +481 -498
  29. imap_processing/hi/hi_l1a.py +4 -4
  30. imap_processing/hi/hi_l1b.py +2 -2
  31. imap_processing/hi/packet_definitions/TLM_HI_COMBINED_SCI.xml +218 -38
  32. imap_processing/hit/hit_utils.py +2 -2
  33. imap_processing/hit/l0/decom_hit.py +4 -3
  34. imap_processing/hit/l1a/hit_l1a.py +64 -24
  35. imap_processing/hit/l1b/constants.py +5 -0
  36. imap_processing/hit/l1b/hit_l1b.py +18 -16
  37. imap_processing/hit/l2/constants.py +1 -1
  38. imap_processing/hit/l2/hit_l2.py +4 -4
  39. imap_processing/ialirt/constants.py +21 -0
  40. imap_processing/ialirt/generate_coverage.py +188 -0
  41. imap_processing/ialirt/l0/parse_mag.py +62 -5
  42. imap_processing/ialirt/l0/process_swapi.py +1 -1
  43. imap_processing/ialirt/l0/process_swe.py +23 -7
  44. imap_processing/ialirt/utils/constants.py +22 -16
  45. imap_processing/ialirt/utils/create_xarray.py +42 -19
  46. imap_processing/idex/idex_constants.py +8 -5
  47. imap_processing/idex/idex_l2b.py +554 -58
  48. imap_processing/idex/idex_l2c.py +30 -196
  49. imap_processing/lo/l0/lo_apid.py +1 -0
  50. imap_processing/lo/l0/lo_star_sensor.py +48 -0
  51. imap_processing/lo/l1a/lo_l1a.py +74 -30
  52. imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
  53. imap_processing/mag/constants.py +1 -0
  54. imap_processing/mag/l0/decom_mag.py +9 -6
  55. imap_processing/mag/l0/mag_l0_data.py +46 -0
  56. imap_processing/mag/l1d/__init__.py +0 -0
  57. imap_processing/mag/l1d/mag_l1d.py +133 -0
  58. imap_processing/mag/l1d/mag_l1d_data.py +588 -0
  59. imap_processing/mag/l2/__init__.py +0 -0
  60. imap_processing/mag/l2/mag_l2.py +25 -20
  61. imap_processing/mag/l2/mag_l2_data.py +191 -130
  62. imap_processing/quality_flags.py +20 -2
  63. imap_processing/spice/geometry.py +25 -3
  64. imap_processing/spice/pointing_frame.py +1 -1
  65. imap_processing/spice/spin.py +4 -0
  66. imap_processing/spice/time.py +51 -0
  67. imap_processing/swapi/l1/swapi_l1.py +12 -2
  68. imap_processing/swapi/l2/swapi_l2.py +59 -14
  69. imap_processing/swapi/swapi_utils.py +1 -1
  70. imap_processing/swe/l1b/swe_l1b.py +11 -4
  71. imap_processing/swe/l2/swe_l2.py +111 -17
  72. imap_processing/ultra/constants.py +49 -1
  73. imap_processing/ultra/l0/decom_tools.py +28 -14
  74. imap_processing/ultra/l0/decom_ultra.py +225 -15
  75. imap_processing/ultra/l0/ultra_utils.py +281 -8
  76. imap_processing/ultra/l1a/ultra_l1a.py +77 -8
  77. imap_processing/ultra/l1b/cullingmask.py +3 -3
  78. imap_processing/ultra/l1b/de.py +53 -15
  79. imap_processing/ultra/l1b/extendedspin.py +26 -2
  80. imap_processing/ultra/l1b/lookup_utils.py +171 -50
  81. imap_processing/ultra/l1b/quality_flag_filters.py +14 -0
  82. imap_processing/ultra/l1b/ultra_l1b_culling.py +198 -5
  83. imap_processing/ultra/l1b/ultra_l1b_extended.py +304 -66
  84. imap_processing/ultra/l1c/helio_pset.py +54 -7
  85. imap_processing/ultra/l1c/spacecraft_pset.py +9 -1
  86. imap_processing/ultra/l1c/ultra_l1c.py +2 -0
  87. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +106 -109
  88. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +3 -3
  89. imap_processing/ultra/utils/ultra_l1_utils.py +13 -1
  90. imap_processing/utils.py +20 -42
  91. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/METADATA +2 -2
  92. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/RECORD +95 -103
  93. imap_processing/lo/l0/data_classes/star_sensor.py +0 -98
  94. imap_processing/lo/l0/utils/lo_base.py +0 -57
  95. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +0 -526
  96. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +0 -526
  97. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +0 -526
  98. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +0 -524
  99. imap_processing/ultra/lookup_tables/EgyNorm.mem.csv +0 -32769
  100. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  101. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
  102. imap_processing/ultra/lookup_tables/dps_grid45_compressed.cdf +0 -0
  103. imap_processing/ultra/lookup_tables/ultra45_back-pos-luts.csv +0 -4097
  104. imap_processing/ultra/lookup_tables/ultra45_tdc_norm.csv +0 -2050
  105. imap_processing/ultra/lookup_tables/ultra90_back-pos-luts.csv +0 -4097
  106. imap_processing/ultra/lookup_tables/ultra90_tdc_norm.csv +0 -2050
  107. imap_processing/ultra/lookup_tables/yadjust.csv +0 -257
  108. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/LICENSE +0 -0
  109. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/WHEEL +0 -0
  110. {imap_processing-0.16.2.dist-info → imap_processing-0.18.0.dist-info}/entry_points.txt +0 -0
@@ -7,7 +7,12 @@ import pandas as pd
7
7
  import xarray as xr
8
8
  from numpy.typing import NDArray
9
9
 
10
- from imap_processing.quality_flags import ImapAttitudeUltraFlags, ImapRatesUltraFlags
10
+ from imap_processing.quality_flags import (
11
+ ImapAttitudeUltraFlags,
12
+ ImapHkUltraFlags,
13
+ ImapInstrumentUltraFlags,
14
+ ImapRatesUltraFlags,
15
+ )
11
16
  from imap_processing.spice.spin import get_spin_data
12
17
  from imap_processing.ultra.constants import UltraConstants
13
18
 
@@ -106,6 +111,10 @@ def flag_attitude(
106
111
 
107
112
  spin_period = spin_df.loc[spin_df.spin_number.isin(spins), "spin_period_sec"]
108
113
  spin_starttime = spin_df.loc[spin_df.spin_number.isin(spins), "spin_start_met"]
114
+ spin_phase_valid = spin_df.loc[spin_df.spin_number.isin(spins), "spin_phase_valid"]
115
+ spin_period_valid = spin_df.loc[
116
+ spin_df.spin_number.isin(spins), "spin_period_valid"
117
+ ]
109
118
  spin_rates = 60 / spin_period # 60 seconds in a minute
110
119
  bad_spin_rate_indices = (spin_rates < UltraConstants.CULLING_RPM_MIN) | (
111
120
  spin_rates > UltraConstants.CULLING_RPM_MAX
@@ -118,9 +127,59 @@ def flag_attitude(
118
127
  mismatch_indices = compare_aux_univ_spin_table(aux_dataset, spins, spin_df)
119
128
  quality_flags[mismatch_indices] |= ImapAttitudeUltraFlags.AUXMISMATCH.value
120
129
 
130
+ # Spin phase validity flag
131
+ phase_invalid_indices = spin_phase_valid == 0
132
+ quality_flags[phase_invalid_indices] |= ImapAttitudeUltraFlags.SPINPHASE.value
133
+
134
+ # Spin period validity flag
135
+ period_invalid_indices = ~spin_period_valid
136
+ quality_flags[period_invalid_indices] |= ImapAttitudeUltraFlags.SPINPERIOD.value
137
+
121
138
  return quality_flags, spin_rates, spin_period, spin_starttime
122
139
 
123
140
 
141
+ def flag_hk(spin_number: NDArray) -> NDArray:
142
+ """
143
+ Flag data based on hk.
144
+
145
+ Parameters
146
+ ----------
147
+ spin_number : NDArray
148
+ Spin number at each direct event.
149
+
150
+ Returns
151
+ -------
152
+ quality_flags : NDArray
153
+ Quality flags..
154
+ """
155
+ spins = np.unique(spin_number) # Get unique spins
156
+ quality_flags = np.full(spins.shape, ImapHkUltraFlags.NONE.value, dtype=np.uint16)
157
+
158
+ return quality_flags
159
+
160
+
161
+ def flag_imap_instruments(spin_number: NDArray) -> NDArray:
162
+ """
163
+ Flag data based on other IMAP instruments.
164
+
165
+ Parameters
166
+ ----------
167
+ spin_number : NDArray
168
+ Spin number at each direct event.
169
+
170
+ Returns
171
+ -------
172
+ quality_flags : NDArray
173
+ Quality flags..
174
+ """
175
+ spins = np.unique(spin_number) # Get unique spins
176
+ quality_flags = np.full(
177
+ spins.shape, ImapInstrumentUltraFlags.NONE.value, dtype=np.uint16
178
+ )
179
+
180
+ return quality_flags
181
+
182
+
124
183
  def get_n_sigma(count_rates: NDArray, mean_duration: float, sigma: int = 6) -> NDArray:
125
184
  """
126
185
  Calculate the threshold for the HIGHRATES flag.
@@ -140,7 +199,8 @@ def get_n_sigma(count_rates: NDArray, mean_duration: float, sigma: int = 6) -> N
140
199
  threshold : NDArray
141
200
  Threshold for applying HIGHRATES flag.
142
201
  """
143
- sigma_per_energy = np.std(count_rates, axis=1)
202
+ # Take the Sample Standard Deviation.
203
+ sigma_per_energy = np.std(count_rates, axis=1, ddof=1)
144
204
  n_sigma_per_energy = sigma * sigma_per_energy
145
205
  mean_per_energy = np.mean(count_rates, axis=1)
146
206
  # Must have a HIGHRATES threshold of at least 3 counts per spin.
@@ -149,7 +209,7 @@ def get_n_sigma(count_rates: NDArray, mean_duration: float, sigma: int = 6) -> N
149
209
  return threshold
150
210
 
151
211
 
152
- def flag_spin(
212
+ def flag_rates(
153
213
  spin_number: NDArray, energy: NDArray, sigma: int = 6
154
214
  ) -> tuple[NDArray, NDArray, NDArray, NDArray]:
155
215
  """
@@ -182,8 +242,6 @@ def flag_spin(
182
242
  count_rates.shape, ImapRatesUltraFlags.NONE.value, dtype=np.uint16
183
243
  )
184
244
 
185
- # Zero counts/spin/energy level
186
- quality_flags[counts == 0] |= ImapRatesUltraFlags.ZEROCOUNTS.value
187
245
  threshold = get_n_sigma(count_rates, duration, sigma=sigma)
188
246
 
189
247
  bin_edges = np.array(UltraConstants.CULLING_ENERGY_BIN_EDGES)
@@ -194,6 +252,10 @@ def flag_spin(
194
252
  indices_n_sigma = count_rates > threshold[:, np.newaxis]
195
253
  quality_flags[indices_n_sigma] |= ImapRatesUltraFlags.HIGHRATES.value
196
254
 
255
+ # Flags the first and last spin
256
+ quality_flags[:, 0] |= ImapRatesUltraFlags.FIRSTSPIN.value
257
+ quality_flags[:, -1] |= ImapRatesUltraFlags.LASTSPIN.value
258
+
197
259
  return quality_flags, spin, energy_midpoints, threshold
198
260
 
199
261
 
@@ -256,3 +318,134 @@ def compare_aux_univ_spin_table(
256
318
  mismatch_indices[missing_spin_mask] = True
257
319
 
258
320
  return mismatch_indices
321
+
322
+
323
+ # TODO: Make this a common util since it is being used for the de and rates packets.
324
+ def get_spin_and_duration(met: NDArray, spin: NDArray) -> tuple[NDArray, NDArray]:
325
+ """
326
+ Get the spin number and duration.
327
+
328
+ Parameters
329
+ ----------
330
+ met : NDArray
331
+ Mission elapsed time.
332
+ spin : NDArray
333
+ Spin number 0-255.
334
+
335
+ Returns
336
+ -------
337
+ assigned_spin_number : NDArray
338
+ Spin number for packet data product.
339
+ """
340
+ # Packet data.
341
+ # Since the spin number in the direct events packet
342
+ # is only 8 bits it goes from 0-255.
343
+ # Within a pointing that means we will always have duplicate spin numbers.
344
+ # In other words, different spins will be represented by the same spin number.
345
+ # Just to make certain that we won't accidentally combine
346
+ # multiple spins we need to sort by time here.
347
+ sort_idx = np.argsort(met)
348
+ packet_met_sorted = met[sort_idx]
349
+ packet_spin_sorted = spin[sort_idx]
350
+ # Here we are finding the start and end indices of each spin in the sorted array.
351
+ is_new_spin = np.concatenate(
352
+ [[True], packet_spin_sorted.values[1:] != packet_spin_sorted.values[:-1]]
353
+ )
354
+ spin_start_indices = np.where(is_new_spin)[0]
355
+ spin_end_indices = np.append(spin_start_indices[1:], len(packet_met_sorted))
356
+
357
+ # Universal Spin Table.
358
+ spin_df = get_spin_data()
359
+ # Retrieve the met values of the start of the spin.
360
+ spin_start_mets = spin_df["spin_start_met"].values
361
+ # Retrieve the corresponding spin numbers.
362
+ spin_numbers = spin_df["spin_number"].values
363
+ spin_period_sec = spin_df["spin_period_sec"].values
364
+ assigned_spin_number_sorted = np.empty(packet_spin_sorted.shape, dtype=np.uint32)
365
+ assigned_spin_duration_sorted = np.empty(packet_spin_sorted.shape, dtype=np.float32)
366
+ # These last 8 bits are the same as the spin number in the DE packet.
367
+ # So this will give us choices of which spins are
368
+ # available to assign to the packet data.
369
+ possible_spins = spin_numbers & 0xFF
370
+
371
+ # Assign each group based on time.
372
+ for start, end in zip(spin_start_indices, spin_end_indices):
373
+ # Now that we have the possible spins from the Universal Spin Table,
374
+ # we match the times of those spins to the nearest times in the DE data.
375
+ possible_times = spin_start_mets[
376
+ possible_spins == packet_spin_sorted.values[start]
377
+ ]
378
+ # Get nearest time for matching spins.
379
+ nearest_idx = np.abs(possible_times - packet_met_sorted.values[start]).argmin()
380
+ nearest_value = possible_times[nearest_idx]
381
+ assigned_spin_number_sorted[start:end] = spin_numbers[
382
+ spin_start_mets == nearest_value
383
+ ]
384
+ assigned_spin_duration_sorted[start:end] = spin_period_sec[
385
+ spin_start_mets == nearest_value
386
+ ]
387
+
388
+ # Undo the sort to match original order.
389
+ assigned_spin_number = np.empty_like(assigned_spin_number_sorted)
390
+ assigned_spin_number[sort_idx] = assigned_spin_number_sorted
391
+ assigned_duration = np.empty_like(assigned_spin_duration_sorted)
392
+ assigned_duration[sort_idx] = assigned_spin_duration_sorted
393
+
394
+ return assigned_spin_number, assigned_duration
395
+
396
+
397
+ def get_pulses_per_spin(rates: xr.Dataset) -> tuple[NDArray, NDArray, NDArray]:
398
+ """
399
+ Get the total number of pulses per spin.
400
+
401
+ Parameters
402
+ ----------
403
+ rates : xr.Dataset
404
+ Rates dataset.
405
+
406
+ Returns
407
+ -------
408
+ start_per_spin : NDArray
409
+ Total start pulses per spin.
410
+ stop_per_spin : NDArray
411
+ Total stop pulses per spin.
412
+ coin_per_spin : NDArray
413
+ Total coincidence pulses per spin.
414
+ """
415
+ spin_number, duration = get_spin_and_duration(rates["shcoarse"], rates["spin"])
416
+
417
+ # Top coin pulses
418
+ top_coin_pulses = np.stack(
419
+ [v for k, v in rates.items() if k.startswith("coin_t")], axis=1
420
+ )
421
+ max_top_coin_pulse = np.max(top_coin_pulses, axis=1)
422
+
423
+ # Bottom coin pulses
424
+ bottom_coin_pulses = np.stack(
425
+ [v for k, v in rates.items() if k.startswith("coin_b")], axis=1
426
+ )
427
+ max_bottom_coin_pulse = np.max(bottom_coin_pulses, axis=1)
428
+
429
+ # Top stop pulses
430
+ top_stop_pulses = np.stack(
431
+ [v for k, v in rates.items() if k.startswith("stop_t")], axis=1
432
+ )
433
+ max_top_stop_pulse = np.max(top_stop_pulses, axis=1)
434
+
435
+ # Bottom stop pulses
436
+ bottom_stop_pulses = np.stack(
437
+ [v for k, v in rates.items() if k.startswith("stop_b")], axis=1
438
+ )
439
+ max_bottom_stop_pulse = np.max(bottom_stop_pulses, axis=1)
440
+
441
+ stop_pulses = max_top_stop_pulse + max_bottom_stop_pulse
442
+ start_pulses = rates["start_rf"] + rates["start_lf"]
443
+ coin_pulses = max_top_coin_pulse + max_bottom_coin_pulse
444
+
445
+ unique_spins, spin_idx = np.unique(spin_number, return_inverse=True)
446
+
447
+ start_per_spin = np.bincount(spin_idx, weights=start_pulses)
448
+ stop_per_spin = np.bincount(spin_idx, weights=stop_pulses)
449
+ coin_per_spin = np.bincount(spin_idx, weights=coin_pulses)
450
+
451
+ return start_per_spin, stop_per_spin, coin_per_spin