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
@@ -54,6 +54,8 @@ class CoDICEL1aPipeline:
54
54
 
55
55
  Methods
56
56
  -------
57
+ apply_despinning()
58
+ Apply the despinning algorithm to lo- angular and priority products.
57
59
  decompress_data(science_values)
58
60
  Perform decompression on the data.
59
61
  define_coordinates()
@@ -87,6 +89,73 @@ class CoDICEL1aPipeline:
87
89
  self.plan_step = plan_step
88
90
  self.view_id = view_id
89
91
 
92
+ def apply_despinning(self) -> None:
93
+ """
94
+ Apply the despinning algorithm to lo- angular and priority products.
95
+
96
+ This only applies to CoDICE-Lo angular and priority data products. See
97
+ sections 9.3.4 and 9.3.5 of the algorithm document for more details.
98
+ """
99
+ # Determine the appropriate dimensions for the despun data
100
+ num_energies = self.config["dims"]["esa_step"]
101
+ num_spin_sectors = self.config["dims"]["spin_sector"]
102
+ num_spins = num_spin_sectors * 2
103
+ num_counters = self.config["num_counters"]
104
+ num_positions = self.config["dims"].get(
105
+ "inst_az"
106
+ ) # Defaults to None if not present
107
+
108
+ # The dimensions are dependent on the specific data product
109
+ if "angular" in self.config["dataset_name"]:
110
+ despun_dims: tuple[int, ...] = (
111
+ num_energies,
112
+ num_positions,
113
+ num_spins,
114
+ num_counters,
115
+ )
116
+ elif "priority" in self.config["dataset_name"]:
117
+ despun_dims = (num_energies, num_spins, num_counters)
118
+
119
+ # Placeholder for finalized despun data
120
+ self.data: list[np.ndarray] # Needed to appease mypy
121
+ despun_data = [np.zeros(despun_dims) for _ in range(len(self.data))]
122
+
123
+ # Iterate over the energy and spin sector indices, and determine the
124
+ # appropriate pixel orientation. The combination of the pixel
125
+ # orientation and the azimuth determine which spin sector the data
126
+ # gets stored in.
127
+ # TODO: All these nested for-loops are bad. Try to find a better
128
+ # solution. See GitHub issue #2136.
129
+ for i, epoch_data in enumerate(self.data):
130
+ for energy_index in range(num_energies):
131
+ pixel_orientation = constants.PIXEL_ORIENTATIONS[energy_index]
132
+ for spin_sector_index in range(num_spin_sectors):
133
+ for azimuth_index in range(num_spins):
134
+ if pixel_orientation == "A" and azimuth_index < 12:
135
+ despun_spin_sector = spin_sector_index
136
+ elif pixel_orientation == "A" and azimuth_index >= 12:
137
+ despun_spin_sector = spin_sector_index + 12
138
+ elif pixel_orientation == "B" and azimuth_index < 12:
139
+ despun_spin_sector = spin_sector_index + 12
140
+ elif pixel_orientation == "B" and azimuth_index >= 12:
141
+ despun_spin_sector = spin_sector_index
142
+
143
+ if "angular" in self.config["dataset_name"]:
144
+ spin_data = epoch_data[
145
+ energy_index, :, spin_sector_index, :
146
+ ] # (5, 4)
147
+ despun_data[i][energy_index, :, despun_spin_sector, :] = (
148
+ spin_data
149
+ )
150
+ elif "priority" in self.config["dataset_name"]:
151
+ spin_data = epoch_data[energy_index, spin_sector_index, :]
152
+ despun_data[i][energy_index, despun_spin_sector, :] = (
153
+ spin_data
154
+ )
155
+
156
+ # Replace original data
157
+ self.data = despun_data
158
+
90
159
  def decompress_data(self, science_values: list[NDArray[str]] | list[str]) -> None:
91
160
  """
92
161
  Perform decompression on the data.
@@ -122,7 +191,7 @@ class CoDICEL1aPipeline:
122
191
 
123
192
  else:
124
193
  for packet_data, byte_count in zip(
125
- science_values, self.dataset.byte_count.data
194
+ science_values, self.dataset.byte_count.data, strict=False
126
195
  ):
127
196
  # Convert from numpy array to byte object
128
197
  values = packet_data[()]
@@ -134,11 +203,14 @@ class CoDICEL1aPipeline:
134
203
  decompressed_values = decompress(values, compression_algorithm)
135
204
  self.raw_data.append(decompressed_values)
136
205
 
137
- def define_coordinates(self) -> None:
206
+ def define_coordinates(self) -> None: # noqa: PLR0912 (too many branches)
138
207
  """
139
208
  Create ``xr.DataArrays`` for the coords needed in the final dataset.
140
209
 
141
210
  The coordinates for the dataset depend on the data product being made.
211
+
212
+ # TODO: Split this function up or simplify it to avoid too many branches
213
+ # error.
142
214
  """
143
215
  self.coords = {}
144
216
 
@@ -169,13 +241,18 @@ class CoDICEL1aPipeline:
169
241
  if name in [
170
242
  "esa_step",
171
243
  "inst_az",
172
- "spin_sector",
173
244
  "spin_sector_pairs",
174
245
  "spin_sector_index",
175
246
  "ssd_index",
176
247
  ]:
177
248
  values = np.arange(self.config["dims"][name])
178
249
  dims = [name]
250
+ elif name == "spin_sector":
251
+ if self.config["dataset_name"] in constants.REQUIRES_DESPINNING:
252
+ values = np.arange(24)
253
+ else:
254
+ values = np.arange(self.config["dims"][name])
255
+ dims = [name]
179
256
  elif name == "spin_sector_pairs_label":
180
257
  values = np.array(
181
258
  [
@@ -197,7 +274,6 @@ class CoDICEL1aPipeline:
197
274
  values = np.arange(self.config["dims"]["inst_az"]).astype(str)
198
275
  dims = ["inst_az"]
199
276
  elif name in [
200
- "spin_sector_label",
201
277
  "esa_step_label",
202
278
  "spin_sector_index_label",
203
279
  "ssd_index_label",
@@ -205,6 +281,13 @@ class CoDICEL1aPipeline:
205
281
  key = name.removesuffix("_label")
206
282
  values = np.arange(self.config["dims"][key]).astype(str)
207
283
  dims = [key]
284
+ elif name == "spin_sector_label":
285
+ key = name.removesuffix("_label")
286
+ dims = [key]
287
+ if self.config["dataset_name"] in constants.REQUIRES_DESPINNING:
288
+ values = np.arange(24).astype(str)
289
+ else:
290
+ values = np.arange(self.config["dims"][key]).astype(str)
208
291
 
209
292
  coord = xr.DataArray(
210
293
  values,
@@ -243,7 +326,7 @@ class CoDICEL1aPipeline:
243
326
  # the num_counters dimension to isolate the data for each counter so
244
327
  # each counter's data can be placed in a separate CDF data variable.
245
328
  for counter, variable_name in zip(
246
- range(all_data.shape[-1]), self.config["variable_names"]
329
+ range(all_data.shape[-1]), self.config["variable_names"], strict=False
247
330
  ):
248
331
  # Extract the counter data
249
332
  counter_data = all_data[..., counter]
@@ -262,7 +345,7 @@ class CoDICEL1aPipeline:
262
345
  # energy dimension
263
346
  # TODO: This bit of code may no longer be needed once I can figure
264
347
  # out how to run hi-sectored product through the
265
- # create_binned_dataset function
348
+ # create_binned_dataset function. See GitHub issue #2137.
266
349
  if self.config["dataset_name"] == "imap_codice_l1a_hi-sectored":
267
350
  dims = [
268
351
  f"energy_{variable_name}" if item == "esa_step" else item
@@ -284,7 +367,7 @@ class CoDICEL1aPipeline:
284
367
  # longer need the "esa_step" coordinate
285
368
  # TODO: This bit of code may no longer be needed once I can figure
286
369
  # out how to run hi-sectored product through the
287
- # create_binned_dataset function
370
+ # create_binned_dataset function. See GitHub issue #2137.
288
371
  if self.config["dataset_name"] == "imap_codice_l1a_hi-sectored":
289
372
  for species in self.config["energy_table"]:
290
373
  dataset = self.define_energy_bins(dataset, species)
@@ -313,7 +396,7 @@ class CoDICEL1aPipeline:
313
396
  ``xarray`` dataset for the data product, with added energy variables.
314
397
  """
315
398
  energy_bin_name = f"energy_{species}"
316
- centers, deltas = self.get_hi_energy_table_data(
399
+ centers, deltas_minus, deltas_plus = self.get_hi_energy_table_data(
317
400
  energy_bin_name.split("energy_")[-1]
318
401
  )
319
402
 
@@ -326,11 +409,19 @@ class CoDICEL1aPipeline:
326
409
  check_schema=False,
327
410
  ),
328
411
  )
329
- dataset[f"{energy_bin_name}_delta"] = xr.DataArray(
330
- deltas,
331
- dims=[f"{energy_bin_name}_delta"],
412
+ dataset[f"{energy_bin_name}_minus"] = xr.DataArray(
413
+ deltas_minus,
414
+ dims=[f"{energy_bin_name}_minus"],
332
415
  attrs=self.cdf_attrs.get_variable_attributes(
333
- f"{self.config['dataset_name'].split('_')[-1]}-{energy_bin_name}_delta",
416
+ f"{self.config['dataset_name'].split('_')[-1]}-{energy_bin_name}_minus",
417
+ check_schema=False,
418
+ ),
419
+ )
420
+ dataset[f"{energy_bin_name}_plus"] = xr.DataArray(
421
+ deltas_plus,
422
+ dims=[f"{energy_bin_name}_plus"],
423
+ attrs=self.cdf_attrs.get_variable_attributes(
424
+ f"{self.config['dataset_name'].split('_')[-1]}-{energy_bin_name}_plus",
334
425
  check_schema=False,
335
426
  ),
336
427
  )
@@ -488,7 +579,7 @@ class CoDICEL1aPipeline:
488
579
 
489
580
  def get_hi_energy_table_data(
490
581
  self, species: str
491
- ) -> tuple[NDArray[float], NDArray[float]]:
582
+ ) -> tuple[NDArray[float], NDArray[float], NDArray[float]]:
492
583
  """
493
584
  Retrieve energy table data for CoDICE-Hi products.
494
585
 
@@ -506,22 +597,25 @@ class CoDICEL1aPipeline:
506
597
  -------
507
598
  centers : NDArray[float]
508
599
  An array whose values represent the centers of the energy bins.
509
- deltas : NDArray[float]
510
- An array whose values represent the deltas of the energy bins.
600
+ deltas_minus : NDArray[float]
601
+ An array whose values represent the minus deltas of the energy bins.
602
+ deltas_plus : NDArray[float]
603
+ An array whose values represent the plus deltas of the energy bins.
511
604
  """
512
605
  data_product = self.config["dataset_name"].split("-")[-1].upper()
513
- energy_table = getattr(constants, f"{data_product}_ENERGY_TABLE")[species]
514
-
515
- # Find the centers and deltas of the energy bins
516
- centers = np.array(
517
- [
518
- (energy_table[i] + energy_table[i + 1]) / 2
519
- for i in range(len(energy_table) - 1)
520
- ]
606
+ energy_table = np.array(
607
+ getattr(constants, f"{data_product}_ENERGY_TABLE")[species]
521
608
  )
522
- deltas = energy_table[1:] - centers
523
609
 
524
- return centers, deltas
610
+ # Find the geometric centers and deltas of the energy bins
611
+ # The delta minus is the difference between the center of the bin
612
+ # and the 'left edge' of the bin. The delta plus is the difference
613
+ # between the 'right edge' of the bin and the center of the bin
614
+ centers = np.sqrt(energy_table[:-1] * energy_table[1:])
615
+ deltas_minus = centers - energy_table[:-1]
616
+ deltas_plus = energy_table[1:] - centers
617
+
618
+ return centers, deltas_minus, deltas_plus
525
619
 
526
620
  def reshape_binned_data(self, dataset: xr.Dataset) -> dict[str, list]:
527
621
  """
@@ -624,6 +718,10 @@ class CoDICEL1aPipeline:
624
718
  )
625
719
  self.data.append(reshaped_packet_data)
626
720
 
721
+ # Apply despinning if necessary
722
+ if self.config["dataset_name"] in constants.REQUIRES_DESPINNING:
723
+ self.apply_despinning()
724
+
627
725
  # No longer need to keep the raw data around
628
726
  del self.raw_data
629
727
 
@@ -724,9 +822,6 @@ def group_ialirt_data(
724
822
 
725
823
  # Workaround to get this function working for both I-ALiRT spacecraft
726
824
  # data and CoDICE-specific I-ALiRT test data from Joey
727
- # TODO: Once CoDICE I-ALiRT processing is more established, we can probably
728
- # do away with processing the test data from Joey and just use the
729
- # I-ALiRT data that is constructed closer to what we expect in-flight.
730
825
  if hasattr(packets, "acquisition_time"):
731
826
  time_key = "acquisition_time"
732
827
  counter_key = "counter"
@@ -782,7 +877,7 @@ def create_binned_dataset(
782
877
  Xarray dataset containing the final processed dataset.
783
878
  """
784
879
  # TODO: hi-sectored data product should be processed similar to hi-omni,
785
- # so I should be able to use this method.
880
+ # so I should be able to use this method. See GitHub issue #2137.
786
881
 
787
882
  # Get the four "main" parameters for processing
788
883
  table_id, plan_id, plan_step, view_id = get_params(dataset)
@@ -803,7 +898,7 @@ def create_binned_dataset(
803
898
  attrs=pipeline.cdf_attrs.get_variable_attributes("epoch", check_schema=False),
804
899
  )
805
900
  # TODO: Figure out how to calculate epoch centers and deltas and store them
806
- # in variables here
901
+ # in variables here. See GitHub issue #1501.
807
902
  dataset = xr.Dataset(
808
903
  coords={"epoch": coord},
809
904
  attrs=pipeline.cdf_attrs.get_global_attributes(pipeline.config["dataset_name"]),
@@ -843,7 +938,7 @@ def create_binned_dataset(
843
938
  return dataset
844
939
 
845
940
 
846
- def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
941
+ def create_direct_event_dataset(apid: int, unpacked_dataset: xr.Dataset) -> xr.Dataset:
847
942
  """
848
943
  Create dataset for direct event data.
849
944
 
@@ -857,7 +952,7 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
857
952
  dictionary. Padding is added to any fields that have less than 10000 events.
858
953
 
859
954
  In order to process these data, we must take the decommed raw data, group
860
- the packets appropriately based on their `seq_flgs`, decompress the data,
955
+ the unpacked_dataset appropriately based on their `seq_flgs`, decompress the data,
861
956
  then arrange the data into CDF data variables for each priority and bit
862
957
  field. For example, P2_SpinAngle represents the spin angles for the 2nd
863
958
  priority data.
@@ -866,8 +961,8 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
866
961
  ----------
867
962
  apid : int
868
963
  The APID of the packet.
869
- packets : xarray.Dataset
870
- The packets to process.
964
+ unpacked_dataset : xarray.Dataset
965
+ The unpacked dataset to process.
871
966
 
872
967
  Returns
873
968
  -------
@@ -875,13 +970,13 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
875
970
  Xarray dataset containing the direct event data.
876
971
  """
877
972
  # Group and decompress the data
878
- grouped_data = group_data(packets)
973
+ grouped_data = group_data(unpacked_dataset)
879
974
  decompressed_data = [
880
975
  decompress(group, CoDICECompression.LOSSLESS) for group in grouped_data
881
976
  ]
882
977
 
883
978
  # Reshape the packet data into CDF-ready variables
884
- data = reshape_de_data(packets, decompressed_data, apid)
979
+ reshaped_de_data = reshape_de_data(unpacked_dataset, decompressed_data, apid)
885
980
 
886
981
  # Gather the CDF attributes
887
982
  cdf_attrs = ImapCdfAttributes()
@@ -891,11 +986,11 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
891
986
  # Determine the epochs to use in the dataset, which are the epochs whenever
892
987
  # there is a start of a segment and the priority is 0
893
988
  epoch_indices = np.where(
894
- ((packets.seq_flgs.data == 3) | (packets.seq_flgs.data == 1))
895
- & (packets.priority.data == 0)
989
+ ((unpacked_dataset.seq_flgs.data == 3) | (unpacked_dataset.seq_flgs.data == 1))
990
+ & (unpacked_dataset.priority.data == 0)
896
991
  )[0]
897
- acq_start_seconds = packets.acq_start_seconds[epoch_indices]
898
- acq_start_subseconds = packets.acq_start_subseconds[epoch_indices]
992
+ acq_start_seconds = unpacked_dataset.acq_start_seconds[epoch_indices]
993
+ acq_start_subseconds = unpacked_dataset.acq_start_subseconds[epoch_indices]
899
994
 
900
995
  # Calculate epoch variables
901
996
  epochs, epochs_delta_minus, epochs_delta_plus = calculate_epoch_values(
@@ -953,20 +1048,19 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
953
1048
  )
954
1049
 
955
1050
  # Create the CDF data variables for each Priority and Field
956
- for i in range(constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["num_priorities"]):
957
- for field in constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["cdf_fields"]:
958
- variable_name = f"P{i}_{field}"
959
- attrs = cdf_attrs.get_variable_attributes(variable_name)
960
- if field in ["NumEvents", "DataQuality"]:
961
- dims = ["epoch"]
962
- else:
963
- dims = ["epoch", "event_num"]
964
- dataset[variable_name] = xr.DataArray(
965
- np.array(data[variable_name]),
966
- name=variable_name,
967
- dims=dims,
968
- attrs=attrs,
969
- )
1051
+ for field in constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["cdf_fields"]:
1052
+ if field in ["num_events", "data_quality"]:
1053
+ attrs = cdf_attrs.get_variable_attributes("de_2d_attrs")
1054
+ dims = ["epoch", "priority"]
1055
+ else:
1056
+ attrs = cdf_attrs.get_variable_attributes("de_3d_attrs")
1057
+ dims = ["epoch", "priority", "event_num"]
1058
+ dataset[field] = xr.DataArray(
1059
+ np.array(reshaped_de_data[field]),
1060
+ name=field,
1061
+ dims=dims,
1062
+ attrs=attrs,
1063
+ )
970
1064
 
971
1065
  return dataset
972
1066
 
@@ -1392,7 +1486,7 @@ def reshape_de_data(
1392
1486
  CDF variable names, and the values represent the data.
1393
1487
  """
1394
1488
  # Dictionary to hold all the (soon to be restructured) direct event data
1395
- data: dict[str, np.ndarray] = {}
1489
+ de_data: dict[str, np.ndarray] = {}
1396
1490
 
1397
1491
  # Extract some useful variables
1398
1492
  num_priorities = constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["num_priorities"]
@@ -1412,16 +1506,20 @@ def reshape_de_data(
1412
1506
 
1413
1507
  # Initialize data arrays for each priority and field to store the data
1414
1508
  # We also need arrays to hold number of events and data quality
1415
- for priority_num in range(num_priorities):
1416
- for field in bit_structure:
1417
- if field not in ["Priority", "Spare"]:
1418
- data[f"P{priority_num}_{field}"] = np.full(
1419
- (num_epochs, 10000),
1420
- bit_structure[field]["fillval"],
1421
- dtype=bit_structure[field]["dtype"],
1422
- )
1423
- data[f"P{priority_num}_NumEvents"] = np.full(num_epochs, 65535, dtype=np.uint16)
1424
- data[f"P{priority_num}_DataQuality"] = np.full(num_epochs, 255, dtype=np.uint8)
1509
+ for field in bit_structure:
1510
+ # if these two, no need to store
1511
+ if field not in ["Priority", "Spare"]:
1512
+ de_data[f"{field}"] = np.full(
1513
+ (num_epochs, num_priorities, 10000),
1514
+ bit_structure[field]["fillval"],
1515
+ dtype=bit_structure[field]["dtype"],
1516
+ )
1517
+ # Add other additional fields of l1a
1518
+ de_data["num_events"] = np.full(
1519
+ (num_epochs, num_priorities), 65535, dtype=np.uint16
1520
+ )
1521
+
1522
+ de_data["data_quality"] = np.full((num_epochs, num_priorities), 255, dtype=np.uint8)
1425
1523
 
1426
1524
  # decompressed_data is one large list of values of length
1427
1525
  # (<number of epochs> * <number of priorities>)
@@ -1445,8 +1543,8 @@ def reshape_de_data(
1445
1543
 
1446
1544
  # Number of events and data quality can be determined at this stage
1447
1545
  num_events = num_events_arr[epoch_start:epoch_end][i]
1448
- data[f"P{priority_num}_NumEvents"][epoch_index] = num_events
1449
- data[f"P{priority_num}_DataQuality"][epoch_index] = data_quality[i]
1546
+ de_data["num_events"][epoch_index, priority_num] = num_events
1547
+ de_data["data_quality"][epoch_index, priority_num] = data_quality[i]
1450
1548
 
1451
1549
  # Iterate over each event
1452
1550
  for event_index in range(num_events):
@@ -1477,12 +1575,12 @@ def reshape_de_data(
1477
1575
  )
1478
1576
 
1479
1577
  # Set the value into the data array
1480
- data[f"P{priority_num}_{field_name}"][epoch_index, event_index] = (
1578
+ de_data[f"{field_name}"][epoch_index, priority_num, event_index] = (
1481
1579
  value
1482
1580
  )
1483
1581
  bit_position += field_components["bit_length"]
1484
1582
 
1485
- return data
1583
+ return de_data
1486
1584
 
1487
1585
 
1488
1586
  def process_codice_l1a(file_path: Path) -> list[xr.Dataset]:
@@ -9,18 +9,18 @@ from imap_processing.codice.codice_l1b import process_codice_l1b
9
9
  dataset = process_codice_l1b(l1a_filenanme)
10
10
  """
11
11
 
12
- # TODO: Figure out how to convert hi-priority data product. Need an updated
13
- # algorithm document that describes this.
14
-
15
12
  import logging
16
13
  from pathlib import Path
17
14
 
18
15
  import numpy as np
19
16
  import xarray as xr
20
17
 
18
+ from imap_processing import imap_module_directory
21
19
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
22
20
  from imap_processing.cdf.utils import load_cdf
23
21
  from imap_processing.codice import constants
22
+ from imap_processing.codice.utils import CODICEAPID
23
+ from imap_processing.utils import packet_file_to_datasets
24
24
 
25
25
  logger = logging.getLogger(__name__)
26
26
  logger.setLevel(logging.INFO)
@@ -49,9 +49,6 @@ def convert_to_rates(
49
49
  rates_data : np.ndarray
50
50
  The converted data array.
51
51
  """
52
- # TODO: Temporary workaround to create CDFs for SIT-4. Revisit after SIT-4.
53
- acq_times = 1
54
-
55
52
  if descriptor in [
56
53
  "lo-counters-aggregated",
57
54
  "lo-counters-singles",
@@ -65,6 +62,13 @@ def convert_to_rates(
65
62
  ]:
66
63
  # Applying rate calculation described in section 10.2 of the algorithm
67
64
  # document
65
+ # In order to divide by acquisition times, we must reshape the acq
66
+ # time data array to match the data variable shape
67
+ dims = [1] * dataset[variable_name].data.ndim
68
+ dims[1] = 128
69
+ acq_times = dataset.acquisition_time_per_step.data.reshape(dims)
70
+
71
+ # Now perform the calculation
68
72
  rates_data = dataset[variable_name].data / (
69
73
  acq_times
70
74
  * 1e-6 # Converting from microseconds to seconds
@@ -83,10 +87,8 @@ def convert_to_rates(
83
87
  rates_data = dataset[variable_name].data / (
84
88
  constants.L1B_DATA_PRODUCT_CONFIGURATIONS[descriptor]["num_spin_sectors"]
85
89
  * constants.L1B_DATA_PRODUCT_CONFIGURATIONS[descriptor]["num_spins"]
86
- * acq_times
90
+ * constants.HI_ACQUISITION_TIME
87
91
  )
88
- elif descriptor == "hskp":
89
- rates_data = dataset[variable_name].data / acq_times
90
92
 
91
93
  return rates_data
92
94
 
@@ -131,35 +133,43 @@ def process_codice_l1b(file_path: Path) -> xr.Dataset:
131
133
  # Update the global attributes
132
134
  l1b_dataset.attrs = cdf_attrs.get_global_attributes(dataset_name)
133
135
 
134
- # Determine which variables need to be converted from counts to rates
135
- # TODO: Figure out exactly which hskp variables need to be converted
136
- # Housekeeping and binned datasets are treated a bit differently since
137
- # not all variables need to be converted
136
+ # TODO: This was thrown together quickly and should be double-checked
138
137
  if descriptor == "hskp":
139
- # TODO: Check with Joey if any housekeeping data needs to be converted
140
- variables_to_convert = []
141
- elif descriptor == "hi-sectored":
142
- variables_to_convert = ["h", "he3he4", "cno", "fe"]
143
- elif descriptor == "hi-omni":
144
- variables_to_convert = ["h", "he3", "he4", "c", "o", "ne_mg_si", "fe", "uh"]
145
- elif descriptor == "hi-ialirt":
146
- variables_to_convert = ["h"]
138
+ xtce_filename = "codice_packet_definition.xml"
139
+ xtce_packet_definition = Path(
140
+ f"{imap_module_directory}/codice/packet_definitions/{xtce_filename}"
141
+ )
142
+ packet_file = (
143
+ imap_module_directory
144
+ / "tests"
145
+ / "codice"
146
+ / "data"
147
+ / "imap_codice_l0_raw_20241110_v001.pkts"
148
+ )
149
+ datasets: dict[int, xr.Dataset] = packet_file_to_datasets(
150
+ packet_file, xtce_packet_definition, use_derived_value=True
151
+ )
152
+ l1b_dataset = datasets[CODICEAPID.COD_NHK]
153
+
154
+ # TODO: Drop the same variables as we do in L1a? (see line 1103 in
155
+ # codice_l1a.py
156
+
147
157
  else:
148
158
  variables_to_convert = getattr(
149
159
  constants, f"{descriptor.upper().replace('-', '_')}_VARIABLE_NAMES"
150
160
  )
151
161
 
152
- # Apply the conversion to rates
153
- for variable_name in variables_to_convert:
154
- l1b_dataset[variable_name].data = convert_to_rates(
155
- l1b_dataset, descriptor, variable_name
156
- )
157
-
158
- # Set the variable attributes
159
- cdf_attrs_key = f"{descriptor}-{variable_name}"
160
- l1b_dataset[variable_name].attrs = cdf_attrs.get_variable_attributes(
161
- cdf_attrs_key, check_schema=False
162
- )
162
+ # Apply the conversion to rates
163
+ for variable_name in variables_to_convert:
164
+ l1b_dataset[variable_name].data = convert_to_rates(
165
+ l1b_dataset, descriptor, variable_name
166
+ )
167
+
168
+ # Set the variable attributes
169
+ cdf_attrs_key = f"{descriptor}-{variable_name}"
170
+ l1b_dataset[variable_name].attrs = cdf_attrs.get_variable_attributes(
171
+ cdf_attrs_key, check_schema=False
172
+ )
163
173
 
164
174
  logger.info(f"\nFinal data product:\n{l1b_dataset}\n")
165
175