imap-processing 0.8.0__py3-none-any.whl → 0.9.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 (99) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/ccsds/excel_to_xtce.py +2 -0
  3. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +100 -1
  4. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +14 -0
  5. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +63 -1
  6. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +7 -0
  7. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +574 -231
  8. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +326 -0
  9. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +33 -23
  10. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +7 -4
  11. imap_processing/cdf/utils.py +3 -5
  12. imap_processing/cli.py +13 -4
  13. imap_processing/codice/codice_l1a.py +5 -5
  14. imap_processing/codice/constants.py +9 -9
  15. imap_processing/codice/decompress.py +6 -2
  16. imap_processing/glows/l1a/glows_l1a.py +1 -2
  17. imap_processing/hi/l1a/hi_l1a.py +4 -4
  18. imap_processing/hi/l1a/histogram.py +106 -108
  19. imap_processing/hi/l1a/science_direct_event.py +91 -224
  20. imap_processing/hi/packet_definitions/TLM_HI_COMBINED_SCI.xml +3994 -0
  21. imap_processing/hit/l0/constants.py +2 -2
  22. imap_processing/hit/l0/decom_hit.py +12 -101
  23. imap_processing/hit/l1a/hit_l1a.py +164 -23
  24. imap_processing/ialirt/l0/process_codicelo.py +153 -0
  25. imap_processing/ialirt/l0/process_hit.py +5 -5
  26. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +281 -0
  27. imap_processing/ialirt/process_ephemeris.py +212 -0
  28. imap_processing/idex/idex_l1a.py +55 -75
  29. imap_processing/idex/idex_l1b.py +192 -0
  30. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -0
  31. imap_processing/idex/packet_definitions/idex_packet_definition.xml +97 -595
  32. imap_processing/lo/l0/decompression_tables/decompression_tables.py +16 -0
  33. imap_processing/lo/l0/lo_science.py +44 -12
  34. imap_processing/lo/l1a/lo_l1a.py +76 -8
  35. imap_processing/lo/packet_definitions/lo_xtce.xml +9877 -87
  36. imap_processing/mag/l1a/mag_l1a.py +1 -2
  37. imap_processing/mag/l1a/mag_l1a_data.py +1 -2
  38. imap_processing/mag/l1b/mag_l1b.py +2 -1
  39. imap_processing/spice/geometry.py +37 -19
  40. imap_processing/spice/time.py +144 -2
  41. imap_processing/swapi/l1/swapi_l1.py +3 -3
  42. imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +1535 -446
  43. imap_processing/swe/l2/swe_l2.py +134 -17
  44. imap_processing/tests/ccsds/test_data/expected_output.xml +1 -1
  45. imap_processing/tests/codice/test_codice_l1a.py +8 -8
  46. imap_processing/tests/codice/test_decompress.py +4 -4
  47. imap_processing/tests/conftest.py +46 -43
  48. imap_processing/tests/hi/test_data/l0/H90_NHK_20241104.bin +0 -0
  49. imap_processing/tests/hi/test_data/l0/H90_sci_cnt_20241104.bin +0 -0
  50. imap_processing/tests/hi/test_data/l0/H90_sci_de_20241104.bin +0 -0
  51. imap_processing/tests/hi/test_hi_l1b.py +2 -2
  52. imap_processing/tests/hi/test_l1a.py +31 -58
  53. imap_processing/tests/hi/test_science_direct_event.py +58 -0
  54. imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
  55. imap_processing/tests/hit/test_decom_hit.py +60 -50
  56. imap_processing/tests/hit/test_hit_l1a.py +327 -12
  57. imap_processing/tests/hit/test_hit_l1b.py +76 -0
  58. imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +89 -0
  59. imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +29 -0
  60. imap_processing/tests/ialirt/test_data/l0/apid01152.tlm +0 -0
  61. imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  62. imap_processing/tests/ialirt/unit/test_process_codicelo.py +106 -0
  63. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +109 -0
  64. imap_processing/tests/ialirt/unit/test_process_hit.py +9 -6
  65. imap_processing/tests/idex/conftest.py +1 -1
  66. imap_processing/tests/idex/test_idex_l0.py +1 -1
  67. imap_processing/tests/idex/test_idex_l1a.py +7 -1
  68. imap_processing/tests/idex/test_idex_l1b.py +126 -0
  69. imap_processing/tests/lo/test_lo_l1a.py +7 -16
  70. imap_processing/tests/lo/test_lo_science.py +67 -3
  71. imap_processing/tests/lo/test_pkts/imap_lo_l0_raw_20240803_v002.pkts +0 -0
  72. imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SCI_DE_dec_DN_with_fills.csv +1999 -0
  73. imap_processing/tests/mag/test_mag_l1b.py +39 -5
  74. imap_processing/tests/spice/test_geometry.py +32 -6
  75. imap_processing/tests/spice/test_time.py +135 -6
  76. imap_processing/tests/swapi/test_swapi_decom.py +75 -69
  77. imap_processing/tests/swapi/test_swapi_l1.py +4 -4
  78. imap_processing/tests/swe/test_swe_l2.py +64 -8
  79. imap_processing/tests/test_utils.py +1 -1
  80. imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
  81. imap_processing/tests/ultra/unit/test_de.py +8 -3
  82. imap_processing/tests/ultra/unit/test_spatial_utils.py +125 -0
  83. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +39 -29
  84. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +2 -25
  85. imap_processing/ultra/constants.py +4 -0
  86. imap_processing/ultra/l1b/de.py +8 -14
  87. imap_processing/ultra/l1b/ultra_l1b_extended.py +29 -70
  88. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +1 -36
  89. imap_processing/ultra/utils/spatial_utils.py +221 -0
  90. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/METADATA +1 -1
  91. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/RECORD +94 -76
  92. imap_processing/hi/l0/__init__.py +0 -0
  93. imap_processing/hi/l0/decom_hi.py +0 -24
  94. imap_processing/hi/packet_definitions/hi_packet_definition.xml +0 -482
  95. imap_processing/tests/hi/test_decom.py +0 -55
  96. imap_processing/tests/hi/test_l1a_sci_de.py +0 -72
  97. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/LICENSE +0 -0
  98. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/WHEEL +0 -0
  99. {imap_processing-0.8.0.dist-info → imap_processing-0.9.0.dist-info}/entry_points.txt +0 -0
@@ -184,7 +184,7 @@ DATA_PRODUCT_CONFIGURATIONS = {
184
184
  "energy_label",
185
185
  ], # TODO: These will likely change
186
186
  "dataset_name": "imap_codice_l1a_lo-counters-aggregated",
187
- "dims": ["epoch", "inst_az", "spin_sector", "esa_step"],
187
+ "dims": ["epoch", "esa_step", "inst_az", "spin_sector"],
188
188
  "instrument": "lo",
189
189
  "num_counters": 1,
190
190
  "num_energy_steps": 128,
@@ -205,7 +205,7 @@ DATA_PRODUCT_CONFIGURATIONS = {
205
205
  "energy_label",
206
206
  ], # TODO: These will likely change
207
207
  "dataset_name": "imap_codice_l1a_lo-counters-singles",
208
- "dims": ["epoch", "inst_az", "spin_sector", "esa_step"],
208
+ "dims": ["epoch", "esa_step", "inst_az", "spin_sector"],
209
209
  "instrument": "lo",
210
210
  "num_counters": 1,
211
211
  "num_energy_steps": 128,
@@ -225,9 +225,9 @@ DATA_PRODUCT_CONFIGURATIONS = {
225
225
  "variable_names": LO_INST_COUNTS_SINGLES_VARIABLE_NAMES,
226
226
  },
227
227
  CODICEAPID.COD_LO_SW_ANGULAR_COUNTS: {
228
- "coords": ["epoch", "inst_az", "spin_sector", "esa_step", "energy_label"],
228
+ "coords": ["epoch", "energy_label", "esa_step", "inst_az", "spin_sector"],
229
229
  "dataset_name": "imap_codice_l1a_lo-sw-angular",
230
- "dims": ["epoch", "inst_az", "spin_sector", "esa_step"],
230
+ "dims": ["epoch", "esa_step", "inst_az", "spin_sector"],
231
231
  "instrument": "lo",
232
232
  "num_counters": 4,
233
233
  "num_energy_steps": 128,
@@ -248,7 +248,7 @@ DATA_PRODUCT_CONFIGURATIONS = {
248
248
  CODICEAPID.COD_LO_NSW_ANGULAR_COUNTS: {
249
249
  "coords": ["epoch", "inst_az", "spin_sector", "esa_step", "energy_label"],
250
250
  "dataset_name": "imap_codice_l1a_lo-nsw-angular",
251
- "dims": ["epoch", "inst_az", "spin_sector", "esa_step"],
251
+ "dims": ["epoch", "esa_step", "inst_az", "spin_sector"],
252
252
  "instrument": "lo",
253
253
  "num_counters": 1,
254
254
  "num_energy_steps": 128,
@@ -269,7 +269,7 @@ DATA_PRODUCT_CONFIGURATIONS = {
269
269
  CODICEAPID.COD_LO_SW_PRIORITY_COUNTS: {
270
270
  "coords": ["epoch", "inst_az", "spin_sector", "esa_step", "energy_label"],
271
271
  "dataset_name": "imap_codice_l1a_lo-sw-priority",
272
- "dims": ["epoch", "inst_az", "spin_sector", "esa_step"],
272
+ "dims": ["epoch", "esa_step", "inst_az", "spin_sector"],
273
273
  "instrument": "lo",
274
274
  "num_counters": 5,
275
275
  "num_energy_steps": 128,
@@ -290,7 +290,7 @@ DATA_PRODUCT_CONFIGURATIONS = {
290
290
  CODICEAPID.COD_LO_NSW_PRIORITY_COUNTS: {
291
291
  "coords": ["epoch", "inst_az", "spin_sector", "esa_step", "energy_label"],
292
292
  "dataset_name": "imap_codice_l1a_lo-nsw-priority",
293
- "dims": ["epoch", "inst_az", "spin_sector", "esa_step"],
293
+ "dims": ["epoch", "esa_step", "inst_az", "spin_sector"],
294
294
  "instrument": "lo",
295
295
  "num_counters": 2,
296
296
  "num_energy_steps": 128,
@@ -311,7 +311,7 @@ DATA_PRODUCT_CONFIGURATIONS = {
311
311
  CODICEAPID.COD_LO_SW_SPECIES_COUNTS: {
312
312
  "coords": ["epoch", "inst_az", "spin_sector", "esa_step", "energy_label"],
313
313
  "dataset_name": "imap_codice_l1a_lo-sw-species",
314
- "dims": ["epoch", "inst_az", "spin_sector", "esa_step"],
314
+ "dims": ["epoch", "esa_step", "inst_az", "spin_sector"],
315
315
  "instrument": "lo",
316
316
  "num_counters": 16,
317
317
  "num_energy_steps": 128,
@@ -332,7 +332,7 @@ DATA_PRODUCT_CONFIGURATIONS = {
332
332
  CODICEAPID.COD_LO_NSW_SPECIES_COUNTS: {
333
333
  "coords": ["epoch", "inst_az", "spin_sector", "esa_step", "energy_label"],
334
334
  "dataset_name": "imap_codice_l1a_lo-nsw-species",
335
- "dims": ["epoch", "inst_az", "spin_sector", "esa_step"],
335
+ "dims": ["epoch", "esa_step", "inst_az", "spin_sector"],
336
336
  "instrument": "lo",
337
337
  "num_counters": 8,
338
338
  "num_energy_steps": 128,
@@ -50,7 +50,9 @@ def _apply_lossy_a(compressed_bytes: bytes) -> list[int]:
50
50
  The 24- or 32-bit decompressed values.
51
51
  """
52
52
  compressed_values = list(compressed_bytes)
53
- decompressed_values = [LOSSY_A_TABLE[item] for item in compressed_values]
53
+ decompressed_values = [
54
+ LOSSY_A_TABLE[item - 1] if item > 0 else 0 for item in compressed_values
55
+ ]
54
56
  return decompressed_values
55
57
 
56
58
 
@@ -71,7 +73,9 @@ def _apply_lossy_b(compressed_bytes: bytes) -> list[int]:
71
73
  The 24- or 32-bit decompressed values.
72
74
  """
73
75
  compressed_values = list(compressed_bytes)
74
- decompressed_values = [LOSSY_B_TABLE[item] for item in compressed_values]
76
+ decompressed_values = [
77
+ LOSSY_B_TABLE[item - 1] if item > 0 else 0 for item in compressed_values
78
+ ]
75
79
  return decompressed_values
76
80
 
77
81
 
@@ -7,11 +7,10 @@ import numpy as np
7
7
  import xarray as xr
8
8
 
9
9
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
10
- from imap_processing.cdf.utils import J2000_EPOCH
11
10
  from imap_processing.glows.l0.decom_glows import decom_packets
12
11
  from imap_processing.glows.l0.glows_l0_data import DirectEventL0
13
12
  from imap_processing.glows.l1a.glows_l1a_data import DirectEventL1A, HistogramL1A
14
- from imap_processing.spice.time import met_to_j2000ns
13
+ from imap_processing.spice.time import J2000_EPOCH, met_to_j2000ns
15
14
 
16
15
 
17
16
  def create_glows_attr_obj(data_version: str) -> ImapCdfAttributes:
@@ -33,7 +33,7 @@ def hi_l1a(packet_file_path: Union[str, Path], data_version: str) -> list[xr.Dat
33
33
  List of processed xarray dataset.
34
34
  """
35
35
  packet_def_file = (
36
- imap_module_directory / "hi/packet_definitions/hi_packet_definition.xml"
36
+ imap_module_directory / "hi/packet_definitions/TLM_HI_COMBINED_SCI.xml"
37
37
  )
38
38
  datasets_by_apid = packet_file_to_datasets(
39
39
  packet_file=packet_file_path, xtce_packet_definition=packet_def_file
@@ -42,18 +42,18 @@ def hi_l1a(packet_file_path: Union[str, Path], data_version: str) -> list[xr.Dat
42
42
  # Process science to l1a.
43
43
  processed_data = []
44
44
  for apid in datasets_by_apid:
45
- if apid == HIAPID.H45_SCI_CNT:
45
+ if apid in [HIAPID.H45_SCI_CNT, HIAPID.H90_SCI_CNT]:
46
46
  logger.info(
47
47
  "Processing histogram data for [%s] packets", HIAPID.H45_SCI_CNT.name
48
48
  )
49
49
  data = hist_create_dataset(datasets_by_apid[apid])
50
- elif apid == HIAPID.H45_SCI_DE:
50
+ elif apid in [HIAPID.H45_SCI_DE, HIAPID.H90_SCI_DE]:
51
51
  logger.info(
52
52
  "Processing direct event data for [%s] packets", HIAPID.H45_SCI_DE.name
53
53
  )
54
54
 
55
55
  data = science_direct_event(datasets_by_apid[apid])
56
- elif apid == HIAPID.H45_APP_NHK:
56
+ elif apid in [HIAPID.H45_APP_NHK, HIAPID.H90_APP_NHK]:
57
57
  logger.info(
58
58
  "Processing housekeeping data for [%s] packets", HIAPID.H45_APP_NHK.name
59
59
  )
@@ -2,36 +2,36 @@
2
2
 
3
3
  import numpy as np
4
4
  import xarray as xr
5
+ from numpy._typing import NDArray
5
6
 
6
7
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
7
- from imap_processing.utils import convert_to_binary_string
8
8
 
9
9
  # define the names of the 24 counter arrays
10
10
  # contained in the histogram packet
11
11
  QUALIFIED_COUNTERS = (
12
- "qual_ab",
13
- "qual_c1c2",
14
- "qual_ac1",
15
- "qual_bc1",
16
- "qual_abc1",
17
- "qual_ac1c2",
18
- "qual_bc1c2",
19
- "qual_abc1c2",
12
+ "ab_qualified",
13
+ "c1c2_qualified",
14
+ "ac1_qualified",
15
+ "bc1_qualified",
16
+ "abc1_qualified",
17
+ "ac1c2_qualified",
18
+ "bc1c2_qualified",
19
+ "abc1c2_qualified",
20
20
  )
21
21
  LONG_COUNTERS = (
22
- "long_a",
23
- "long_b",
24
- "long_c",
25
- "long_ab",
26
- "long_c1c2",
27
- "long_ac1",
28
- "long_bc1",
29
- "long_abc1",
30
- "long_ac1c2",
31
- "long_bc1c2",
32
- "long_abc1c2",
22
+ "a_first_only",
23
+ "b_first_only",
24
+ "c_first_only",
25
+ "ab_long",
26
+ "c1c2_long",
27
+ "ac1_long",
28
+ "bc1_long",
29
+ "abc1_long",
30
+ "ac1c2_long",
31
+ "bc1c2_long",
32
+ "abc1c2_long",
33
33
  )
34
- TOTAL_COUNTERS = ("total_a", "total_b", "total_c", "fee_de_sent", "fee_de_recd")
34
+ TOTAL_COUNTERS = ("a_total", "b_total", "c_total", "fee_de_recd", "fee_de_sent")
35
35
 
36
36
 
37
37
  def create_dataset(input_ds: xr.Dataset) -> xr.Dataset:
@@ -41,99 +41,56 @@ def create_dataset(input_ds: xr.Dataset) -> xr.Dataset:
41
41
  Parameters
42
42
  ----------
43
43
  input_ds : xarray.Dataset
44
- Dataset of packets.
44
+ Dataset of packets generated using the
45
+ `imap_processing.utils.packet_file_to_datasets` function.
45
46
 
46
47
  Returns
47
48
  -------
48
49
  dataset : xarray.Dataset
49
50
  Dataset with all metadata field data in xr.DataArray.
50
51
  """
51
- dataset = allocate_histogram_dataset(len(input_ds.epoch))
52
-
53
- # TODO: Move into the allocate dataset function?
54
- dataset["epoch"].data[:] = input_ds["epoch"].data
55
- dataset["ccsds_met"].data = input_ds["ccsds_met"].data
56
- dataset["esa_stepping_num"].data = input_ds["esa_step"].data
57
-
58
- # unpack the packets data into the Dataset
59
- # (npackets, 24 * 90 * 12)
60
- # TODO: Look into avoiding the for-loops below
61
- # It seems like we could try to reshape the arrays and do some numpy
62
- # broadcasting rather than for-loops directly here
63
- for i_epoch, counters_bytes_data in enumerate(input_ds["counters"].data):
64
- binary_str_val = convert_to_binary_string(counters_bytes_data)
65
- # unpack 24 arrays of 90 12-bit unsigned integers
66
- counter_ints = [
67
- int(binary_str_val[i * 12 : (i + 1) * 12], 2) for i in range(90 * 24)
68
- ]
69
- # populate the dataset with the unpacked integers
70
- for i_counter, counter in enumerate(
71
- (*QUALIFIED_COUNTERS, *LONG_COUNTERS, *TOTAL_COUNTERS)
72
- ):
73
- dataset[counter][i_epoch] = counter_ints[
74
- i_counter * 90 : (i_counter + 1) * 90
75
- ]
76
-
77
- return dataset
78
-
79
-
80
- def allocate_histogram_dataset(num_packets: int) -> xr.Dataset:
81
- """
82
- Allocate empty xarray.Dataset for specified number of Hi Histogram packets.
83
-
84
- Parameters
85
- ----------
86
- num_packets : int
87
- The number of Hi Histogram packets to allocate space for
88
- in the xarray.Dataset.
89
-
90
- Returns
91
- -------
92
- dataset : xarray.Dataset
93
- Empty xarray.Dataset ready to be filled with packet data.
94
- """
95
52
  attr_mgr = ImapCdfAttributes()
96
53
  attr_mgr.add_instrument_global_attrs(instrument="hi")
97
54
  attr_mgr.add_instrument_variable_attrs(instrument="hi", level=None)
98
- # preallocate the xr.DataArrays for all CDF attributes based on number of packets
99
- coords = dict()
100
- coords["epoch"] = xr.DataArray(
101
- np.empty(num_packets, dtype="datetime64[ns]"),
102
- name="epoch",
103
- dims=["epoch"],
104
- attrs=attr_mgr.get_variable_attributes("epoch"),
105
- )
106
- # Histogram data is binned in 90, 4-degree bins
107
- coords["angle"] = xr.DataArray(
108
- np.arange(2, 360, 4),
109
- name="angle",
110
- dims=["angle"],
111
- attrs=attr_mgr.get_variable_attributes("hi_hist_angle"),
112
- )
113
55
 
114
- data_vars = dict()
115
- # Generate label variables
116
- data_vars["angle_label"] = xr.DataArray(
117
- coords["angle"].values.astype(str),
118
- name="angle_label",
119
- dims=["angle"],
120
- attrs=attr_mgr.get_variable_attributes(
121
- "hi_hist_angle_label", check_schema=False
122
- ),
123
- )
124
- # Other data variables
125
- data_vars["ccsds_met"] = xr.DataArray(
126
- np.empty(num_packets, dtype=np.uint32),
127
- dims=["epoch"],
128
- attrs=attr_mgr.get_variable_attributes("hi_hist_ccsds_met"),
56
+ # Rename shcoarse variable (do this first since it copies the input_ds)
57
+ dataset = input_ds.rename_vars({"shcoarse": "ccsds_met"})
58
+
59
+ dataset.epoch.attrs.update(
60
+ attr_mgr.get_variable_attributes("epoch"),
129
61
  )
130
- data_vars["esa_stepping_num"] = xr.DataArray(
131
- np.empty(num_packets, dtype=np.uint8),
132
- dims=["epoch"],
133
- attrs=attr_mgr.get_variable_attributes("hi_hist_esa_step"),
62
+ # Add the hist_angle coordinate
63
+ # Histogram data is binned in 90, 4-degree bins
64
+ attrs = attr_mgr.get_variable_attributes("hi_hist_angle")
65
+ dataset.coords.update(
66
+ {
67
+ "angle": xr.DataArray(
68
+ np.arange(2, 360, 4),
69
+ name="angle",
70
+ dims=["angle"],
71
+ attrs=attrs,
72
+ )
73
+ }
134
74
  )
135
-
136
- # Allocate xarray.DataArray objects for the 24 90-element histogram counters
75
+ # Update existing variable attributes
76
+ for var_name in [
77
+ "version",
78
+ "type",
79
+ "sec_hdr_flg",
80
+ "pkt_apid",
81
+ "seq_flgs",
82
+ "src_seq_ctr",
83
+ "pkt_len",
84
+ "ccsds_met",
85
+ "esa_step",
86
+ "num_of_spins",
87
+ "cksum",
88
+ ]:
89
+ attrs = attr_mgr.get_variable_attributes(f"hi_hist_{var_name}")
90
+ dataset.data_vars[var_name].attrs.update(attrs)
91
+
92
+ new_vars = dict()
93
+ # Populate 90-element histogram counters
137
94
  default_counter_attrs = attr_mgr.get_variable_attributes("hi_hist_counters")
138
95
  for counter_name in (*QUALIFIED_COUNTERS, *LONG_COUNTERS, *TOTAL_COUNTERS):
139
96
  # Inject counter name into generic counter attributes
@@ -141,15 +98,56 @@ def allocate_histogram_dataset(num_packets: int) -> xr.Dataset:
141
98
  for key, val in counter_attrs.items():
142
99
  if isinstance(val, str) and "{counter_name}" in val:
143
100
  counter_attrs[key] = val.format(counter_name=counter_name)
144
- data_vars[counter_name] = xr.DataArray(
145
- data=np.empty((num_packets, len(coords["angle"])), np.uint16),
101
+ # Instantiate the counter DataArray
102
+ new_vars[counter_name] = xr.DataArray(
103
+ data=unpack_hist_counter(input_ds[counter_name].data.sum()),
146
104
  dims=["epoch", "angle"],
147
105
  attrs=counter_attrs,
148
106
  )
149
107
 
150
- dataset = xr.Dataset(
151
- data_vars=data_vars,
152
- coords=coords,
153
- attrs=attr_mgr.get_global_attributes("imap_hi_l1a_hist_attrs"),
108
+ # Generate label variable for angle coordinate
109
+ new_vars["angle_label"] = xr.DataArray(
110
+ dataset.coords["angle"].values.astype(str),
111
+ name="angle_label",
112
+ dims=["angle"],
113
+ attrs=attr_mgr.get_variable_attributes(
114
+ "hi_hist_angle_label", check_schema=False
115
+ ),
154
116
  )
117
+
118
+ dataset.update(new_vars)
119
+ dataset.attrs.update(attr_mgr.get_global_attributes("imap_hi_l1a_hist_attrs"))
120
+
155
121
  return dataset
122
+
123
+
124
+ def unpack_hist_counter(counter_bytes: bytes) -> NDArray[np.uint16]:
125
+ """
126
+ Unpack Hi SCI_CNT counter data for a single counter.
127
+
128
+ Parameters
129
+ ----------
130
+ counter_bytes : bytes
131
+ Sum individual bytes for all epochs of a Hi SCI_CNT counter.
132
+
133
+ Returns
134
+ -------
135
+ output_array : numpy.ndarray[numpy.uint16]
136
+ The unpacked 12-bit unsigned integers for the input bytes. The
137
+ output array has a shape of (n, 90) where n is the number of SCI_CNT
138
+ packets in the input dataset.
139
+ """
140
+ # Interpret bytes for all epochs of current counter as uint8 array
141
+ counter_uint8 = np.frombuffer(counter_bytes, dtype=np.uint8)
142
+ # Split into triplets of upper-byte, split-byte and lower-byte arrays
143
+ upper_uint8, split_unit8, lower_uint8 = np.reshape(
144
+ counter_uint8, (3, -1), order="F"
145
+ ).astype(np.uint16)
146
+ # Compute even indexed uint12 values from upper-byte and first 4-bits of
147
+ # split-byte
148
+ even_uint12 = (upper_uint8 << 4) + (split_unit8 >> 4)
149
+ # Compute odd indexed uint12 values from lower 4-bits of split-byte and
150
+ # lower-byte
151
+ odd_uint12 = ((split_unit8 & (2**4 - 1)) << 8) + lower_uint8
152
+ output_array = np.column_stack((even_uint12, odd_uint12)).reshape(-1, 90)
153
+ return output_array