imap-processing 1.0.1__py3-none-any.whl → 1.0.3__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.
Files changed (58) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +18 -0
  3. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +101 -258
  4. imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +1 -1
  5. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +12 -2
  6. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +1 -8
  7. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +16 -5
  8. imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +27 -25
  9. imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +16 -16
  10. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +2 -2
  11. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +2 -13
  12. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +12 -0
  13. imap_processing/cdf/utils.py +2 -2
  14. imap_processing/cli.py +4 -16
  15. imap_processing/codice/codice_l1a_lo_angular.py +362 -0
  16. imap_processing/codice/codice_l1a_lo_species.py +282 -0
  17. imap_processing/codice/codice_l1b.py +80 -97
  18. imap_processing/codice/codice_l2.py +270 -103
  19. imap_processing/codice/codice_new_l1a.py +64 -0
  20. imap_processing/codice/constants.py +37 -2
  21. imap_processing/codice/utils.py +270 -0
  22. imap_processing/ena_maps/ena_maps.py +51 -39
  23. imap_processing/ena_maps/utils/corrections.py +196 -14
  24. imap_processing/ena_maps/utils/naming.py +3 -1
  25. imap_processing/hi/hi_l1c.py +57 -19
  26. imap_processing/hi/hi_l2.py +89 -36
  27. imap_processing/ialirt/calculate_ingest.py +19 -1
  28. imap_processing/ialirt/constants.py +12 -6
  29. imap_processing/ialirt/generate_coverage.py +6 -1
  30. imap_processing/ialirt/l0/parse_mag.py +1 -0
  31. imap_processing/ialirt/l0/process_hit.py +1 -0
  32. imap_processing/ialirt/l0/process_swapi.py +1 -0
  33. imap_processing/ialirt/l0/process_swe.py +2 -0
  34. imap_processing/ialirt/process_ephemeris.py +6 -2
  35. imap_processing/ialirt/utils/create_xarray.py +3 -2
  36. imap_processing/lo/l1b/lo_l1b.py +12 -2
  37. imap_processing/lo/l1c/lo_l1c.py +4 -4
  38. imap_processing/lo/l2/lo_l2.py +101 -8
  39. imap_processing/quality_flags.py +1 -0
  40. imap_processing/swapi/constants.py +4 -0
  41. imap_processing/swapi/l1/swapi_l1.py +47 -20
  42. imap_processing/swapi/l2/swapi_l2.py +17 -3
  43. imap_processing/ultra/l1a/ultra_l1a.py +121 -72
  44. imap_processing/ultra/l1b/de.py +57 -1
  45. imap_processing/ultra/l1b/ultra_l1b_annotated.py +0 -1
  46. imap_processing/ultra/l1b/ultra_l1b_extended.py +24 -11
  47. imap_processing/ultra/l1c/helio_pset.py +34 -8
  48. imap_processing/ultra/l1c/l1c_lookup_utils.py +4 -2
  49. imap_processing/ultra/l1c/spacecraft_pset.py +13 -7
  50. imap_processing/ultra/l1c/ultra_l1c.py +6 -6
  51. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +79 -20
  52. imap_processing/ultra/l2/ultra_l2.py +2 -2
  53. imap_processing/ultra/utils/ultra_l1_utils.py +6 -0
  54. {imap_processing-1.0.1.dist-info → imap_processing-1.0.3.dist-info}/METADATA +1 -1
  55. {imap_processing-1.0.1.dist-info → imap_processing-1.0.3.dist-info}/RECORD +58 -54
  56. {imap_processing-1.0.1.dist-info → imap_processing-1.0.3.dist-info}/LICENSE +0 -0
  57. {imap_processing-1.0.1.dist-info → imap_processing-1.0.3.dist-info}/WHEEL +0 -0
  58. {imap_processing-1.0.1.dist-info → imap_processing-1.0.3.dist-info}/entry_points.txt +0 -0
@@ -26,11 +26,16 @@ from imap_processing.codice.constants import (
26
26
  L2_GEOMETRIC_FACTOR,
27
27
  L2_HI_NUMBER_OF_SSD,
28
28
  L2_HI_SECTORED_ANGLE,
29
+ LO_NSW_ANGULAR_VARIABLE_NAMES,
29
30
  LO_NSW_SPECIES_VARIABLE_NAMES,
31
+ LO_POSITION_TO_ELEVATION_ANGLE,
32
+ LO_SW_ANGULAR_VARIABLE_NAMES,
30
33
  LO_SW_PICKUP_ION_SPECIES_VARIABLE_NAMES,
31
- LO_SW_SPECIES_VARIABLE_NAMES,
34
+ LO_SW_SOLAR_WIND_SPECIES_VARIABLE_NAMES,
32
35
  NSW_POSITIONS,
36
+ PIXEL_ORIENTATIONS,
33
37
  PUI_POSITIONS,
38
+ SOLAR_WIND_POSITIONS,
34
39
  SW_POSITIONS,
35
40
  )
36
41
 
@@ -94,7 +99,7 @@ def get_efficiency_lut(dependencies: ProcessingInputCollection) -> pd.DataFrame:
94
99
  return pd.read_csv(dependencies.get_file_paths(descriptor="l2-lo-efficiency")[0])
95
100
 
96
101
 
97
- def get_species_efficiency(species: str, efficiency: pd.DataFrame) -> np.ndarray:
102
+ def get_species_efficiency(species: str, efficiency: pd.DataFrame) -> xr.DataArray:
98
103
  """
99
104
  Get the efficiency values for a given species.
100
105
 
@@ -107,7 +112,7 @@ def get_species_efficiency(species: str, efficiency: pd.DataFrame) -> np.ndarray
107
112
 
108
113
  Returns
109
114
  -------
110
- efficiency : np.ndarray
115
+ efficiency : xarray.DataArray
111
116
  A 2D array of efficiencies with shape (epoch, esa_steps).
112
117
  """
113
118
  species_efficiency = efficiency[efficiency["species"] == species].sort_values(
@@ -118,13 +123,16 @@ def get_species_efficiency(species: str, efficiency: pd.DataFrame) -> np.ndarray
118
123
  [col for col in species_efficiency if col.startswith("position")],
119
124
  key=lambda x: int(x.split("_")[-1]),
120
125
  )
121
- # Shape: (esa_steps, positions)
122
- return species_efficiency[position_names_sorted].to_numpy()
126
+ # Shape: (energy_table, inst_az)
127
+ return xr.DataArray(
128
+ species_efficiency[position_names_sorted].to_numpy(),
129
+ dims=("energy_table", "inst_az"),
130
+ )
123
131
 
124
132
 
125
133
  def compute_geometric_factors(
126
134
  dataset: xr.Dataset, geometric_factor_lookup: dict
127
- ) -> np.ndarray:
135
+ ) -> xr.DataArray:
128
136
  """
129
137
  Calculate geometric factors needed for intensity calculations.
130
138
 
@@ -148,7 +156,7 @@ def compute_geometric_factors(
148
156
 
149
157
  Returns
150
158
  -------
151
- geometric_factors : np.ndarray
159
+ geometric_factors : xarray.DataArray
152
160
  A 3D array of geometric factors with shape (epoch, esa_steps, positions).
153
161
  """
154
162
  # Convert the HALF_SPIN_LUT to a reverse mapping of esa_step to half_spin
@@ -170,22 +178,26 @@ def compute_geometric_factors(
170
178
 
171
179
  # Get the geometric factors based on the modes
172
180
  gf = np.where(
173
- modes[:, :, np.newaxis], # Shape (epoch, esa_step, 1)
174
- geometric_factor_lookup["reduced"], # Shape (1, esa_step, 24) - reduced mode
175
- geometric_factor_lookup["full"], # Shape (1, esa_step, 24) - full mode
176
- ) # Shape: (epoch, esa_step, positions)
177
- return gf
181
+ modes[:, :, np.newaxis], # Shape (epoch, energy_table, 1)
182
+ geometric_factor_lookup[
183
+ "reduced"
184
+ ], # Shape (1, energy_table, 24) - reduced mode
185
+ geometric_factor_lookup["full"], # Shape (1, energy_table, 24) - full mode
186
+ ) # Shape: (epoch, energy_table, inst_az)
178
187
 
188
+ return xr.DataArray(gf, dims=("epoch", "energy_table", "inst_az"))
179
189
 
180
- def process_lo_species_intensity(
190
+
191
+ def calculate_intensity(
181
192
  dataset: xr.Dataset,
182
193
  species_list: list,
183
- geometric_factors: np.ndarray,
194
+ geometric_factors: xr.DataArray,
184
195
  efficiency: pd.DataFrame,
185
196
  positions: list,
197
+ average_across_positions: bool = False,
186
198
  ) -> xr.Dataset:
187
199
  """
188
- Process the lo-species L2 dataset to calculate species intensities.
200
+ Calculate species or angular intensities.
189
201
 
190
202
  Parameters
191
203
  ----------
@@ -200,6 +212,9 @@ def process_lo_species_intensity(
200
212
  positions : list
201
213
  A list of position indices to select from the geometric factor and
202
214
  efficiency lookup tables.
215
+ average_across_positions : bool
216
+ Whether to average the efficiencies and geometric factors across the selected
217
+ positions. Default is False.
203
218
 
204
219
  Returns
205
220
  -------
@@ -207,33 +222,207 @@ def process_lo_species_intensity(
207
222
  The updated L2 dataset with species intensities calculated.
208
223
  """
209
224
  # Select the relevant positions from the geometric factors
210
- geometric_factors = geometric_factors[:, :, positions]
211
- # take the mean geometric factor across positions
212
- geometric_factors = np.nanmean(geometric_factors, axis=-1)
213
- scaler = len(positions)
214
- # Calculate the species intensities using the provided geometric factors and
215
- # efficiency. Species_intensity = species_rate / (gm * eff * esa_step)
225
+ geometric_factors = geometric_factors.isel(inst_az=positions)
226
+ if average_across_positions:
227
+ # take the mean geometric factor across positions
228
+ geometric_factors = geometric_factors.mean(dim="inst_az")
229
+ scalar = len(positions)
230
+ else:
231
+ scalar = 1
232
+ # Calculate the angular intensities using the provided geometric factors and
233
+ # efficiency.
234
+ # intensity = species_rate / (gm * eff * esa_step) for position and spin angle
216
235
  for species in species_list:
217
236
  # Select the relevant positions for the species from the efficiency LUT
218
- # Shape: (epoch, esa_steps, positions)
219
- species_eff = get_species_efficiency(species, efficiency)[
220
- np.newaxis, :, positions
221
- ]
237
+ # Shape: (epoch, energy_table, inst_az)
238
+ species_eff = get_species_efficiency(species, efficiency).isel(
239
+ inst_az=positions
240
+ )
222
241
  if species_eff.size == 0:
223
- logger.warning("No efficiency data found for species {species}. Skipping.")
242
+ logger.warning(f"No efficiency data found for species {species}. Skipping.")
224
243
  continue
225
- # Take the mean efficiency across positions
226
- species_eff = np.nanmean(species_eff, axis=-1)
227
- denominator = (
228
- scaler * geometric_factors * species_eff * dataset["energy_table"].data
229
- )
244
+
245
+ if average_across_positions:
246
+ # Take the mean efficiency across positions
247
+ species_eff = species_eff.mean(dim="inst_az")
248
+
249
+ # Shape: (epoch, energy_table, inst_az) or
250
+ # (epoch, energy_table) if averaged
251
+ denominator = scalar * geometric_factors * species_eff * dataset["energy_table"]
230
252
  if species not in dataset:
231
253
  logger.warning(
232
254
  f"Species {species} not found in dataset. Filling with NaNS."
233
255
  )
234
256
  dataset[species] = np.full(dataset["energy_table"].data.shape, np.nan)
235
257
  else:
236
- dataset[species] = dataset[species] / denominator[:, :, np.newaxis]
258
+ dataset[species] = dataset[species] / denominator
259
+
260
+ # Also calculate uncertainty if available
261
+ species_uncertainty = f"unc_{species}"
262
+ if species_uncertainty not in dataset:
263
+ logger.warning(
264
+ f"Uncertainty {species_uncertainty} not found in dataset."
265
+ f" Filling with NaNS."
266
+ )
267
+ dataset[species_uncertainty] = np.full(
268
+ dataset["energy_table"].data.shape, np.nan
269
+ )
270
+ else:
271
+ dataset[species_uncertainty] = dataset[species_uncertainty] / denominator
272
+
273
+ return dataset
274
+
275
+
276
+ def process_lo_species_intensity(
277
+ dataset: xr.Dataset,
278
+ species_list: list,
279
+ geometric_factors: xr.DataArray,
280
+ efficiency: pd.DataFrame,
281
+ positions: list,
282
+ ) -> xr.Dataset:
283
+ """
284
+ Process the lo-species L2 dataset to calculate species intensities.
285
+
286
+ Parameters
287
+ ----------
288
+ dataset : xarray.Dataset
289
+ The L2 dataset to process.
290
+ species_list : list
291
+ List of species variable names to calculate intensity.
292
+ geometric_factors : xarray.DataArray
293
+ The geometric factors array with shape (epoch, esa_steps).
294
+ efficiency : pandas.DataFrame
295
+ The efficiency lookup table.
296
+ positions : list
297
+ A list of position indices to select from the geometric factor and
298
+ efficiency lookup tables.
299
+
300
+ Returns
301
+ -------
302
+ xarray.Dataset
303
+ The updated L2 dataset with species intensities calculated.
304
+ """
305
+ # Calculate the species intensities using the provided geometric factors and
306
+ # efficiency.
307
+ dataset = calculate_intensity(
308
+ dataset,
309
+ species_list,
310
+ geometric_factors,
311
+ efficiency,
312
+ positions,
313
+ average_across_positions=True,
314
+ )
315
+
316
+ return dataset
317
+
318
+
319
+ def process_lo_angular_intensity(
320
+ dataset: xr.Dataset,
321
+ species_list: list,
322
+ geometric_factors: xr.DataArray,
323
+ efficiency: pd.DataFrame,
324
+ positions: list,
325
+ ) -> xr.Dataset:
326
+ """
327
+ Process the lo-species L2 dataset to calculate angular intensities.
328
+
329
+ Parameters
330
+ ----------
331
+ dataset : xarray.Dataset
332
+ The L2 dataset to process.
333
+ species_list : list
334
+ List of species variable names to calculate intensity.
335
+ geometric_factors : xarray.DataArray
336
+ The geometric factors array with shape (epoch, esa_steps).
337
+ efficiency : pandas.DataFrame
338
+ The efficiency lookup table.
339
+ positions : list
340
+ A list of position indices to select from the geometric factor and
341
+ efficiency lookup tables.
342
+
343
+ Returns
344
+ -------
345
+ xarray.Dataset
346
+ The updated L2 dataset with angular intensities calculated.
347
+ """
348
+ # Calculate the angular intensities using the provided geometric factors and
349
+ # efficiency.
350
+ dataset = calculate_intensity(
351
+ dataset,
352
+ species_list,
353
+ geometric_factors,
354
+ efficiency,
355
+ positions,
356
+ average_across_positions=False,
357
+ )
358
+
359
+ # transform positions to elevation angles
360
+ if positions == SW_POSITIONS:
361
+ pos_to_el = LO_POSITION_TO_ELEVATION_ANGLE["sw"]
362
+ position_index_to_adjust = 0
363
+ elif positions == NSW_POSITIONS:
364
+ pos_to_el = LO_POSITION_TO_ELEVATION_ANGLE["nsw"]
365
+ position_index_to_adjust = 9
366
+ else:
367
+ raise ValueError("Unknown positions for elevation angle mapping.")
368
+
369
+ # Create a new coordinate for elevation_angle based on inst_az
370
+ dataset = dataset.assign_coords(
371
+ elevation_angle=(
372
+ "inst_az",
373
+ [pos_to_el[pos] for pos in dataset["inst_az"].data],
374
+ )
375
+ )
376
+ # add uncertainties to species list
377
+ species_list = species_list + [f"unc_{var}" for var in species_list]
378
+ # Take the mean across elevation angles and restore the original dimension order
379
+ dataset_converted = (
380
+ dataset[species_list]
381
+ .groupby("elevation_angle")
382
+ .sum(keep_attrs=True) # One position should always contain zeros so sum is safe
383
+ # Restore original dimension order because groupby moves the grouped
384
+ # dimension to the front
385
+ .transpose("epoch", "energy_table", "spin_sector", "elevation_angle", ...)
386
+ )
387
+ # Create a new coordinate for spin angle based on spin_sector
388
+ # Use equation from section 11.2.2 of algorithm document
389
+ dataset = dataset.assign_coords(
390
+ spin_angle=("spin_sector", dataset["spin_sector"].data * 15.0 + 7.5)
391
+ )
392
+ dataset = dataset.drop_vars(species_list).merge(dataset_converted)
393
+ # Positions 0 and 10 only observe half of the 24 spins for each esa step.
394
+ # To account for this, we replicate the counts observed in position 0 and 10 for
395
+ # each esa step to either spin angles 0-11 or 12-23, depending on the pixel
396
+ # orientation (A/B). See section 11.2.2 of the CoDICE algorithm document
397
+ a_inds = np.array(
398
+ [pos for pos, orientation in PIXEL_ORIENTATIONS.items() if orientation == "A"]
399
+ )
400
+ b_inds = np.array(
401
+ [pos for pos, orientation in PIXEL_ORIENTATIONS.items() if orientation == "B"]
402
+ )
403
+
404
+ position_index = position_index_to_adjust
405
+ for species in species_list:
406
+ # Determine the correct spin indices based on the position
407
+ spin_sectors = dataset["spin_sector"].data
408
+ spin_inds_1 = np.where(spin_sectors >= 12)[0]
409
+ spin_inds_2 = np.where(spin_sectors < 12)[0]
410
+ # if position_index is 9, swap the spin indices
411
+ if position_index == 9:
412
+ spin_inds_1, spin_inds_2 = spin_inds_2, spin_inds_1
413
+
414
+ # Assign the values to the correct positions and spin sectors
415
+ dataset[species].values[
416
+ :, a_inds[:, np.newaxis], spin_inds_1, position_index
417
+ ] = dataset[species].values[
418
+ :, a_inds[:, np.newaxis], spin_inds_2, position_index
419
+ ]
420
+
421
+ dataset[species].values[
422
+ :, b_inds[:, np.newaxis], spin_inds_2, position_index
423
+ ] = dataset[species].values[
424
+ :, b_inds[:, np.newaxis], spin_inds_1, position_index
425
+ ]
237
426
 
238
427
  return dataset
239
428
 
@@ -627,7 +816,6 @@ def process_codice_l2(
627
816
  # This should get science files since ancillary or spice doesn't have data_type
628
817
  # as data level.
629
818
  file_path = dependencies.get_file_paths(descriptor=descriptor)[0]
630
-
631
819
  # Now form product name from descriptor
632
820
  descriptor = ScienceFilePath(file_path).descriptor
633
821
  dataset_name = f"imap_codice_l2_{descriptor}"
@@ -637,7 +825,12 @@ def process_codice_l2(
637
825
  if dataset_name in [
638
826
  "imap_codice_l2_lo-sw-species",
639
827
  "imap_codice_l2_lo-nsw-species",
828
+ "imap_codice_l2_lo-nsw-angular",
829
+ "imap_codice_l2_lo-sw-angular",
640
830
  ]:
831
+ cdf_attrs = ImapCdfAttributes()
832
+ cdf_attrs.add_instrument_global_attrs("codice")
833
+
641
834
  l2_dataset = load_cdf(file_path).copy()
642
835
 
643
836
  geometric_factor_lookup = get_geometric_factor_lut(dependencies)
@@ -645,12 +838,13 @@ def process_codice_l2(
645
838
  geometric_factors = compute_geometric_factors(
646
839
  l2_dataset, geometric_factor_lookup
647
840
  )
841
+
648
842
  if dataset_name == "imap_codice_l2_lo-sw-species":
649
843
  # Filter the efficiency lookup table for solar wind efficiencies
650
844
  efficiencies = efficiency_lookup[efficiency_lookup["product"] == "sw"]
651
845
  # Calculate the pickup ion sunward solar wind intensities using equation
652
- # described in section 11.2.4 of algorithm document.
653
- process_lo_species_intensity(
846
+ # described in section 11.2.3 of algorithm document.
847
+ l2_dataset = process_lo_species_intensity(
654
848
  l2_dataset,
655
849
  LO_SW_PICKUP_ION_SPECIES_VARIABLE_NAMES,
656
850
  geometric_factors,
@@ -658,27 +852,59 @@ def process_codice_l2(
658
852
  PUI_POSITIONS,
659
853
  )
660
854
  # Calculate the sunward solar wind species intensities using equation
661
- # described in section 11.2.4 of algorithm document.
662
- process_lo_species_intensity(
855
+ # described in section 11.2.3 of algorithm document.
856
+ l2_dataset = process_lo_species_intensity(
663
857
  l2_dataset,
664
- LO_SW_SPECIES_VARIABLE_NAMES,
858
+ LO_SW_SOLAR_WIND_SPECIES_VARIABLE_NAMES,
665
859
  geometric_factors,
666
860
  efficiencies,
667
- SW_POSITIONS,
861
+ SOLAR_WIND_POSITIONS,
668
862
  )
669
- else:
670
- # Filter the efficiency lookup table for non solar wind efficiencies
863
+ l2_dataset.attrs.update(
864
+ cdf_attrs.get_global_attributes("imap_codice_l2_lo-sw-species")
865
+ )
866
+ elif dataset_name == "imap_codice_l2_lo-nsw-species":
867
+ # Filter the efficiency lookup table for non-solar wind efficiencies
671
868
  efficiencies = efficiency_lookup[efficiency_lookup["product"] == "nsw"]
672
869
  # Calculate the non-sunward species intensities using equation
673
- # described in section 11.2.4 of algorithm document.
674
- process_lo_species_intensity(
870
+ # described in section 11.2.3 of algorithm document.
871
+ l2_dataset = process_lo_species_intensity(
675
872
  l2_dataset,
676
873
  LO_NSW_SPECIES_VARIABLE_NAMES,
677
874
  geometric_factors,
678
875
  efficiencies,
679
876
  NSW_POSITIONS,
680
877
  )
681
-
878
+ l2_dataset.attrs.update(
879
+ cdf_attrs.get_global_attributes("imap_codice_l2_lo-nsw-species")
880
+ )
881
+ elif dataset_name == "imap_codice_l2_lo-sw-angular":
882
+ efficiencies = efficiency_lookup[efficiency_lookup["product"] == "sw"]
883
+ # Calculate the sunward solar wind angular intensities using equation
884
+ # described in section 11.2.2 of algorithm document.
885
+ l2_dataset = process_lo_angular_intensity(
886
+ l2_dataset,
887
+ LO_SW_ANGULAR_VARIABLE_NAMES,
888
+ geometric_factors,
889
+ efficiencies,
890
+ SW_POSITIONS,
891
+ )
892
+ l2_dataset.attrs.update(
893
+ cdf_attrs.get_global_attributes("imap_codice_l2_lo-sw-angular")
894
+ )
895
+ if dataset_name == "imap_codice_l2_lo-nsw-angular":
896
+ # Calculate the non sunward angular intensities
897
+ efficiencies = efficiency_lookup[efficiency_lookup["product"] == "nsw"]
898
+ l2_dataset = process_lo_angular_intensity(
899
+ l2_dataset,
900
+ LO_NSW_ANGULAR_VARIABLE_NAMES,
901
+ geometric_factors,
902
+ efficiencies,
903
+ NSW_POSITIONS,
904
+ )
905
+ l2_dataset.attrs.update(
906
+ cdf_attrs.get_global_attributes("imap_codice_l2_lo-nsw-angular")
907
+ )
682
908
  if dataset_name in [
683
909
  "imap_codice_l2_hi-counters-singles",
684
910
  "imap_codice_l2_hi-counters-aggregated",
@@ -727,65 +953,6 @@ def process_codice_l2(
727
953
  # See section 11.1.2 of algorithm document
728
954
  pass
729
955
 
730
- elif dataset_name == "imap_codice_l2_lo-sw-angular":
731
- # Calculate the sunward angular intensities using equation described in
732
- # section 11.2.3 of algorithm document.
733
- pass
734
-
735
- elif dataset_name == "imap_codice_l2_lo-nsw-angular":
736
- # Calculate the non-sunward angular intensities using equation described
737
- # in section 11.2.3 of algorithm document.
738
- pass
739
-
740
956
  # logger.info(f"\nFinal data product:\n{l2_dataset}\n")
741
957
 
742
958
  return l2_dataset
743
-
744
-
745
- def add_dataset_attributes(
746
- dataset: xr.Dataset, dataset_name: str, cdf_attrs: ImapCdfAttributes
747
- ) -> xr.Dataset:
748
- """
749
- Add the global and variable attributes to the dataset.
750
-
751
- Parameters
752
- ----------
753
- dataset : xarray.Dataset
754
- The dataset to update.
755
- dataset_name : str
756
- The name of the dataset.
757
- cdf_attrs : ImapCdfAttributes
758
- The attribute manager for CDF attributes.
759
-
760
- Returns
761
- -------
762
- xarray.Dataset
763
- The updated dataset.
764
- """
765
- cdf_attrs.add_instrument_global_attrs("codice")
766
- cdf_attrs.add_instrument_variable_attrs("codice", "l2")
767
-
768
- # Update the global attributes
769
- dataset.attrs = cdf_attrs.get_global_attributes(dataset_name)
770
-
771
- # Set the variable attributes
772
- for variable_name in dataset.data_vars.keys():
773
- try:
774
- dataset[variable_name].attrs = cdf_attrs.get_variable_attributes(
775
- variable_name, check_schema=False
776
- )
777
- except KeyError:
778
- # Some variables may have a product descriptor prefix in the
779
- # cdf attributes key if they are common to multiple products.
780
- descriptor = dataset_name.split("imap_codice_l2_")[-1]
781
- cdf_attrs_key = f"{descriptor}-{variable_name}"
782
- try:
783
- dataset[variable_name].attrs = cdf_attrs.get_variable_attributes(
784
- f"{cdf_attrs_key}", check_schema=False
785
- )
786
- except KeyError:
787
- logger.error(
788
- f"Field '{variable_name}' and '{cdf_attrs_key}' not found in "
789
- f"attribute manager."
790
- )
791
- return dataset
@@ -0,0 +1,64 @@
1
+ """CoDICE L1A processing functions."""
2
+
3
+ import logging
4
+
5
+ import xarray as xr
6
+ from imap_data_access import ProcessingInputCollection
7
+
8
+ from imap_processing import imap_module_directory
9
+ from imap_processing.codice.codice_l1a_lo_angular import l1a_lo_angular
10
+ from imap_processing.codice.codice_l1a_lo_species import l1a_lo_species
11
+ from imap_processing.codice.utils import (
12
+ CODICEAPID,
13
+ )
14
+ from imap_processing.utils import packet_file_to_datasets
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def process_l1a(dependency: ProcessingInputCollection) -> list[xr.Dataset]:
20
+ """
21
+ Process L1A data based on descriptor and dependencies.
22
+
23
+ Parameters
24
+ ----------
25
+ dependency : ProcessingInputCollection
26
+ Collection of processing inputs required for L1A processing.
27
+
28
+ Returns
29
+ -------
30
+ list[xarray.Dataset]
31
+ List of processed L1A datasets generated from available APIDs.
32
+ """
33
+ # Get science data which is L0 packet file
34
+ science_file = dependency.get_file_paths(data_type="l0")[0]
35
+ # Get LUT file
36
+ lut_file = dependency.get_file_paths(descriptor="l1a-sci-lut")[0]
37
+
38
+ logger.info(f"Processing L1A for {science_file.name} with {lut_file.name}")
39
+
40
+ xtce_file = (
41
+ imap_module_directory / "codice/packet_definitions/codice_packet_definition.xml"
42
+ )
43
+ # Decom packet
44
+ datasets_by_apid = packet_file_to_datasets(
45
+ science_file,
46
+ xtce_file,
47
+ )
48
+
49
+ datasets = []
50
+ for apid in datasets_by_apid:
51
+ if apid == CODICEAPID.COD_LO_SW_SPECIES_COUNTS:
52
+ logger.info("Processing Lo SW Species Counts")
53
+ datasets.append(l1a_lo_species(datasets_by_apid[apid], lut_file))
54
+ elif apid == CODICEAPID.COD_LO_NSW_SPECIES_COUNTS:
55
+ logger.info("Processing Lo NSW Species Counts")
56
+ datasets.append(l1a_lo_species(datasets_by_apid[apid], lut_file))
57
+ elif apid == CODICEAPID.COD_LO_SW_ANGULAR_COUNTS:
58
+ logger.info("Processing Lo SW Angular Counts")
59
+ datasets.append(l1a_lo_angular(datasets_by_apid[apid], lut_file))
60
+ elif apid == CODICEAPID.COD_LO_NSW_ANGULAR_COUNTS:
61
+ logger.info("Processing Lo NSW Angular Counts")
62
+ datasets.append(l1a_lo_angular(datasets_by_apid[apid], lut_file))
63
+
64
+ return datasets
@@ -61,6 +61,8 @@ CODICEAPID_MAPPING = {
61
61
  SPIN_PERIOD_CONVERSION = 0.00032
62
62
  K_FACTOR = 5.76 # This is used to convert voltages to energies in L2
63
63
  HI_ACQUISITION_TIME = 0.59916
64
+ NUM_ESA_STEPS = 128
65
+ LO_DESPIN_SPIN_SECTORS = 24
64
66
 
65
67
  # CDF variable names used for lo data products
66
68
  LO_COUNTERS_SINGLES_VARIABLE_NAMES = ["apd_singles"]
@@ -2281,8 +2283,9 @@ HALF_SPIN_LUT = {
2281
2283
  }
2282
2284
 
2283
2285
  NSW_POSITIONS = [x for x in range(3, 22)]
2284
- SW_POSITIONS = [0]
2285
- PUI_POSITIONS = [0, 1, 2, 22, 23]
2286
+ SW_POSITIONS = [0, 1, 2, 22, 23]
2287
+ SOLAR_WIND_POSITIONS = [0]
2288
+ PUI_POSITIONS = SW_POSITIONS
2286
2289
  L2_GEOMETRIC_FACTOR = 0.013
2287
2290
  L2_HI_NUMBER_OF_SSD = 12.0
2288
2291
 
@@ -2320,3 +2323,35 @@ HI_L2_ELEVATION_ANGLE = np.array(
2320
2323
  ],
2321
2324
  dtype=float,
2322
2325
  )
2326
+
2327
+
2328
+ LO_POSITION_TO_ELEVATION_ANGLE = {
2329
+ "sw": {
2330
+ 1: 0,
2331
+ 2: 15,
2332
+ 24: 15,
2333
+ 3: 30,
2334
+ 23: 30,
2335
+ },
2336
+ "nsw": {
2337
+ 4: 45,
2338
+ 22: 45,
2339
+ 5: 60,
2340
+ 21: 60,
2341
+ 6: 75,
2342
+ 20: 75,
2343
+ 7: 90,
2344
+ 19: 90,
2345
+ 8: 105,
2346
+ 18: 105,
2347
+ 9: 120,
2348
+ 17: 120,
2349
+ 10: 135,
2350
+ 16: 135,
2351
+ 11: 150,
2352
+ 15: 150,
2353
+ 12: 165,
2354
+ 14: 165,
2355
+ 13: 180,
2356
+ },
2357
+ }