imap-processing 0.19.0__py3-none-any.whl → 0.19.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.

Potentially problematic release.


This version of imap-processing might be problematic. Click here for more details.

Files changed (73) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -0
  3. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +31 -894
  4. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +279 -255
  5. imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +55 -0
  6. imap_processing/cdf/config/imap_enamaps_l2-healpix_variable_attrs.yaml +29 -0
  7. imap_processing/cdf/config/imap_enamaps_l2-rectangular_variable_attrs.yaml +32 -0
  8. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +3 -1
  9. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +5 -4
  10. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +28 -16
  11. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -31
  12. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +61 -1
  13. imap_processing/cli.py +62 -71
  14. imap_processing/codice/codice_l0.py +2 -1
  15. imap_processing/codice/codice_l1a.py +47 -49
  16. imap_processing/codice/codice_l1b.py +42 -32
  17. imap_processing/codice/codice_l2.py +105 -7
  18. imap_processing/codice/constants.py +50 -8
  19. imap_processing/codice/data/lo_stepping_values.csv +1 -1
  20. imap_processing/ena_maps/ena_maps.py +39 -18
  21. imap_processing/ena_maps/utils/corrections.py +291 -0
  22. imap_processing/ena_maps/utils/map_utils.py +20 -4
  23. imap_processing/glows/l1b/glows_l1b.py +38 -23
  24. imap_processing/glows/l1b/glows_l1b_data.py +10 -11
  25. imap_processing/hi/hi_l1c.py +4 -109
  26. imap_processing/hi/hi_l2.py +34 -23
  27. imap_processing/hi/utils.py +109 -0
  28. imap_processing/ialirt/l0/ialirt_spice.py +1 -1
  29. imap_processing/ialirt/l0/parse_mag.py +18 -4
  30. imap_processing/ialirt/l0/process_hit.py +9 -4
  31. imap_processing/ialirt/l0/process_swapi.py +9 -4
  32. imap_processing/ialirt/l0/process_swe.py +9 -4
  33. imap_processing/ialirt/utils/create_xarray.py +1 -1
  34. imap_processing/lo/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
  35. imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
  36. imap_processing/lo/l1b/lo_l1b.py +90 -16
  37. imap_processing/lo/l1c/lo_l1c.py +164 -50
  38. imap_processing/lo/l2/lo_l2.py +941 -127
  39. imap_processing/mag/l1d/mag_l1d_data.py +36 -3
  40. imap_processing/mag/l2/mag_l2.py +2 -0
  41. imap_processing/mag/l2/mag_l2_data.py +4 -3
  42. imap_processing/quality_flags.py +14 -0
  43. imap_processing/spice/geometry.py +13 -8
  44. imap_processing/spice/pointing_frame.py +4 -2
  45. imap_processing/spice/repoint.py +49 -0
  46. imap_processing/ultra/constants.py +29 -0
  47. imap_processing/ultra/l0/decom_tools.py +58 -46
  48. imap_processing/ultra/l0/decom_ultra.py +21 -9
  49. imap_processing/ultra/l0/ultra_utils.py +4 -4
  50. imap_processing/ultra/l1b/badtimes.py +35 -11
  51. imap_processing/ultra/l1b/de.py +15 -9
  52. imap_processing/ultra/l1b/extendedspin.py +24 -12
  53. imap_processing/ultra/l1b/goodtimes.py +112 -0
  54. imap_processing/ultra/l1b/lookup_utils.py +1 -1
  55. imap_processing/ultra/l1b/ultra_l1b.py +7 -7
  56. imap_processing/ultra/l1b/ultra_l1b_culling.py +8 -4
  57. imap_processing/ultra/l1b/ultra_l1b_extended.py +79 -43
  58. imap_processing/ultra/l1c/helio_pset.py +68 -39
  59. imap_processing/ultra/l1c/l1c_lookup_utils.py +45 -12
  60. imap_processing/ultra/l1c/spacecraft_pset.py +81 -37
  61. imap_processing/ultra/l1c/ultra_l1c.py +27 -22
  62. imap_processing/ultra/l1c/ultra_l1c_culling.py +7 -0
  63. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +41 -41
  64. imap_processing/ultra/l2/ultra_l2.py +75 -18
  65. imap_processing/ultra/utils/ultra_l1_utils.py +10 -5
  66. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/METADATA +2 -2
  67. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/RECORD +71 -69
  68. imap_processing/ultra/l1b/cullingmask.py +0 -90
  69. imap_processing/ultra/l1c/histogram.py +0 -36
  70. /imap_processing/glows/ancillary/{imap_glows_pipeline_settings_20250923_v002.json → imap_glows_pipeline-settings_20250923_v002.json} +0 -0
  71. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/LICENSE +0 -0
  72. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/WHEEL +0 -0
  73. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/entry_points.txt +0 -0
imap_processing/cli.py CHANGED
@@ -24,8 +24,7 @@ import imap_data_access
24
24
  import numpy as np
25
25
  import spiceypy
26
26
  import xarray as xr
27
- from imap_data_access import ScienceFilePath
28
- from imap_data_access.io import download
27
+ from imap_data_access.io import IMAPDataAccessError, download
29
28
  from imap_data_access.processing_input import (
30
29
  ProcessingInputCollection,
31
30
  ProcessingInputType,
@@ -407,32 +406,23 @@ class ProcessInstrument(ABC):
407
406
  A list of file paths to upload to the SDC.
408
407
  """
409
408
  if self.upload_to_sdc:
410
- # Validate that the files don't already exist
411
- for filename in products:
412
- file_path = ScienceFilePath(filename)
413
- existing_file = imap_data_access.query(
414
- instrument=file_path.instrument,
415
- data_level=file_path.data_level,
416
- descriptor=file_path.descriptor,
417
- start_date=file_path.start_date,
418
- end_date=file_path.start_date,
419
- repointing=file_path.repointing,
420
- version=file_path.version,
421
- extension="cdf",
422
- table="science",
423
- )
424
- if existing_file:
425
- raise ProcessInstrument.ImapFileExistsError(
426
- f"File {filename} already exists in the IMAP SDC. "
427
- "No files were uploaded."
428
- f"Generated files: {products}."
429
- )
430
-
431
- if len(products) == 0:
409
+ if not products:
432
410
  logger.info("No files to upload.")
411
+ return
412
+
433
413
  for filename in products:
434
- logger.info(f"Uploading file: {filename}")
435
- imap_data_access.upload(filename)
414
+ try:
415
+ logger.info(f"Uploading file: {filename}")
416
+ imap_data_access.upload(filename)
417
+ except IMAPDataAccessError as e:
418
+ msg = str(e)
419
+ if "FileAlreadyExists" in msg and "409" in msg:
420
+ logger.warning("Skipping upload of existing file, %s", filename)
421
+ continue
422
+ else:
423
+ logger.error(f"Upload failed with error: {msg}")
424
+ except Exception as e:
425
+ logger.error(f"Upload failed unknown error: {e}")
436
426
 
437
427
  @final
438
428
  def process(self) -> None:
@@ -676,7 +666,7 @@ class Glows(ProcessInstrument):
676
666
  datasets: list[xr.Dataset] = []
677
667
 
678
668
  if self.data_level == "l1a":
679
- science_files = dependencies.get_file_paths(source="glows")
669
+ science_files = dependencies.get_file_paths(source="glows", data_type="l0")
680
670
  if len(science_files) != 1:
681
671
  raise ValueError(
682
672
  f"GLOWS L1A requires exactly one input science file, received: "
@@ -685,33 +675,30 @@ class Glows(ProcessInstrument):
685
675
  datasets = glows_l1a(science_files[0])
686
676
 
687
677
  if self.data_level == "l1b":
688
- science_files = dependencies.get_file_paths(source="glows")
678
+ science_files = dependencies.get_file_paths(source="glows", data_type="l1a")
689
679
  if len(science_files) != 1:
690
680
  raise ValueError(
691
681
  f"GLOWS L1B requires exactly one input science file, received: "
692
682
  f"{science_files}."
693
683
  )
694
684
  input_dataset = load_cdf(science_files[0])
695
- # TODO: Replace this by reading from AWS/ProcessingInputs
696
-
697
- glows_ancillary_dir = Path(__file__).parent / "glows" / "ancillary"
698
685
 
699
686
  # Create file lists for each ancillary type
700
- excluded_regions_files = [
701
- glows_ancillary_dir
702
- / "imap_glows_map-of-excluded-regions_20250923_v002.dat"
703
- ]
704
- uv_sources_files = [
705
- glows_ancillary_dir / "imap_glows_map-of-uv-sources_20250923_v002.dat"
706
- ]
707
- suspected_transients_files = [
708
- glows_ancillary_dir
709
- / "imap_glows_suspected-transients_20250923_v002.dat"
710
- ]
711
- exclusions_by_instr_team_files = [
712
- glows_ancillary_dir
713
- / "imap_glows_exclusions-by-instr-team_20250923_v002.dat"
714
- ]
687
+ excluded_regions_files = dependencies.get_processing_inputs(
688
+ descriptor="map-of-excluded-regions"
689
+ )[0]
690
+ uv_sources_files = dependencies.get_processing_inputs(
691
+ descriptor="map-of-uv-sources"
692
+ )[0]
693
+ suspected_transients_files = dependencies.get_processing_inputs(
694
+ descriptor="suspected-transients"
695
+ )[0]
696
+ exclusions_by_instr_team_files = dependencies.get_processing_inputs(
697
+ descriptor="exclusions-by-instr-team"
698
+ )[0]
699
+ pipeline_settings = dependencies.get_processing_inputs(
700
+ descriptor="pipeline-settings"
701
+ )[0]
715
702
 
716
703
  # Use end date buffer for ancillary data
717
704
  current_day = np.datetime64(
@@ -730,6 +717,9 @@ class Glows(ProcessInstrument):
730
717
  exclusions_by_instr_team_combiner = GlowsAncillaryCombiner(
731
718
  exclusions_by_instr_team_files, day_buffer
732
719
  )
720
+ pipeline_settings_combiner = GlowsAncillaryCombiner(
721
+ pipeline_settings, day_buffer
722
+ )
733
723
 
734
724
  datasets = [
735
725
  glows_l1b(
@@ -738,6 +728,7 @@ class Glows(ProcessInstrument):
738
728
  uv_sources_combiner.combined_dataset,
739
729
  suspected_transients_combiner.combined_dataset,
740
730
  exclusions_by_instr_team_combiner.combined_dataset,
731
+ pipeline_settings_combiner.combined_dataset,
741
732
  )
742
733
  ]
743
734
 
@@ -1020,16 +1011,19 @@ class Lo(ProcessInstrument):
1020
1011
  elif self.data_level == "l1b":
1021
1012
  data_dict = {}
1022
1013
  science_files = dependencies.get_file_paths(source="lo", data_type="l1a")
1014
+ ancillary_files = dependencies.get_file_paths(
1015
+ source="lo", data_type="ancillary"
1016
+ )
1023
1017
  logger.info(f"Science files for L1B: {science_files}")
1024
1018
  for file in science_files:
1025
1019
  dataset = load_cdf(file)
1026
1020
  data_dict[dataset.attrs["Logical_source"]] = dataset
1027
- datasets = lo_l1b.lo_l1b(data_dict)
1021
+ datasets = lo_l1b.lo_l1b(data_dict, ancillary_files)
1028
1022
 
1029
1023
  elif self.data_level == "l1c":
1030
1024
  data_dict = {}
1031
1025
  anc_dependencies: list = dependencies.get_file_paths(
1032
- source="lo", descriptor="goodtimes"
1026
+ source="lo", data_type="ancillary"
1033
1027
  )
1034
1028
  science_files = dependencies.get_file_paths(source="lo", descriptor="de")
1035
1029
  for file in science_files:
@@ -1039,13 +1033,11 @@ class Lo(ProcessInstrument):
1039
1033
 
1040
1034
  elif self.data_level == "l2":
1041
1035
  data_dict = {}
1042
- # TODO: Add ancillary descriptors when maps using them are
1043
- # implemented.
1044
- anc_dependencies = []
1045
1036
  science_files = dependencies.get_file_paths(source="lo", descriptor="pset")
1046
- psets = []
1047
- for file in science_files:
1048
- psets.append(load_cdf(file))
1037
+ anc_dependencies = dependencies.get_file_paths(data_type="ancillary")
1038
+
1039
+ # Load all pset files into datasets
1040
+ psets = [load_cdf(file) for file in science_files]
1049
1041
  data_dict[psets[0].attrs["Logical_source"]] = psets
1050
1042
  datasets = lo_l2.lo_l2(data_dict, anc_dependencies, self.descriptor)
1051
1043
  return datasets
@@ -1228,8 +1220,8 @@ class Spacecraft(ProcessInstrument):
1228
1220
  The list of processed products.
1229
1221
  """
1230
1222
  print(f"Processing Spacecraft {self.data_level}")
1231
-
1232
- if self.data_level == "l1a":
1223
+ processed_dataset = []
1224
+ if self.descriptor == "quaternions":
1233
1225
  # File path is expected output file path
1234
1226
  input_files = dependencies.get_file_paths(source="spacecraft")
1235
1227
  if len(input_files) > 1:
@@ -1238,26 +1230,21 @@ class Spacecraft(ProcessInstrument):
1238
1230
  f"{input_files}. Expected only one dependency."
1239
1231
  )
1240
1232
  datasets = list(quaternions.process_quaternions(input_files[0]))
1241
- return datasets
1242
- elif self.data_level == "spice":
1233
+ processed_dataset.extend(datasets)
1234
+ elif self.descriptor == "pointing-attitude":
1243
1235
  spice_inputs = dependencies.get_file_paths(
1244
1236
  data_type=SPICESource.SPICE.value
1245
1237
  )
1246
1238
  ah_paths = [path for path in spice_inputs if ".ah" in path.suffixes]
1247
- if len(ah_paths) != 1:
1248
- raise ValueError(
1249
- f"Unexpected spice dependencies found for Spacecraft "
1250
- f"pointing_kernel: {ah_paths}. Expected exactly one "
1251
- f"attitude history file."
1252
- )
1253
1239
  pointing_kernel_paths = pointing_frame.generate_pointing_attitude_kernel(
1254
- ah_paths[0]
1240
+ ah_paths[-1]
1255
1241
  )
1256
- return pointing_kernel_paths
1242
+ processed_dataset.extend(pointing_kernel_paths)
1257
1243
  else:
1258
1244
  raise NotImplementedError(
1259
1245
  f"Spacecraft processing not implemented for level {self.data_level}"
1260
1246
  )
1247
+ return processed_dataset
1261
1248
 
1262
1249
 
1263
1250
  class Swapi(ProcessInstrument):
@@ -1461,7 +1448,10 @@ class Ultra(ProcessInstrument):
1461
1448
  }
1462
1449
  science_files = dependencies.get_file_paths(source="ultra", data_type="l1b")
1463
1450
  l1b_dict = {
1464
- dataset.attrs["Logical_source"]: dataset
1451
+ # TODO remove
1452
+ dataset.attrs["Logical_source"].replace(
1453
+ "cullingmask", "goodtimes"
1454
+ ): dataset
1465
1455
  for dataset in [load_cdf(sci_file) for sci_file in science_files]
1466
1456
  }
1467
1457
  combined = {**l1a_dict, **l1b_dict}
@@ -1470,11 +1460,12 @@ class Ultra(ProcessInstrument):
1470
1460
  for path in anc_paths:
1471
1461
  ancillary_files[path.stem.split("_")[2]] = path
1472
1462
  spice_paths = dependencies.get_file_paths(data_type="spice")
1473
- if spice_paths:
1474
- has_spice = True
1463
+ # Only the helio pset needs IMAP frames
1464
+ if any("imap_frames" in path.as_posix() for path in spice_paths):
1465
+ imap_frames = True
1475
1466
  else:
1476
- has_spice = False
1477
- datasets = ultra_l1c.ultra_l1c(combined, ancillary_files, has_spice)
1467
+ imap_frames = False
1468
+ datasets = ultra_l1c.ultra_l1c(combined, ancillary_files, imap_frames)
1478
1469
  elif self.data_level == "l2":
1479
1470
  all_pset_filepaths = dependencies.get_file_paths(
1480
1471
  source="ultra", descriptor="pset"
@@ -39,7 +39,8 @@ def decom_packets(packet_file: Path) -> dict[int, xr.Dataset]:
39
39
  # TODO: Currently need to use the 'old' packet definition for housekeeping
40
40
  # because the simulated housekeeping data being used has various
41
41
  # mis-matches from the telemetry definition. This may be updated
42
- # once new simulated housekeeping data are acquired.
42
+ # once new simulated housekeeping data are acquired. See GitHub issue
43
+ # #2135.
43
44
  if "hskp" in str(packet_file):
44
45
  xtce_filename = "P_COD_NHK.xml"
45
46
  else:
@@ -125,7 +125,7 @@ class CoDICEL1aPipeline:
125
125
  # orientation and the azimuth determine which spin sector the data
126
126
  # gets stored in.
127
127
  # TODO: All these nested for-loops are bad. Try to find a better
128
- # solution.
128
+ # solution. See GitHub issue #2136.
129
129
  for i, epoch_data in enumerate(self.data):
130
130
  for energy_index in range(num_energies):
131
131
  pixel_orientation = constants.PIXEL_ORIENTATIONS[energy_index]
@@ -345,7 +345,7 @@ class CoDICEL1aPipeline:
345
345
  # energy dimension
346
346
  # TODO: This bit of code may no longer be needed once I can figure
347
347
  # out how to run hi-sectored product through the
348
- # create_binned_dataset function
348
+ # create_binned_dataset function. See GitHub issue #2137.
349
349
  if self.config["dataset_name"] == "imap_codice_l1a_hi-sectored":
350
350
  dims = [
351
351
  f"energy_{variable_name}" if item == "esa_step" else item
@@ -367,7 +367,7 @@ class CoDICEL1aPipeline:
367
367
  # longer need the "esa_step" coordinate
368
368
  # TODO: This bit of code may no longer be needed once I can figure
369
369
  # out how to run hi-sectored product through the
370
- # create_binned_dataset function
370
+ # create_binned_dataset function. See GitHub issue #2137.
371
371
  if self.config["dataset_name"] == "imap_codice_l1a_hi-sectored":
372
372
  for species in self.config["energy_table"]:
373
373
  dataset = self.define_energy_bins(dataset, species)
@@ -822,9 +822,6 @@ def group_ialirt_data(
822
822
 
823
823
  # Workaround to get this function working for both I-ALiRT spacecraft
824
824
  # data and CoDICE-specific I-ALiRT test data from Joey
825
- # TODO: Once CoDICE I-ALiRT processing is more established, we can probably
826
- # do away with processing the test data from Joey and just use the
827
- # I-ALiRT data that is constructed closer to what we expect in-flight.
828
825
  if hasattr(packets, "acquisition_time"):
829
826
  time_key = "acquisition_time"
830
827
  counter_key = "counter"
@@ -880,7 +877,7 @@ def create_binned_dataset(
880
877
  Xarray dataset containing the final processed dataset.
881
878
  """
882
879
  # TODO: hi-sectored data product should be processed similar to hi-omni,
883
- # so I should be able to use this method.
880
+ # so I should be able to use this method. See GitHub issue #2137.
884
881
 
885
882
  # Get the four "main" parameters for processing
886
883
  table_id, plan_id, plan_step, view_id = get_params(dataset)
@@ -901,7 +898,7 @@ def create_binned_dataset(
901
898
  attrs=pipeline.cdf_attrs.get_variable_attributes("epoch", check_schema=False),
902
899
  )
903
900
  # TODO: Figure out how to calculate epoch centers and deltas and store them
904
- # in variables here
901
+ # in variables here. See GitHub issue #1501.
905
902
  dataset = xr.Dataset(
906
903
  coords={"epoch": coord},
907
904
  attrs=pipeline.cdf_attrs.get_global_attributes(pipeline.config["dataset_name"]),
@@ -941,7 +938,7 @@ def create_binned_dataset(
941
938
  return dataset
942
939
 
943
940
 
944
- 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:
945
942
  """
946
943
  Create dataset for direct event data.
947
944
 
@@ -955,7 +952,7 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
955
952
  dictionary. Padding is added to any fields that have less than 10000 events.
956
953
 
957
954
  In order to process these data, we must take the decommed raw data, group
958
- the packets appropriately based on their `seq_flgs`, decompress the data,
955
+ the unpacked_dataset appropriately based on their `seq_flgs`, decompress the data,
959
956
  then arrange the data into CDF data variables for each priority and bit
960
957
  field. For example, P2_SpinAngle represents the spin angles for the 2nd
961
958
  priority data.
@@ -964,8 +961,8 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
964
961
  ----------
965
962
  apid : int
966
963
  The APID of the packet.
967
- packets : xarray.Dataset
968
- The packets to process.
964
+ unpacked_dataset : xarray.Dataset
965
+ The unpacked dataset to process.
969
966
 
970
967
  Returns
971
968
  -------
@@ -973,13 +970,13 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
973
970
  Xarray dataset containing the direct event data.
974
971
  """
975
972
  # Group and decompress the data
976
- grouped_data = group_data(packets)
973
+ grouped_data = group_data(unpacked_dataset)
977
974
  decompressed_data = [
978
975
  decompress(group, CoDICECompression.LOSSLESS) for group in grouped_data
979
976
  ]
980
977
 
981
978
  # Reshape the packet data into CDF-ready variables
982
- data = reshape_de_data(packets, decompressed_data, apid)
979
+ reshaped_de_data = reshape_de_data(unpacked_dataset, decompressed_data, apid)
983
980
 
984
981
  # Gather the CDF attributes
985
982
  cdf_attrs = ImapCdfAttributes()
@@ -989,11 +986,11 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
989
986
  # Determine the epochs to use in the dataset, which are the epochs whenever
990
987
  # there is a start of a segment and the priority is 0
991
988
  epoch_indices = np.where(
992
- ((packets.seq_flgs.data == 3) | (packets.seq_flgs.data == 1))
993
- & (packets.priority.data == 0)
989
+ ((unpacked_dataset.seq_flgs.data == 3) | (unpacked_dataset.seq_flgs.data == 1))
990
+ & (unpacked_dataset.priority.data == 0)
994
991
  )[0]
995
- acq_start_seconds = packets.acq_start_seconds[epoch_indices]
996
- 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]
997
994
 
998
995
  # Calculate epoch variables
999
996
  epochs, epochs_delta_minus, epochs_delta_plus = calculate_epoch_values(
@@ -1051,20 +1048,19 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
1051
1048
  )
1052
1049
 
1053
1050
  # Create the CDF data variables for each Priority and Field
1054
- for i in range(constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["num_priorities"]):
1055
- for field in constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["cdf_fields"]:
1056
- variable_name = f"p{i}_{field}"
1057
- attrs = cdf_attrs.get_variable_attributes(variable_name)
1058
- if field in ["num_events", "data_quality"]:
1059
- dims = ["epoch"]
1060
- else:
1061
- dims = ["epoch", "event_num"]
1062
- dataset[variable_name] = xr.DataArray(
1063
- np.array(data[variable_name]),
1064
- name=variable_name,
1065
- dims=dims,
1066
- attrs=attrs,
1067
- )
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
+ )
1068
1064
 
1069
1065
  return dataset
1070
1066
 
@@ -1490,7 +1486,7 @@ def reshape_de_data(
1490
1486
  CDF variable names, and the values represent the data.
1491
1487
  """
1492
1488
  # Dictionary to hold all the (soon to be restructured) direct event data
1493
- data: dict[str, np.ndarray] = {}
1489
+ de_data: dict[str, np.ndarray] = {}
1494
1490
 
1495
1491
  # Extract some useful variables
1496
1492
  num_priorities = constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["num_priorities"]
@@ -1510,18 +1506,20 @@ def reshape_de_data(
1510
1506
 
1511
1507
  # Initialize data arrays for each priority and field to store the data
1512
1508
  # We also need arrays to hold number of events and data quality
1513
- for priority_num in range(num_priorities):
1514
- for field in bit_structure:
1515
- if field not in ["Priority", "Spare"]:
1516
- data[f"p{priority_num}_{field}"] = np.full(
1517
- (num_epochs, 10000),
1518
- bit_structure[field]["fillval"],
1519
- dtype=bit_structure[field]["dtype"],
1520
- )
1521
- data[f"p{priority_num}_num_events"] = np.full(
1522
- num_epochs, 65535, dtype=np.uint16
1523
- )
1524
- data[f"p{priority_num}_data_quality"] = 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)
1525
1523
 
1526
1524
  # decompressed_data is one large list of values of length
1527
1525
  # (<number of epochs> * <number of priorities>)
@@ -1545,8 +1543,8 @@ def reshape_de_data(
1545
1543
 
1546
1544
  # Number of events and data quality can be determined at this stage
1547
1545
  num_events = num_events_arr[epoch_start:epoch_end][i]
1548
- data[f"p{priority_num}_num_events"][epoch_index] = num_events
1549
- data[f"p{priority_num}_data_quality"][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]
1550
1548
 
1551
1549
  # Iterate over each event
1552
1550
  for event_index in range(num_events):
@@ -1577,12 +1575,12 @@ def reshape_de_data(
1577
1575
  )
1578
1576
 
1579
1577
  # Set the value into the data array
1580
- data[f"p{priority_num}_{field_name}"][epoch_index, event_index] = (
1578
+ de_data[f"{field_name}"][epoch_index, priority_num, event_index] = (
1581
1579
  value
1582
1580
  )
1583
1581
  bit_position += field_components["bit_length"]
1584
1582
 
1585
- return data
1583
+ return de_data
1586
1584
 
1587
1585
 
1588
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