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
@@ -1,6 +1,7 @@
1
1
  """Culls Events for ULTRA L1b."""
2
2
 
3
3
  import logging
4
+ from collections import namedtuple
4
5
 
5
6
  import numpy as np
6
7
  import pandas as pd
@@ -9,18 +10,37 @@ from numpy.typing import NDArray
9
10
 
10
11
  from imap_processing.quality_flags import (
11
12
  ImapAttitudeUltraFlags,
13
+ ImapDEScatteringUltraFlags,
12
14
  ImapHkUltraFlags,
13
15
  ImapInstrumentUltraFlags,
14
16
  ImapRatesUltraFlags,
15
17
  )
16
18
  from imap_processing.spice.spin import get_spin_data
17
19
  from imap_processing.ultra.constants import UltraConstants
20
+ from imap_processing.ultra.l1b.lookup_utils import (
21
+ get_scattering_coefficients,
22
+ get_scattering_thresholds,
23
+ )
24
+ from imap_processing.ultra.l1b.quality_flag_filters import DE_QUALITY_FLAG_FILTERS
18
25
 
19
26
  logging.basicConfig(level=logging.INFO)
20
27
  logger = logging.getLogger(__name__)
21
28
 
22
29
  SPIN_DURATION = 15 # Default spin duration in seconds.
23
30
 
31
+ RateResult = namedtuple(
32
+ "RateResult",
33
+ [
34
+ "unique_spins",
35
+ "start_per_spin",
36
+ "stop_per_spin",
37
+ "coin_per_spin",
38
+ "start_pulses",
39
+ "stop_pulses",
40
+ "coin_pulses",
41
+ ],
42
+ )
43
+
24
44
 
25
45
  def get_energy_histogram(
26
46
  spin_number: NDArray, energy: NDArray
@@ -230,8 +250,8 @@ def flag_rates(
230
250
  Quality flags.
231
251
  spin : NDArray
232
252
  Spin data.
233
- energy_midpoints : NDArray
234
- Energy midpoint data.
253
+ energy_bin_geometric_mean : NDArray
254
+ Energy bin geometric mean.
235
255
  n_sigma_per_energy_reshape : NDArray
236
256
  N sigma per energy.
237
257
  """
@@ -245,7 +265,7 @@ def flag_rates(
245
265
  threshold = get_n_sigma(count_rates, duration, sigma=sigma)
246
266
 
247
267
  bin_edges = np.array(UltraConstants.CULLING_ENERGY_BIN_EDGES)
248
- energy_midpoints = np.sqrt(bin_edges[:-1] * bin_edges[1:])
268
+ energy_bin_geometric_mean = np.sqrt(bin_edges[:-1] * bin_edges[1:])
249
269
  spin = np.unique(spin_number)
250
270
 
251
271
  # Indices where the counts exceed the threshold
@@ -256,7 +276,7 @@ def flag_rates(
256
276
  quality_flags[:, 0] |= ImapRatesUltraFlags.FIRSTSPIN.value
257
277
  quality_flags[:, -1] |= ImapRatesUltraFlags.LASTSPIN.value
258
278
 
259
- return quality_flags, spin, energy_midpoints, threshold
279
+ return quality_flags, spin, energy_bin_geometric_mean, threshold
260
280
 
261
281
 
262
282
  def compare_aux_univ_spin_table(
@@ -369,7 +389,7 @@ def get_spin_and_duration(met: NDArray, spin: NDArray) -> tuple[NDArray, NDArray
369
389
  possible_spins = spin_numbers & 0xFF
370
390
 
371
391
  # Assign each group based on time.
372
- for start, end in zip(spin_start_indices, spin_end_indices):
392
+ for start, end in zip(spin_start_indices, spin_end_indices, strict=False):
373
393
  # Now that we have the possible spins from the Universal Spin Table,
374
394
  # we match the times of those spins to the nearest times in the DE data.
375
395
  possible_times = spin_start_mets[
@@ -394,7 +414,7 @@ def get_spin_and_duration(met: NDArray, spin: NDArray) -> tuple[NDArray, NDArray
394
414
  return assigned_spin_number, assigned_duration
395
415
 
396
416
 
397
- def get_pulses_per_spin(rates: xr.Dataset) -> tuple[NDArray, NDArray, NDArray]:
417
+ def get_pulses_per_spin(rates: xr.Dataset) -> RateResult:
398
418
  """
399
419
  Get the total number of pulses per spin.
400
420
 
@@ -405,12 +425,20 @@ def get_pulses_per_spin(rates: xr.Dataset) -> tuple[NDArray, NDArray, NDArray]:
405
425
 
406
426
  Returns
407
427
  -------
428
+ unique_spins : NDArray
429
+ Unique spin numbers.
408
430
  start_per_spin : NDArray
409
431
  Total start pulses per spin.
410
432
  stop_per_spin : NDArray
411
433
  Total stop pulses per spin.
412
434
  coin_per_spin : NDArray
413
435
  Total coincidence pulses per spin.
436
+ start_pulses : NDArray
437
+ Total start pulses.
438
+ stop_pulses : NDArray
439
+ Total stop pulses.
440
+ coin_pulses : NDArray
441
+ Total coincidence pulses.
414
442
  """
415
443
  spin_number, duration = get_spin_and_duration(rates["shcoarse"], rates["spin"])
416
444
 
@@ -448,4 +476,138 @@ def get_pulses_per_spin(rates: xr.Dataset) -> tuple[NDArray, NDArray, NDArray]:
448
476
  stop_per_spin = np.bincount(spin_idx, weights=stop_pulses)
449
477
  coin_per_spin = np.bincount(spin_idx, weights=coin_pulses)
450
478
 
451
- return start_per_spin, stop_per_spin, coin_per_spin
479
+ return RateResult(
480
+ unique_spins=unique_spins,
481
+ start_per_spin=start_per_spin,
482
+ stop_per_spin=stop_per_spin,
483
+ coin_per_spin=coin_per_spin,
484
+ start_pulses=start_pulses,
485
+ stop_pulses=stop_pulses,
486
+ coin_pulses=coin_pulses,
487
+ )
488
+
489
+
490
+ def flag_scattering(
491
+ tof_energy: NDArray,
492
+ theta: NDArray,
493
+ phi: NDArray,
494
+ ancillary_files: dict,
495
+ sensor: str,
496
+ quality_flags: NDArray,
497
+ ) -> None:
498
+ """
499
+ Flag events where either theta or phi FWHM exceed the threshold or equal nan.
500
+
501
+ Parameters
502
+ ----------
503
+ tof_energy : NDArray
504
+ TOF energy for each event in keV.
505
+ theta : NDArray
506
+ Elevation angles in degrees.
507
+ phi : NDArray
508
+ Azimuth angles in degrees.
509
+ ancillary_files : dict[Path]
510
+ Ancillary files.
511
+ sensor : str
512
+ Sensor name: "ultra45" or "ultra90".
513
+ quality_flags : NDArray
514
+ Quality flags.
515
+ """
516
+ scattering_thresholds = get_scattering_thresholds(ancillary_files)
517
+
518
+ for (e_min, e_max), threshold in scattering_thresholds.items():
519
+ event_mask = (tof_energy >= e_min) & (tof_energy < e_max)
520
+ # Input the theta and phi values for the current energy range.
521
+ # Returns a_theta_val, g_theta_val, a_phi_val, g_phi_val
522
+ theta_coeffs, phi_coeffs = get_scattering_coefficients(
523
+ theta[event_mask],
524
+ phi[event_mask],
525
+ lookup_tables=None,
526
+ ancillary_files=ancillary_files,
527
+ instrument_id=int(sensor[-2:]),
528
+ )
529
+ # FWHM_PHI = A_PHI * E^G_PHI
530
+ # FWHM_THETA = A_THETA * E^G_THETA
531
+ fwhm_theta = theta_coeffs[:, 0] * tof_energy[event_mask] ** theta_coeffs[:, 1]
532
+ fwhm_phi = phi_coeffs[:, 0] * tof_energy[event_mask] ** phi_coeffs[:, 1]
533
+ is_nan = np.isnan(fwhm_theta) | np.isnan(fwhm_phi)
534
+ quality_flags[np.where(event_mask)[0][is_nan]] |= (
535
+ ImapDEScatteringUltraFlags.NAN_PHI_OR_THETA.value
536
+ )
537
+
538
+ theta_exceeds = fwhm_theta > threshold
539
+ phi_exceeds = fwhm_phi > threshold
540
+ either_exceeds = theta_exceeds | phi_exceeds
541
+
542
+ # Set flags for events where either theta or phi FWHM exceed the threshold
543
+ quality_flags[np.where(event_mask)[0][either_exceeds]] |= (
544
+ ImapDEScatteringUltraFlags.ABOVE_THRESHOLD.value
545
+ )
546
+
547
+
548
+ def get_de_rejection_mask(
549
+ quality_scattering: NDArray, quality_outliers: NDArray
550
+ ) -> NDArray:
551
+ """
552
+ Create boolean mask where event is rejected due to relevant flags.
553
+
554
+ Parameters
555
+ ----------
556
+ quality_scattering : NDArray
557
+ Quality scattering flags.
558
+ quality_outliers : NDArray
559
+ Quality outliers flags.
560
+
561
+ Returns
562
+ -------
563
+ rejected : NDArray
564
+ Rejected events where True = rejected.
565
+ """
566
+ # Bitmasks from the DE_QUALITY_FLAG_FILTERS
567
+ scattering_mask = sum(
568
+ flag.value for flag in DE_QUALITY_FLAG_FILTERS["quality_scattering"]
569
+ )
570
+ outliers_mask = sum(
571
+ flag.value for flag in DE_QUALITY_FLAG_FILTERS["quality_outliers"]
572
+ )
573
+
574
+ # Boolean mask where event is rejected due to relevant flags
575
+ rejected = ((quality_scattering & scattering_mask) != 0) | (
576
+ (quality_outliers & outliers_mask) != 0
577
+ )
578
+
579
+ return rejected
580
+
581
+
582
+ def count_rejected_events_per_spin(
583
+ spins: NDArray, quality_scattering: NDArray, quality_outliers: NDArray
584
+ ) -> NDArray:
585
+ """
586
+ Count rejected events per spin based on DE_QUALITY_FLAG_FILTERS.
587
+
588
+ Parameters
589
+ ----------
590
+ spins : NDArray
591
+ Spins in which each direct event is within.
592
+ quality_scattering : NDArray
593
+ Quality scattering flags.
594
+ quality_outliers : NDArray
595
+ Quality outliers flags.
596
+
597
+ Returns
598
+ -------
599
+ rejected_counts : NDArray
600
+ Rejected counts per spin.
601
+ """
602
+ # Boolean mask where event is rejected due to relevant flags
603
+ rejected = get_de_rejection_mask(quality_scattering, quality_outliers)
604
+
605
+ # Unique spin numbers
606
+ unique_spins = np.unique(spins)
607
+
608
+ # Count rejected events per spin
609
+ rejected_counts = np.array(
610
+ [np.count_nonzero(rejected[spins == spin]) for spin in unique_spins], dtype=int
611
+ )
612
+
613
+ return rejected_counts