imap-processing 0.18.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 (104) 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_l1a_variable_attrs.yaml +301 -274
  4. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +28 -28
  5. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1044 -203
  6. imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
  7. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +12 -0
  8. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
  9. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
  10. imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +33 -4
  11. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +8 -91
  12. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +106 -16
  13. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
  14. imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
  15. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +85 -2
  16. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +12 -4
  18. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
  19. imap_processing/cli.py +95 -41
  20. imap_processing/codice/codice_l1a.py +131 -31
  21. imap_processing/codice/codice_l2.py +118 -10
  22. imap_processing/codice/constants.py +740 -595
  23. imap_processing/decom.py +1 -4
  24. imap_processing/ena_maps/ena_maps.py +32 -25
  25. imap_processing/ena_maps/utils/naming.py +8 -2
  26. imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
  27. imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
  28. imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
  29. imap_processing/glows/ancillary/imap_glows_pipeline_settings_20250923_v002.json +54 -0
  30. imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
  31. imap_processing/glows/l1b/glows_l1b.py +99 -9
  32. imap_processing/glows/l1b/glows_l1b_data.py +350 -38
  33. imap_processing/glows/l2/glows_l2.py +11 -0
  34. imap_processing/hi/hi_l1a.py +124 -3
  35. imap_processing/hi/hi_l1b.py +154 -71
  36. imap_processing/hi/hi_l2.py +84 -51
  37. imap_processing/hi/utils.py +153 -8
  38. imap_processing/hit/l0/constants.py +3 -0
  39. imap_processing/hit/l0/decom_hit.py +3 -6
  40. imap_processing/hit/l1a/hit_l1a.py +311 -21
  41. imap_processing/hit/l1b/hit_l1b.py +54 -126
  42. imap_processing/hit/l2/hit_l2.py +6 -6
  43. imap_processing/ialirt/calculate_ingest.py +219 -0
  44. imap_processing/ialirt/constants.py +12 -2
  45. imap_processing/ialirt/generate_coverage.py +15 -2
  46. imap_processing/ialirt/l0/ialirt_spice.py +5 -2
  47. imap_processing/ialirt/l0/parse_mag.py +293 -42
  48. imap_processing/ialirt/l0/process_hit.py +5 -3
  49. imap_processing/ialirt/l0/process_swapi.py +41 -25
  50. imap_processing/ialirt/process_ephemeris.py +70 -14
  51. imap_processing/idex/idex_l0.py +2 -2
  52. imap_processing/idex/idex_l1a.py +2 -3
  53. imap_processing/idex/idex_l1b.py +2 -3
  54. imap_processing/idex/idex_l2a.py +130 -4
  55. imap_processing/idex/idex_l2b.py +158 -143
  56. imap_processing/idex/idex_utils.py +1 -3
  57. imap_processing/lo/l0/lo_science.py +25 -24
  58. imap_processing/lo/l1b/lo_l1b.py +3 -3
  59. imap_processing/lo/l1c/lo_l1c.py +116 -50
  60. imap_processing/lo/l2/lo_l2.py +29 -29
  61. imap_processing/lo/lo_ancillary.py +55 -0
  62. imap_processing/mag/l1a/mag_l1a.py +1 -0
  63. imap_processing/mag/l1a/mag_l1a_data.py +26 -0
  64. imap_processing/mag/l1b/mag_l1b.py +3 -2
  65. imap_processing/mag/l1c/interpolation_methods.py +14 -15
  66. imap_processing/mag/l1c/mag_l1c.py +23 -6
  67. imap_processing/mag/l1d/mag_l1d.py +57 -14
  68. imap_processing/mag/l1d/mag_l1d_data.py +167 -30
  69. imap_processing/mag/l2/mag_l2_data.py +10 -2
  70. imap_processing/quality_flags.py +9 -1
  71. imap_processing/spice/geometry.py +76 -33
  72. imap_processing/spice/pointing_frame.py +0 -6
  73. imap_processing/spice/repoint.py +29 -2
  74. imap_processing/spice/spin.py +28 -8
  75. imap_processing/spice/time.py +12 -22
  76. imap_processing/swapi/l1/swapi_l1.py +10 -4
  77. imap_processing/swapi/l2/swapi_l2.py +15 -17
  78. imap_processing/swe/l1b/swe_l1b.py +1 -2
  79. imap_processing/ultra/constants.py +1 -24
  80. imap_processing/ultra/l0/ultra_utils.py +9 -11
  81. imap_processing/ultra/l1a/ultra_l1a.py +1 -2
  82. imap_processing/ultra/l1b/cullingmask.py +6 -3
  83. imap_processing/ultra/l1b/de.py +81 -23
  84. imap_processing/ultra/l1b/extendedspin.py +13 -10
  85. imap_processing/ultra/l1b/lookup_utils.py +281 -28
  86. imap_processing/ultra/l1b/quality_flag_filters.py +10 -1
  87. imap_processing/ultra/l1b/ultra_l1b_culling.py +161 -3
  88. imap_processing/ultra/l1b/ultra_l1b_extended.py +253 -47
  89. imap_processing/ultra/l1c/helio_pset.py +97 -24
  90. imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
  91. imap_processing/ultra/l1c/spacecraft_pset.py +83 -16
  92. imap_processing/ultra/l1c/ultra_l1c.py +6 -2
  93. imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
  94. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +385 -277
  95. imap_processing/ultra/l2/ultra_l2.py +0 -1
  96. imap_processing/ultra/utils/ultra_l1_utils.py +28 -3
  97. imap_processing/utils.py +3 -4
  98. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +2 -2
  99. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +102 -95
  100. imap_processing/idex/idex_l2c.py +0 -84
  101. imap_processing/spice/kernels.py +0 -187
  102. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/LICENSE +0 -0
  103. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
  104. {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/entry_points.txt +0 -0
@@ -17,8 +17,9 @@ Examples
17
17
  l1a_data, l1a_evt_data, l1b_evt_data = PacketParser(l0_file)
18
18
  l1b_data = idex_l1b(l1a_data)
19
19
  l1a_data = idex_l2a(l1b_data)
20
- l2b_data = idex_l2b(l2a_data, [evt_data])
21
- write_cdf(l2b_data)
20
+ l2b_and_l2c_datasets = idex_l2b(l2a_data, [evt_data])
21
+ write_cdf(l2b_and_l2c_datasets[0])
22
+ write_cdf(l2b_and_l2c_datasets[1])
22
23
  """
23
24
 
24
25
  import collections
@@ -29,9 +30,11 @@ from datetime import datetime, timedelta
29
30
  import numpy as np
30
31
  import xarray as xr
31
32
 
33
+ from imap_processing.ena_maps.ena_maps import SkyTilingType
32
34
  from imap_processing.ena_maps.utils.spatial_utils import AzElSkyGrid
33
35
  from imap_processing.idex.idex_constants import (
34
36
  FG_TO_KG,
37
+ IDEX_EVENT_REFERENCE_FRAME,
35
38
  IDEX_SPACING_DEG,
36
39
  SECONDS_IN_DAY,
37
40
  IDEXEvtAcquireCodes,
@@ -81,9 +84,12 @@ LAT_BINS_EDGES = SKY_GRID.el_bin_edges
81
84
 
82
85
  def idex_l2b(
83
86
  l2a_datasets: list[xr.Dataset], evt_datasets: list[xr.Dataset]
84
- ) -> xr.Dataset:
87
+ ) -> list[xr.Dataset]:
85
88
  """
86
- Will process IDEX l2a data to create l2b data products.
89
+ Will process IDEX l2a data to create l2b and l2c data products.
90
+
91
+ IDEX L2B processing creates L2b and L2c at the same time because L2c needs no
92
+ additional dependencies and is a natural extension of L2b processing.
87
93
 
88
94
  Parameters
89
95
  ----------
@@ -94,16 +100,18 @@ def idex_l2b(
94
100
 
95
101
  Returns
96
102
  -------
97
- l2b_dataset : xarray.Dataset
98
- The``xarray`` dataset containing the science data and supporting metadata.
103
+ list[xarray.Dataset]
104
+ The``xarray`` datasets containing the l2b and l2c science data and supporting
105
+ metadata.
99
106
  """
100
107
  logger.info(
101
- f"Running IDEX L2B processing on datasets: "
102
- f"{[ds.attrs['Logical_source'] for ds in l2a_datasets]}"
108
+ "Running IDEX L2B and L2C processing on L2a datasets. NOTE: L2C datasets are "
109
+ "processed at the same time as L2B datasets because L2C needs no additional "
110
+ "dependencies."
103
111
  )
104
-
105
112
  # create the attribute manager for this data level
106
- idex_attrs = get_idex_attrs("l2b")
113
+ idex_l2b_attrs = get_idex_attrs("l2b")
114
+ idex_l2c_attrs = get_idex_attrs("l2c")
107
115
  evt_dataset = xr.concat(evt_datasets, dim="epoch")
108
116
 
109
117
  # Concat all the l2a datasets together
@@ -133,125 +141,138 @@ def idex_l2b(
133
141
  daily_on_percentage,
134
142
  )
135
143
  # Create l2b Dataset
136
- charge_bins = np.arange(len(CHARGE_BIN_EDGES) - 1)
137
- mass_bins = np.arange(len(CHARGE_BIN_EDGES) - 1)
138
- spin_phase_bins = np.arange(len(SPIN_PHASE_BIN_EDGES) - 1)
144
+ charge_bin_means = np.sqrt(CHARGE_BIN_EDGES[:-1] * CHARGE_BIN_EDGES[1:])
145
+ mass_bin_means = np.sqrt(MASS_BIN_EDGES[:-1] * MASS_BIN_EDGES[1:])
146
+ spin_phase_means = (SPIN_PHASE_BIN_EDGES[:-1] + SPIN_PHASE_BIN_EDGES[1:]) / 2
147
+
148
+ # Define xarrays that are shared between l2b and l2c
139
149
  epoch = xr.DataArray(
140
150
  name="epoch",
141
151
  data=daily_epoch,
142
152
  dims="epoch",
143
- attrs=idex_attrs.get_variable_attributes("epoch", check_schema=False),
153
+ attrs=idex_l2b_attrs.get_variable_attributes("epoch", check_schema=False),
144
154
  )
145
- data_vars = {
155
+
156
+ common_vars = {
146
157
  "impact_day_of_year": xr.DataArray(
147
158
  name="impact_day_of_year",
148
159
  data=epoch_doy_unique,
149
160
  dims="epoch",
150
- attrs=idex_attrs.get_variable_attributes("impact_day_of_year"),
151
- ),
152
- "rate_calculation_quality_flags": xr.DataArray(
153
- name="rate_calculation_quality_flags",
154
- data=rate_quality_flags,
155
- dims="epoch",
156
- attrs=idex_attrs.get_variable_attributes("rate_calculation_quality_flags"),
161
+ attrs=idex_l2b_attrs.get_variable_attributes("impact_day_of_year"),
157
162
  ),
158
163
  "charge_labels": xr.DataArray(
159
164
  name="impact_charge_labels",
160
- data=charge_bins.astype(str),
165
+ data=charge_bin_means.astype(str),
161
166
  dims="impact_charge",
162
- attrs=idex_attrs.get_variable_attributes(
167
+ attrs=idex_l2b_attrs.get_variable_attributes(
163
168
  "charge_labels", check_schema=False
164
169
  ),
165
170
  ),
166
- "spin_phase_labels": xr.DataArray(
167
- name="spin_phase_labels",
168
- data=spin_phase_bins.astype(str),
169
- dims="spin_phase",
170
- attrs=idex_attrs.get_variable_attributes(
171
- "spin_phase_labels", check_schema=False
172
- ),
173
- ),
174
171
  "mass_labels": xr.DataArray(
175
172
  name="mass_labels",
176
- data=mass_bins.astype(str),
173
+ data=mass_bin_means.astype(str),
177
174
  dims="mass",
178
- attrs=idex_attrs.get_variable_attributes("mass_labels", check_schema=False),
179
- ),
180
- "rectangular_lon_pixel_label": xr.DataArray(
181
- name="rectangular_lon_pixel_label",
182
- data=SKY_GRID.az_bin_midpoints.astype(str),
183
- dims="rectangular_lon_pixel",
184
- attrs=idex_attrs.get_variable_attributes(
185
- "rectangular_lon_pixel_label", check_schema=False
186
- ),
187
- ),
188
- "rectangular_lat_pixel_label": xr.DataArray(
189
- name="rectangular_lat_pixel_label",
190
- data=SKY_GRID.el_bin_midpoints.astype(str),
191
- dims="rectangular_lat_pixel",
192
- attrs=idex_attrs.get_variable_attributes(
193
- "rectangular_lat_pixel_label", check_schema=False
175
+ attrs=idex_l2b_attrs.get_variable_attributes(
176
+ "mass_labels", check_schema=False
194
177
  ),
195
178
  ),
196
179
  "impact_charge": xr.DataArray(
197
180
  name="impact_charge",
198
- data=charge_bins,
181
+ data=charge_bin_means,
199
182
  dims="impact_charge",
200
- attrs=idex_attrs.get_variable_attributes(
183
+ attrs=idex_l2b_attrs.get_variable_attributes(
201
184
  "impact_charge", check_schema=False
202
185
  ),
203
186
  ),
204
187
  "mass": xr.DataArray(
205
188
  name="mass",
206
- data=mass_bins,
189
+ data=mass_bin_means,
207
190
  dims="mass",
208
- attrs=idex_attrs.get_variable_attributes("mass", check_schema=False),
191
+ attrs=idex_l2b_attrs.get_variable_attributes("mass", check_schema=False),
209
192
  ),
193
+ }
194
+ l2b_vars = common_vars | {
210
195
  "spin_phase": xr.DataArray(
211
196
  name="spin_phase",
212
- data=spin_phase_bins,
197
+ data=spin_phase_means,
213
198
  dims="spin_phase",
214
- attrs=idex_attrs.get_variable_attributes("spin_phase", check_schema=False),
199
+ attrs=idex_l2b_attrs.get_variable_attributes(
200
+ "spin_phase", check_schema=False
201
+ ),
215
202
  ),
216
- "rectangular_lon_pixel": xr.DataArray(
217
- name="rectangular_lon_pixel",
218
- data=SKY_GRID.az_bin_midpoints,
219
- dims="rectangular_lon_pixel",
220
- attrs=idex_attrs.get_variable_attributes(
221
- "rectangular_lon_pixel", check_schema=False
203
+ "spin_phase_labels": xr.DataArray(
204
+ name="spin_phase_labels",
205
+ data=spin_phase_means.astype(str),
206
+ dims="spin_phase",
207
+ attrs=idex_l2b_attrs.get_variable_attributes(
208
+ "spin_phase_labels", check_schema=False
222
209
  ),
223
210
  ),
224
- "rectangular_lat_pixel": xr.DataArray(
225
- name="rectangular_lat_pixel",
226
- data=SKY_GRID.el_bin_midpoints,
227
- dims="rectangular_lat_pixel",
228
- attrs=idex_attrs.get_variable_attributes(
229
- "rectangular_lat_pixel", check_schema=False
211
+ "rate_calculation_quality_flags": xr.DataArray(
212
+ name="rate_calculation_quality_flags",
213
+ data=rate_quality_flags,
214
+ dims="epoch",
215
+ attrs=idex_l2b_attrs.get_variable_attributes(
216
+ "rate_calculation_quality_flags"
230
217
  ),
231
218
  ),
232
219
  "counts_by_charge": xr.DataArray(
233
220
  name="counts_by_charge",
234
221
  data=counts_by_charge.astype(np.int64),
235
222
  dims=("epoch", "impact_charge", "spin_phase"),
236
- attrs=idex_attrs.get_variable_attributes("counts_by_charge"),
223
+ attrs=idex_l2b_attrs.get_variable_attributes("counts_by_charge"),
237
224
  ),
238
225
  "counts_by_mass": xr.DataArray(
239
226
  name="counts_by_mass",
240
227
  data=counts_by_mass.astype(np.int64),
241
228
  dims=("epoch", "mass", "spin_phase"),
242
- attrs=idex_attrs.get_variable_attributes("counts_by_mass"),
229
+ attrs=idex_l2b_attrs.get_variable_attributes("counts_by_mass"),
243
230
  ),
244
231
  "rate_by_charge": xr.DataArray(
245
232
  name="rate_by_charge",
246
233
  data=rate_by_charge,
247
234
  dims=("epoch", "impact_charge", "spin_phase"),
248
- attrs=idex_attrs.get_variable_attributes("rate_by_charge"),
235
+ attrs=idex_l2b_attrs.get_variable_attributes("rate_by_charge"),
249
236
  ),
250
237
  "rate_by_mass": xr.DataArray(
251
238
  name="rate_by_mass",
252
239
  data=rate_by_mass,
253
240
  dims=("epoch", "mass", "spin_phase"),
254
- attrs=idex_attrs.get_variable_attributes("rate_by_mass"),
241
+ attrs=idex_l2b_attrs.get_variable_attributes("rate_by_mass"),
242
+ ),
243
+ }
244
+ l2c_vars = common_vars | {
245
+ "rectangular_lon_pixel_label": xr.DataArray(
246
+ name="rectangular_lon_pixel_label",
247
+ data=SKY_GRID.az_bin_midpoints.astype(str),
248
+ dims="rectangular_lon_pixel",
249
+ attrs=idex_l2c_attrs.get_variable_attributes(
250
+ "rectangular_lon_pixel_label", check_schema=False
251
+ ),
252
+ ),
253
+ "rectangular_lat_pixel_label": xr.DataArray(
254
+ name="rectangular_lat_pixel_label",
255
+ data=SKY_GRID.el_bin_midpoints.astype(str),
256
+ dims="rectangular_lat_pixel",
257
+ attrs=idex_l2c_attrs.get_variable_attributes(
258
+ "rectangular_lat_pixel_label", check_schema=False
259
+ ),
260
+ ),
261
+ "rectangular_lon_pixel": xr.DataArray(
262
+ name="rectangular_lon_pixel",
263
+ data=SKY_GRID.az_bin_midpoints,
264
+ dims="rectangular_lon_pixel",
265
+ attrs=idex_l2c_attrs.get_variable_attributes(
266
+ "rectangular_lon_pixel", check_schema=False
267
+ ),
268
+ ),
269
+ "rectangular_lat_pixel": xr.DataArray(
270
+ name="rectangular_lat_pixel",
271
+ data=SKY_GRID.el_bin_midpoints,
272
+ dims="rectangular_lat_pixel",
273
+ attrs=idex_l2c_attrs.get_variable_attributes(
274
+ "rectangular_lat_pixel", check_schema=False
275
+ ),
255
276
  ),
256
277
  "counts_by_charge_map": xr.DataArray(
257
278
  name="counts_by_charge_map",
@@ -262,7 +283,7 @@ def idex_l2b(
262
283
  "rectangular_lon_pixel",
263
284
  "rectangular_lat_pixel",
264
285
  ),
265
- attrs=idex_attrs.get_variable_attributes("counts_by_charge_map"),
286
+ attrs=idex_l2c_attrs.get_variable_attributes("counts_by_charge_map"),
266
287
  ),
267
288
  "counts_by_mass_map": xr.DataArray(
268
289
  name="counts_by_mass_map",
@@ -273,7 +294,7 @@ def idex_l2b(
273
294
  "rectangular_lon_pixel",
274
295
  "rectangular_lat_pixel",
275
296
  ),
276
- attrs=idex_attrs.get_variable_attributes("counts_by_mass_map"),
297
+ attrs=idex_l2c_attrs.get_variable_attributes("counts_by_mass_map"),
277
298
  ),
278
299
  "rate_by_charge_map": xr.DataArray(
279
300
  name="rate_by_charge_map",
@@ -284,7 +305,7 @@ def idex_l2b(
284
305
  "rectangular_lon_pixel",
285
306
  "rectangular_lat_pixel",
286
307
  ),
287
- attrs=idex_attrs.get_variable_attributes("rate_by_charge_map"),
308
+ attrs=idex_l2c_attrs.get_variable_attributes("rate_by_charge_map"),
288
309
  ),
289
310
  "rate_by_mass_map": xr.DataArray(
290
311
  name="rate_by_mass_map",
@@ -295,18 +316,31 @@ def idex_l2b(
295
316
  "rectangular_lon_pixel",
296
317
  "rectangular_lat_pixel",
297
318
  ),
298
- attrs=idex_attrs.get_variable_attributes("rate_by_mass_map"),
319
+ attrs=idex_l2c_attrs.get_variable_attributes("rate_by_mass_map"),
299
320
  ),
300
321
  }
322
+
301
323
  l2b_dataset = xr.Dataset(
302
324
  coords={"epoch": epoch},
303
- data_vars=data_vars,
304
- attrs=idex_attrs.get_global_attributes("imap_idex_l2b_sci"),
325
+ data_vars=l2b_vars,
326
+ attrs=idex_l2b_attrs.get_global_attributes("imap_idex_l2b_sci"),
327
+ )
328
+ l2c_dataset = xr.Dataset(
329
+ coords={"epoch": epoch},
330
+ data_vars=l2c_vars,
305
331
  )
332
+ # Add map attributes
333
+ map_attrs = {
334
+ "sky_tiling_type": SkyTilingType.RECTANGULAR.value,
335
+ "Spacing_degrees": str(IDEX_SPACING_DEG),
336
+ "Spice_reference_frame": IDEX_EVENT_REFERENCE_FRAME.name,
337
+ } | idex_l2c_attrs.get_global_attributes("imap_idex_l2c_sci-rectangular")
306
338
 
307
- logger.info("IDEX L2B science data processing completed.")
339
+ l2c_dataset.attrs.update(map_attrs)
308
340
 
309
- return l2b_dataset
341
+ logger.info("IDEX L2B and L2C science data processing completed.")
342
+
343
+ return [l2b_dataset, l2c_dataset]
310
344
 
311
345
 
312
346
  def compute_counts_by_charge_and_mass(
@@ -329,37 +363,11 @@ def compute_counts_by_charge_and_mass(
329
363
  dataset, Two 4D arrays containing counts by charge or mass, and by lon and lat
330
364
  for each dataset, and a 1D array of daily epoch values.
331
365
  """
332
- # Initialize arrays to hold counts.
333
- # There should be 4 spin phase bins, 10 charge bins, and 10 mass bins.
334
- # The first bin for charge and mass is for values below the first bin edge.
335
- counts_by_charge = np.zeros(
336
- (
337
- len(epoch_doy_unique),
338
- len(CHARGE_BIN_EDGES) - 1,
339
- len(SPIN_PHASE_BIN_EDGES) - 1,
340
- ),
341
- )
342
- counts_by_mass = np.zeros(
343
- (len(epoch_doy_unique), len(MASS_BIN_EDGES) - 1, len(SPIN_PHASE_BIN_EDGES) - 1),
344
- )
345
- # Initialize arrays to hold count maps. Each map is a 3 or 4D array with shape
346
- # (epoch, 10 [charge or mass], 60 [longitude bins], 30 [latitude bins]).
347
- counts_by_charge_map = np.zeros(
348
- (
349
- len(epoch_doy_unique),
350
- len(CHARGE_BIN_EDGES) - 1,
351
- len(LON_BINS_EDGES) - 1,
352
- len(LAT_BINS_EDGES) - 1,
353
- ),
354
- )
355
- counts_by_mass_map = np.zeros(
356
- (
357
- len(epoch_doy_unique),
358
- len(MASS_BIN_EDGES) - 1,
359
- len(LON_BINS_EDGES) - 1,
360
- len(LAT_BINS_EDGES) - 1,
361
- ),
362
- )
366
+ # Initialize lists to hold counts.
367
+ counts_by_charge = []
368
+ counts_by_mass = []
369
+ counts_by_charge_map = []
370
+ counts_by_mass_map = []
363
371
  daily_epoch = np.zeros(len(epoch_doy_unique), dtype=np.float64)
364
372
  for i in range(len(epoch_doy_unique)):
365
373
  doy = epoch_doy_unique[i]
@@ -379,43 +387,44 @@ def compute_counts_by_charge_and_mass(
379
387
  latitude = l2a_dataset["latitude"].data[current_day_indices]
380
388
  # Convert units
381
389
  mass_vals = FG_TO_KG * np.atleast_1d(mass_vals)
382
- # Bin masses
383
- binned_mass = np.asarray(np.digitize(mass_vals, bins=MASS_BIN_EDGES))
384
- # Bin charges
385
- binned_charge = np.asarray(np.digitize(charge_vals, bins=CHARGE_BIN_EDGES))
386
390
  # Bin spin phases
387
391
  binned_spin_phase = bin_spin_phases(spin_phase_angles)
388
- # Bin longitude and latitude into the rectangular grid.
389
- binned_longitude = np.asarray(np.digitize(longitude, bins=LON_BINS_EDGES))
392
+ # Clip arrays to ensure that the values are within the valid range of bins.
390
393
  # Latitude should be binned with the right edge included. 90 is a valid latitude
391
- binned_latitude = np.asarray(np.digitize(latitude, bins=LAT_BINS_EDGES))
392
- # Clip latitude value above the right edge to be in the last bin
393
- binned_latitude = np.clip(binned_latitude, 1, len(LAT_BINS_EDGES) - 1)
394
- # If the values in the array are beyond the bounds of bins, 0 or len(bins) it is
395
- # returned as such. In this case, the desired result is to place the values
396
- # beyond the first or last bin into the first or last bin, respectively.
397
- binned_charge = np.clip(binned_charge, 1, len(CHARGE_BIN_EDGES) - 1)
398
- binned_mass = np.clip(binned_mass, 1, len(MASS_BIN_EDGES) - 1)
399
-
400
- # Count dust events for each spin phase, mass bin, charge bin, and bin into
401
- # a rectangular grid
402
- for mass_bin, charge_bin, spin_phase_bin, lon_bin, lat_bin in zip(
403
- binned_mass,
404
- binned_charge,
405
- binned_spin_phase,
406
- binned_longitude,
407
- binned_latitude,
408
- ):
409
- counts_by_mass[i, mass_bin - 1, spin_phase_bin] += 1
410
- counts_by_charge[i, charge_bin - 1, spin_phase_bin] += 1
411
- counts_by_mass_map[i, mass_bin - 1, lon_bin - 1, lat_bin - 1] += 1
412
- counts_by_charge_map[i, charge_bin - 1, lon_bin - 1, lat_bin - 1] += 1
394
+ latitude = np.clip(latitude, -90, 90)
395
+ mass_vals = np.clip(mass_vals, MASS_BIN_EDGES[0], MASS_BIN_EDGES[-1])
396
+ charge_vals = np.clip(charge_vals, CHARGE_BIN_EDGES[0], CHARGE_BIN_EDGES[-1])
397
+
398
+ counts_by_mass.append(
399
+ np.histogramdd(
400
+ np.column_stack([mass_vals, binned_spin_phase]),
401
+ bins=[MASS_BIN_EDGES, np.arange(5)],
402
+ )[0]
403
+ )
404
+ counts_by_charge.append(
405
+ np.histogramdd(
406
+ np.column_stack([charge_vals, binned_spin_phase]),
407
+ bins=[CHARGE_BIN_EDGES, np.arange(5)],
408
+ )[0]
409
+ )
410
+ counts_by_mass_map.append(
411
+ np.histogramdd(
412
+ np.column_stack([mass_vals, longitude, latitude]),
413
+ bins=[MASS_BIN_EDGES, LON_BINS_EDGES, LAT_BINS_EDGES],
414
+ )[0]
415
+ )
416
+ counts_by_charge_map.append(
417
+ np.histogramdd(
418
+ np.column_stack([charge_vals, longitude, latitude]),
419
+ bins=[CHARGE_BIN_EDGES, LON_BINS_EDGES, LAT_BINS_EDGES],
420
+ )[0]
421
+ )
413
422
 
414
423
  return (
415
- counts_by_charge,
416
- counts_by_mass,
417
- counts_by_charge_map,
418
- counts_by_mass_map,
424
+ np.stack(counts_by_charge),
425
+ np.stack(counts_by_mass),
426
+ np.stack(counts_by_charge_map),
427
+ np.stack(counts_by_mass_map),
419
428
  daily_epoch,
420
429
  )
421
430
 
@@ -598,7 +607,7 @@ def get_science_acquisition_timestamps(
598
607
  epochs = evt_dataset["epoch"][sc_indices].data
599
608
  # Now the state change values and check if it is either a science
600
609
  # acquisition start or science acquisition stop event.
601
- for v1, v2, epoch in zip(val1, val2, epochs):
610
+ for v1, v2, epoch in zip(val1, val2, epochs, strict=False):
602
611
  # An "acquire" start will have val1=ACQSETUP and val2=ACQ
603
612
  # An "acquire" stop will have val1=ACQ and val2=CHILL
604
613
  if (v1, v2) == (IDEXEvtAcquireCodes.ACQSETUP, IDEXEvtAcquireCodes.ACQ):
@@ -637,6 +646,12 @@ def get_science_acquisition_on_percentage(evt_dataset: xr.Dataset) -> dict:
637
646
  """
638
647
  # Get science acquisition start and stop times
639
648
  evt_logs, evt_time, evt_values = get_science_acquisition_timestamps(evt_dataset)
649
+ if len(evt_time) == 0:
650
+ logger.warning(
651
+ "No science acquisition events found in event dataset. Returning empty "
652
+ "uptime percentages. All rate variables will be set to -1."
653
+ )
654
+ return {}
640
655
  # Track total and 'on' durations per day
641
656
  daily_totals: collections.defaultdict = defaultdict(timedelta)
642
657
  daily_on: collections.defaultdict = defaultdict(timedelta)
@@ -1,7 +1,5 @@
1
1
  """Contains helper functions to support IDEX processing."""
2
2
 
3
- from typing import Optional
4
-
5
3
  import xarray as xr
6
4
 
7
5
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
@@ -31,7 +29,7 @@ def setup_dataset(
31
29
  dataset: xr.Dataset,
32
30
  match_strings: list,
33
31
  idex_attrs: ImapCdfAttributes,
34
- data_vars: Optional[dict] = None,
32
+ data_vars: dict | None = None,
35
33
  ) -> xr.Dataset:
36
34
  """
37
35
  Initialize a dataset and copy over any dataArrays.
@@ -37,28 +37,28 @@ HistPacking = namedtuple(
37
37
 
38
38
  HIST_DATA_META = {
39
39
  # field: bit_length, section_length, shape
40
- "start_a": HistPacking(12, 504, (6, 7)),
41
- "start_c": HistPacking(12, 504, (6, 7)),
42
- "stop_b0": HistPacking(12, 504, (6, 7)),
43
- "stop_b3": HistPacking(12, 504, (6, 7)),
44
- "tof0_count": HistPacking(8, 336, (6, 7)),
45
- "tof1_count": HistPacking(8, 336, (6, 7)),
46
- "tof2_count": HistPacking(8, 336, (6, 7)),
47
- "tof3_count": HistPacking(8, 336, (6, 7)),
48
- "tof0_tof1": HistPacking(8, 3360, (60, 7)),
49
- "tof0_tof2": HistPacking(8, 3360, (60, 7)),
50
- "tof1_tof2": HistPacking(8, 3360, (60, 7)),
51
- "silver": HistPacking(8, 3360, (60, 7)),
52
- "disc_tof0": HistPacking(8, 336, (6, 7)),
53
- "disc_tof1": HistPacking(8, 336, (6, 7)),
54
- "disc_tof2": HistPacking(8, 336, (6, 7)),
55
- "disc_tof3": HistPacking(8, 336, (6, 7)),
56
- "pos0": HistPacking(12, 504, (6, 7)),
57
- "pos1": HistPacking(12, 504, (6, 7)),
58
- "pos2": HistPacking(12, 504, (6, 7)),
59
- "pos3": HistPacking(12, 504, (6, 7)),
60
- "hydrogen": HistPacking(8, 3360, (60, 7)),
61
- "oxygen": HistPacking(8, 3360, (60, 7)),
40
+ "start_a": HistPacking(12, 504, (7, 6)),
41
+ "start_c": HistPacking(12, 504, (7, 6)),
42
+ "stop_b0": HistPacking(12, 504, (7, 6)),
43
+ "stop_b3": HistPacking(12, 504, (7, 6)),
44
+ "tof0_count": HistPacking(8, 336, (7, 6)),
45
+ "tof1_count": HistPacking(8, 336, (7, 6)),
46
+ "tof2_count": HistPacking(8, 336, (7, 6)),
47
+ "tof3_count": HistPacking(8, 336, (7, 6)),
48
+ "tof0_tof1": HistPacking(8, 3360, (7, 60)),
49
+ "tof0_tof2": HistPacking(8, 3360, (7, 60)),
50
+ "tof1_tof2": HistPacking(8, 3360, (7, 60)),
51
+ "silver": HistPacking(8, 3360, (7, 60)),
52
+ "disc_tof0": HistPacking(8, 336, (7, 6)),
53
+ "disc_tof1": HistPacking(8, 336, (7, 6)),
54
+ "disc_tof2": HistPacking(8, 336, (7, 6)),
55
+ "disc_tof3": HistPacking(8, 336, (7, 6)),
56
+ "pos0": HistPacking(12, 504, (7, 6)),
57
+ "pos1": HistPacking(12, 504, (7, 6)),
58
+ "pos2": HistPacking(12, 504, (7, 6)),
59
+ "pos3": HistPacking(12, 504, (7, 6)),
60
+ "hydrogen": HistPacking(8, 3360, (7, 60)),
61
+ "oxygen": HistPacking(8, 3360, (7, 60)),
62
62
  }
63
63
 
64
64
 
@@ -399,7 +399,7 @@ def combine_segmented_packets(dataset: xr.Dataset) -> xr.Dataset:
399
399
  # Combine the segmented packets into a single binary string
400
400
  dataset["events"] = [
401
401
  "".join(dataset["data"].values[start : end + 1])
402
- for start, end in zip(seg_starts, seg_ends)
402
+ for start, end in zip(seg_starts, seg_ends, strict=False)
403
403
  ]
404
404
 
405
405
  # drop any group of segmented packets that aren't sequential
@@ -441,7 +441,8 @@ def find_valid_groups(
441
441
  """
442
442
  # Check if the sequence counters from the CCSDS header are sequential
443
443
  grouped_seq_ctrs = [
444
- np.array(seq_ctrs[start : end + 1]) for start, end in zip(seg_starts, seg_ends)
444
+ np.array(seq_ctrs[start : end + 1])
445
+ for start, end in zip(seg_starts, seg_ends, strict=False)
445
446
  ]
446
447
  valid_groups = [is_sequential(seq_ctrs) for seq_ctrs in grouped_seq_ctrs]
447
448
  return valid_groups
@@ -3,7 +3,7 @@
3
3
  import logging
4
4
  from dataclasses import Field
5
5
  from pathlib import Path
6
- from typing import Any, Union
6
+ from typing import Any
7
7
 
8
8
  import numpy as np
9
9
  import xarray as xr
@@ -204,7 +204,7 @@ def get_avg_spin_durations_per_cycle(
204
204
  return avg_spin_durations_per_cycle
205
205
 
206
206
 
207
- def get_spin_angle(l1a_de: xr.Dataset) -> Union[np.ndarray[np.float64], Any]:
207
+ def get_spin_angle(l1a_de: xr.Dataset) -> np.ndarray[np.float64] | Any:
208
208
  """
209
209
  Get the spin angle (0 - 360 degrees) for each DE.
210
210
 
@@ -587,7 +587,7 @@ def convert_tofs_to_eu(
587
587
  tof_conversions = [TOF0_CONV, TOF1_CONV, TOF2_CONV, TOF3_CONV]
588
588
 
589
589
  # Loop through the TOF fields and convert them to engineering units
590
- for tof, conv in zip(tof_fields, tof_conversions):
590
+ for tof, conv in zip(tof_fields, tof_conversions, strict=False):
591
591
  # Get the fill value for the L1A and L1B TOF
592
592
  fillval_1a = attr_mgr_l1a.get_variable_attributes(tof)["FILLVAL"]
593
593
  fillval_1b = attr_mgr_l1b.get_variable_attributes(tof)["FILLVAL"]