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
@@ -24,7 +24,7 @@ import pandas as pd
24
24
  import xarray as xr
25
25
  from numpy.typing import NDArray
26
26
  from scipy.integrate import quad
27
- from scipy.optimize import curve_fit
27
+ from scipy.optimize import curve_fit, root_scalar
28
28
  from scipy.signal import butter, detrend, filtfilt, find_peaks
29
29
  from scipy.stats import exponnorm
30
30
 
@@ -52,7 +52,33 @@ class BaselineNoiseTime(IntEnum):
52
52
  STOP = -5
53
53
 
54
54
 
55
- def idex_l2a(l1b_dataset: xr.Dataset) -> xr.Dataset:
55
+ def load_calibration_files(ancillary_files: dict) -> tuple[NDArray, NDArray]:
56
+ """
57
+ Load calibration files for IDEX L2A processing.
58
+
59
+ Parameters
60
+ ----------
61
+ ancillary_files : dict
62
+ Dictionary containing paths to calibration files.
63
+
64
+ Returns
65
+ -------
66
+ numpy.ndarray
67
+ Calibration parameters for the rise time function.
68
+ numpy.ndarray
69
+ Calibration parameters for the charge yield function.
70
+ """
71
+ # Load calibration coefficients from ancillary files
72
+ t_rise_params = pd.read_csv(
73
+ ancillary_files["l2a-calibration-curve-yield-params"], skiprows=1, header=None
74
+ ).values.flatten()[:8]
75
+ yield_params = pd.read_csv(
76
+ ancillary_files["l2a-calibration-curve-t-rise"], skiprows=1, header=None
77
+ ).values.flatten()[:8]
78
+ return t_rise_params, yield_params
79
+
80
+
81
+ def idex_l2a(l1b_dataset: xr.Dataset, ancillary_files: dict) -> xr.Dataset:
56
82
  """
57
83
  Will process IDEX l1b data to create l2a data products.
58
84
 
@@ -68,6 +94,9 @@ def idex_l2a(l1b_dataset: xr.Dataset) -> xr.Dataset:
68
94
  ----------
69
95
  l1b_dataset : xarray.Dataset
70
96
  IDEX L1a dataset to process.
97
+ ancillary_files : dict
98
+ Ancillary files containing calibration coefficients needed to estimate
99
+ velocity and mass of the dust particles.
71
100
 
72
101
  Returns
73
102
  -------
@@ -79,6 +108,7 @@ def idex_l2a(l1b_dataset: xr.Dataset) -> xr.Dataset:
79
108
  logger.info(
80
109
  f"Running IDEX L2A processing on dataset: {l1b_dataset.attrs['Logical_source']}"
81
110
  )
111
+ t_rise_params, yield_params = load_calibration_files(ancillary_files)
82
112
 
83
113
  tof_high = l1b_dataset["TOF_High"]
84
114
  hs_time = l1b_dataset["time_high_sample_rate"]
@@ -176,11 +206,24 @@ def idex_l2a(l1b_dataset: xr.Dataset) -> xr.Dataset:
176
206
  output_dtypes=[np.float64] * 6,
177
207
  keep_attrs=True,
178
208
  )
209
+ # Calculate mass and velocity estimates
210
+ velocity_mass_results = xr.apply_ufunc(
211
+ calculate_velocity_and_mass,
212
+ fit_results[1], # signal amplitude
213
+ fit_results[0].data[:, 3], # fit params
214
+ output_core_dims=[[], []],
215
+ vectorize=True,
216
+ output_dtypes=[np.float64, np.float64],
217
+ keep_attrs=True,
218
+ kwargs={"t_rise_params": t_rise_params, "yield_params": yield_params},
219
+ )
220
+
179
221
  waveform_name = waveform.lower()
180
222
  output_vars = {
181
223
  f"{waveform_name}_fit_parameters": fit_results[0],
182
224
  f"{waveform_name}_impact_charge": fit_results[1],
183
- f"{waveform_name}_dust_mass_estimate": fit_results[1],
225
+ f"{waveform_name}_velocity_estimate": velocity_mass_results[0],
226
+ f"{waveform_name}_dust_mass_estimate": velocity_mass_results[1],
184
227
  # Same as impact_charge for now
185
228
  f"{waveform_name}_chi_squared": fit_results[2],
186
229
  f"{waveform_name}_reduced_chi_squared": fit_results[3],
@@ -261,6 +304,89 @@ def idex_l2a(l1b_dataset: xr.Dataset) -> xr.Dataset:
261
304
  return l2a_dataset
262
305
 
263
306
 
307
+ def calculate_velocity_and_mass(
308
+ sig_amp: float, t_rise: float, t_rise_params: np.ndarray, yield_params: np.ndarray
309
+ ) -> tuple[float, float]:
310
+ """
311
+ Calculate velocity and mass estimates.
312
+
313
+ The fitted target signals are used to generate IDEX’s specific charge yield as a
314
+ function of the impact speed. The calibration curve is fitted with a
315
+ segmented power law distribution. The charge yield curve enables the mass of
316
+ the dust particle to be estimated from the total charge it generates on the target.
317
+
318
+ Parameters
319
+ ----------
320
+ sig_amp : float
321
+ Signal amplitude.
322
+ t_rise : float
323
+ T_rise fit parameter from the target fit.
324
+ t_rise_params : np.ndarray
325
+ Calibration parameters for rise time.
326
+ yield_params : np.ndarray
327
+ Calibration parameters for yield.
328
+
329
+ Returns
330
+ -------
331
+ v_est : float
332
+ Estimated velocity.
333
+ mass_est : float
334
+ Estimated mass.
335
+ """
336
+ log_a_t: float = np.log10(t_rise_params[0])
337
+ try:
338
+ root = root_scalar(
339
+ lambda lv: log_smooth_powerlaw(lv, log_a_t, t_rise_params[1:])
340
+ - np.log10(t_rise),
341
+ bracket=[-1, 2],
342
+ )
343
+ v_est = 10**root.root
344
+ except Exception:
345
+ logger.error(
346
+ "Unable to calculate velocity and mass estimate. "
347
+ "The root finding failed for power law function. "
348
+ "Returning nans for the estimate."
349
+ )
350
+ return np.nan, np.nan
351
+
352
+ log_a_y: float = np.log10(yield_params[0])
353
+ yield_val = 10 ** log_smooth_powerlaw(np.log10(v_est), log_a_y, yield_params[1:])
354
+ mass_est = sig_amp / yield_val
355
+
356
+ return v_est, mass_est
357
+
358
+
359
+ def log_smooth_powerlaw(log_v: float, log_a: float, params: np.ndarray) -> float:
360
+ """
361
+ Define a smoothly transitioning power law to fit the calibration curve to.
362
+
363
+ Parameters
364
+ ----------
365
+ log_v : float
366
+ Velocity.
367
+ log_a : float
368
+ Scale factor.
369
+ params : np.ndarray
370
+ Calibration parameters for the power law.
371
+
372
+ Returns
373
+ -------
374
+ float
375
+ The value of the power law at the given velocity.
376
+ """
377
+ # Unpack the rest of the calibration parameters
378
+ # a1, a2, and a3 are the power law exponents for the low, medium, and high-velocity
379
+ # segments.
380
+ # vb and vc are the characteristic speeds where the slope transition happens, and k
381
+ # setting the sharpness of the transitions.
382
+ a1, a2, a3, vb, vc, k, m = params
383
+ v = 10**log_v
384
+ base = log_a + a1 * log_v
385
+ transition1 = (1 + (v / vb) ** m) ** ((a2 - a1) / m)
386
+ transition2 = (1 + (v / vc) ** m) ** ((a3 - a2) / m)
387
+ return base + np.log10(transition1 * transition2)
388
+
389
+
264
390
  def time_to_mass(
265
391
  tof_high: np.ndarray, high_sampling_time: np.ndarray, masses: np.ndarray
266
392
  ) -> tuple[NDArray, NDArray, NDArray]:
@@ -399,7 +525,7 @@ def calculate_kappa(mass_scales: np.ndarray, peaks_2d: list) -> NDArray:
399
525
  kappas = np.asarray(
400
526
  [
401
527
  np.mean(mass_scale[peaks] - np.round(mass_scale[peaks]))
402
- for mass_scale, peaks in zip(mass_scales, peaks_2d)
528
+ for mass_scale, peaks in zip(mass_scales, peaks_2d, strict=False)
403
529
  ]
404
530
  )
405
531
  return kappas