imap-processing 0.7.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 (172) hide show
  1. imap_processing/__init__.py +1 -1
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/excel_to_xtce.py +36 -2
  4. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +1 -1
  5. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +145 -30
  6. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +36 -36
  7. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +136 -9
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +14 -0
  9. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +63 -1
  10. imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +9 -0
  11. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +14 -7
  12. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +577 -235
  13. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +326 -0
  14. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +33 -23
  15. imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +24 -28
  16. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +1 -0
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +137 -79
  18. imap_processing/cdf/config/imap_variable_schema.yaml +13 -0
  19. imap_processing/cdf/imap_cdf_manager.py +31 -27
  20. imap_processing/cdf/utils.py +3 -5
  21. imap_processing/cli.py +25 -14
  22. imap_processing/codice/codice_l1a.py +153 -63
  23. imap_processing/codice/constants.py +10 -10
  24. imap_processing/codice/decompress.py +10 -11
  25. imap_processing/codice/utils.py +1 -0
  26. imap_processing/glows/l1a/glows_l1a.py +1 -2
  27. imap_processing/glows/l1b/glows_l1b.py +3 -3
  28. imap_processing/glows/l1b/glows_l1b_data.py +59 -37
  29. imap_processing/glows/l2/glows_l2_data.py +123 -0
  30. imap_processing/hi/l1a/hi_l1a.py +4 -4
  31. imap_processing/hi/l1a/histogram.py +107 -109
  32. imap_processing/hi/l1a/science_direct_event.py +92 -225
  33. imap_processing/hi/l1b/hi_l1b.py +85 -11
  34. imap_processing/hi/l1c/hi_l1c.py +23 -1
  35. imap_processing/hi/packet_definitions/TLM_HI_COMBINED_SCI.xml +3994 -0
  36. imap_processing/hi/utils.py +1 -1
  37. imap_processing/hit/hit_utils.py +221 -0
  38. imap_processing/hit/l0/constants.py +118 -0
  39. imap_processing/hit/l0/decom_hit.py +100 -156
  40. imap_processing/hit/l1a/hit_l1a.py +170 -184
  41. imap_processing/hit/l1b/hit_l1b.py +33 -153
  42. imap_processing/ialirt/l0/process_codicelo.py +153 -0
  43. imap_processing/ialirt/l0/process_hit.py +5 -5
  44. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +281 -0
  45. imap_processing/ialirt/process_ephemeris.py +212 -0
  46. imap_processing/idex/idex_l1a.py +65 -84
  47. imap_processing/idex/idex_l1b.py +192 -0
  48. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -0
  49. imap_processing/idex/packet_definitions/idex_packet_definition.xml +97 -595
  50. imap_processing/lo/l0/decompression_tables/decompression_tables.py +17 -1
  51. imap_processing/lo/l0/lo_science.py +45 -13
  52. imap_processing/lo/l1a/lo_l1a.py +76 -8
  53. imap_processing/lo/packet_definitions/lo_xtce.xml +8344 -1849
  54. imap_processing/mag/l0/decom_mag.py +4 -3
  55. imap_processing/mag/l1a/mag_l1a.py +12 -13
  56. imap_processing/mag/l1a/mag_l1a_data.py +1 -2
  57. imap_processing/mag/l1b/mag_l1b.py +90 -7
  58. imap_processing/spice/geometry.py +156 -16
  59. imap_processing/spice/time.py +144 -2
  60. imap_processing/swapi/l1/swapi_l1.py +4 -4
  61. imap_processing/swapi/l2/swapi_l2.py +1 -1
  62. imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +1535 -446
  63. imap_processing/swe/l1b/swe_l1b_science.py +8 -8
  64. imap_processing/swe/l2/swe_l2.py +134 -17
  65. imap_processing/tests/ccsds/test_data/expected_output.xml +2 -1
  66. imap_processing/tests/ccsds/test_excel_to_xtce.py +4 -4
  67. imap_processing/tests/cdf/test_imap_cdf_manager.py +0 -10
  68. imap_processing/tests/codice/conftest.py +1 -17
  69. imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
  70. imap_processing/tests/codice/test_codice_l0.py +8 -2
  71. imap_processing/tests/codice/test_codice_l1a.py +127 -107
  72. imap_processing/tests/codice/test_codice_l1b.py +1 -0
  73. imap_processing/tests/codice/test_decompress.py +7 -7
  74. imap_processing/tests/conftest.py +100 -58
  75. imap_processing/tests/glows/conftest.py +6 -0
  76. imap_processing/tests/glows/test_glows_l1b.py +9 -9
  77. imap_processing/tests/glows/test_glows_l1b_data.py +9 -9
  78. imap_processing/tests/hi/test_data/l0/H90_NHK_20241104.bin +0 -0
  79. imap_processing/tests/hi/test_data/l0/H90_sci_cnt_20241104.bin +0 -0
  80. imap_processing/tests/hi/test_data/l0/H90_sci_de_20241104.bin +0 -0
  81. imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
  82. imap_processing/tests/hi/test_hi_l1b.py +73 -3
  83. imap_processing/tests/hi/test_hi_l1c.py +10 -2
  84. imap_processing/tests/hi/test_l1a.py +31 -58
  85. imap_processing/tests/hi/test_science_direct_event.py +58 -0
  86. imap_processing/tests/hi/test_utils.py +4 -3
  87. imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
  88. imap_processing/tests/hit/{test_hit_decom.py → test_decom_hit.py} +95 -36
  89. imap_processing/tests/hit/test_hit_l1a.py +299 -179
  90. imap_processing/tests/hit/test_hit_l1b.py +231 -24
  91. imap_processing/tests/hit/test_hit_utils.py +218 -0
  92. imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +89 -0
  93. imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +29 -0
  94. imap_processing/tests/ialirt/test_data/l0/apid01152.tlm +0 -0
  95. imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  96. imap_processing/tests/ialirt/unit/test_process_codicelo.py +106 -0
  97. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +109 -0
  98. imap_processing/tests/ialirt/unit/test_process_hit.py +9 -6
  99. imap_processing/tests/idex/conftest.py +2 -2
  100. imap_processing/tests/idex/imap_idex_l0_raw_20231214_v001.pkts +0 -0
  101. imap_processing/tests/idex/impact_14_tof_high_data.txt +4444 -4444
  102. imap_processing/tests/idex/test_idex_l0.py +4 -4
  103. imap_processing/tests/idex/test_idex_l1a.py +8 -2
  104. imap_processing/tests/idex/test_idex_l1b.py +126 -0
  105. imap_processing/tests/lo/test_lo_l1a.py +7 -16
  106. imap_processing/tests/lo/test_lo_science.py +69 -5
  107. imap_processing/tests/lo/test_pkts/imap_lo_l0_raw_20240803_v002.pkts +0 -0
  108. imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SCI_DE_dec_DN_with_fills.csv +1999 -0
  109. imap_processing/tests/mag/imap_mag_l1a_norm-magi_20251017_v001.cdf +0 -0
  110. imap_processing/tests/mag/test_mag_l1b.py +97 -7
  111. imap_processing/tests/spice/test_data/imap_ena_sim_metakernel.template +3 -1
  112. imap_processing/tests/spice/test_geometry.py +115 -9
  113. imap_processing/tests/spice/test_time.py +135 -6
  114. imap_processing/tests/swapi/test_swapi_decom.py +75 -69
  115. imap_processing/tests/swapi/test_swapi_l1.py +4 -4
  116. imap_processing/tests/swe/conftest.py +33 -0
  117. imap_processing/tests/swe/l1_validation/swe_l0_unpacked-data_20240510_v001_VALIDATION_L1B_v3.dat +4332 -0
  118. imap_processing/tests/swe/test_swe_l1b.py +29 -8
  119. imap_processing/tests/swe/test_swe_l2.py +64 -8
  120. imap_processing/tests/test_utils.py +2 -2
  121. imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
  122. imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E12.cdf +0 -0
  123. imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E24.cdf +0 -0
  124. imap_processing/tests/ultra/unit/test_de.py +113 -0
  125. imap_processing/tests/ultra/unit/test_spatial_utils.py +125 -0
  126. imap_processing/tests/ultra/unit/test_ultra_l1b.py +27 -3
  127. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +31 -10
  128. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +55 -35
  129. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +10 -68
  130. imap_processing/ultra/constants.py +12 -3
  131. imap_processing/ultra/l1b/de.py +168 -30
  132. imap_processing/ultra/l1b/ultra_l1b_annotated.py +24 -10
  133. imap_processing/ultra/l1b/ultra_l1b_extended.py +46 -80
  134. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +60 -144
  135. imap_processing/ultra/utils/spatial_utils.py +221 -0
  136. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/METADATA +15 -14
  137. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/RECORD +142 -139
  138. imap_processing/cdf/cdf_attribute_manager.py +0 -322
  139. imap_processing/cdf/config/shared/default_global_cdf_attrs_schema.yaml +0 -246
  140. imap_processing/cdf/config/shared/default_variable_cdf_attrs_schema.yaml +0 -466
  141. imap_processing/hi/l0/decom_hi.py +0 -24
  142. imap_processing/hi/packet_definitions/hi_packet_definition.xml +0 -482
  143. imap_processing/hit/l0/data_classes/housekeeping.py +0 -240
  144. imap_processing/hit/l0/data_classes/science_packet.py +0 -259
  145. imap_processing/hit/l0/utils/hit_base.py +0 -57
  146. imap_processing/tests/cdf/shared/default_global_cdf_attrs_schema.yaml +0 -246
  147. imap_processing/tests/cdf/shared/default_variable_cdf_attrs_schema.yaml +0 -466
  148. imap_processing/tests/cdf/test_cdf_attribute_manager.py +0 -353
  149. imap_processing/tests/codice/data/imap_codice_l0_hi-counters-aggregated_20240429_v001.pkts +0 -0
  150. imap_processing/tests/codice/data/imap_codice_l0_hi-counters-singles_20240429_v001.pkts +0 -0
  151. imap_processing/tests/codice/data/imap_codice_l0_hi-omni_20240429_v001.pkts +0 -0
  152. imap_processing/tests/codice/data/imap_codice_l0_hi-pha_20240429_v001.pkts +0 -0
  153. imap_processing/tests/codice/data/imap_codice_l0_hi-sectored_20240429_v001.pkts +0 -0
  154. imap_processing/tests/codice/data/imap_codice_l0_hskp_20100101_v001.pkts +0 -0
  155. imap_processing/tests/codice/data/imap_codice_l0_lo-counters-aggregated_20240429_v001.pkts +0 -0
  156. imap_processing/tests/codice/data/imap_codice_l0_lo-counters-singles_20240429_v001.pkts +0 -0
  157. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-angular_20240429_v001.pkts +0 -0
  158. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-priority_20240429_v001.pkts +0 -0
  159. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-species_20240429_v001.pkts +0 -0
  160. imap_processing/tests/codice/data/imap_codice_l0_lo-pha_20240429_v001.pkts +0 -0
  161. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-angular_20240429_v001.pkts +0 -0
  162. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-priority_20240429_v001.pkts +0 -0
  163. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-species_20240429_v001.pkts +0 -0
  164. imap_processing/tests/hi/test_decom.py +0 -55
  165. imap_processing/tests/hi/test_l1a_sci_de.py +0 -72
  166. imap_processing/tests/idex/imap_idex_l0_raw_20230725_v001.pkts +0 -0
  167. imap_processing/tests/mag/imap_mag_l1a_burst-magi_20231025_v001.cdf +0 -0
  168. /imap_processing/{hi/l0/__init__.py → tests/glows/test_glows_l2_data.py} +0 -0
  169. /imap_processing/tests/hit/test_data/{imap_hit_l0_hk_20100105_v001.pkts → imap_hit_l0_raw_20100105_v001.pkts} +0 -0
  170. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/LICENSE +0 -0
  171. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/WHEEL +0 -0
  172. {imap_processing-0.7.0.dist-info → imap_processing-0.9.0.dist-info}/entry_points.txt +0 -0
@@ -1,109 +1,16 @@
1
1
  """Decommutate HIT CCSDS science data."""
2
2
 
3
- from collections import namedtuple
4
-
5
3
  import numpy as np
6
4
  import xarray as xr
7
5
 
8
- from imap_processing.utils import convert_to_binary_string
9
-
10
- # TODO: Consider moving global values into a config file
11
-
12
- # Structure to hold binary details for a
13
- # section of science data. Used to unpack
14
- # binary data.
15
- HITPacking = namedtuple(
16
- "HITPacking",
17
- [
18
- "bit_length",
19
- "section_length",
20
- "shape",
21
- ],
6
+ from imap_processing.hit.l0.constants import (
7
+ COUNTS_DATA_STRUCTURE,
8
+ EXPONENT_BITS,
9
+ FLAG_PATTERN,
10
+ FRAME_SIZE,
11
+ MANTISSA_BITS,
22
12
  )
23
-
24
- # Define data structure for counts rates data
25
- COUNTS_DATA_STRUCTURE = {
26
- # field: bit_length, section_length, shape
27
- # ------------------------------------------
28
- # science frame header
29
- "hdr_unit_num": HITPacking(2, 2, (1,)),
30
- "hdr_frame_version": HITPacking(6, 6, (1,)),
31
- "hdr_status_bits": HITPacking(8, 8, (1,)),
32
- "hdr_minute_cnt": HITPacking(8, 8, (1,)),
33
- # ------------------------------------------
34
- # spare bits. Contains no data
35
- "spare": HITPacking(24, 24, (1,)),
36
- # ------------------------------------------
37
- # erates - contains livetime counters
38
- "livetime": HITPacking(16, 16, (1,)), # livetime counter
39
- "num_trig": HITPacking(16, 16, (1,)), # number of triggers
40
- "num_reject": HITPacking(16, 16, (1,)), # number of rejected events
41
- "num_acc_w_pha": HITPacking(
42
- 16, 16, (1,)
43
- ), # number of accepted events with PHA data
44
- "num_acc_no_pha": HITPacking(16, 16, (1,)), # number of events without PHA data
45
- "num_haz_trig": HITPacking(16, 16, (1,)), # number of triggers with hazard flag
46
- "num_haz_reject": HITPacking(
47
- 16, 16, (1,)
48
- ), # number of rejected events with hazard flag
49
- "num_haz_acc_w_pha": HITPacking(
50
- 16, 16, (1,)
51
- ), # number of accepted hazard events with PHA data
52
- "num_haz_acc_no_pha": HITPacking(
53
- 16, 16, (1,)
54
- ), # number of hazard events without PHA data
55
- # -------------------------------------------
56
- "sngrates": HITPacking(16, 1856, (2, 58)), # single rates
57
- # -------------------------------------------
58
- # evprates - contains event processing rates
59
- "nread": HITPacking(16, 16, (1,)), # events read from event fifo
60
- "nhazard": HITPacking(16, 16, (1,)), # events tagged with hazard flag
61
- "nadcstim": HITPacking(16, 16, (1,)), # adc-stim events
62
- "nodd": HITPacking(16, 16, (1,)), # odd events
63
- "noddfix": HITPacking(16, 16, (1,)), # odd events that were fixed in sw
64
- "nmulti": HITPacking(
65
- 16, 16, (1,)
66
- ), # events with multiple hits in a single detector
67
- "nmultifix": HITPacking(16, 16, (1,)), # multi events that were fixed in sw
68
- "nbadtraj": HITPacking(16, 16, (1,)), # bad trajectory
69
- "nl2": HITPacking(16, 16, (1,)), # events sorted into L12 event category
70
- "nl3": HITPacking(16, 16, (1,)), # events sorted into L123 event category
71
- "nl4": HITPacking(16, 16, (1,)), # events sorted into L1423 event category
72
- "npen": HITPacking(16, 16, (1,)), # events sorted into penetrating event category
73
- "nformat": HITPacking(16, 16, (1,)), # nothing currently goes in this slot
74
- "naside": HITPacking(16, 16, (1,)), # A-side events
75
- "nbside": HITPacking(16, 16, (1,)), # B-side events
76
- "nerror": HITPacking(16, 16, (1,)), # events that caused a processing error
77
- "nbadtags": HITPacking(
78
- 16, 16, (1,)
79
- ), # events with inconsistent tags vs pulse heights
80
- # -------------------------------------------
81
- # other count rates
82
- "coinrates": HITPacking(16, 416, (26,)), # coincidence rates
83
- "bufrates": HITPacking(16, 512, (32,)), # priority buffer rates
84
- "l2fgrates": HITPacking(16, 2112, (132,)), # range 2 foreground rates
85
- "l2bgrates": HITPacking(16, 192, (12,)), # range 2 background rates
86
- "l3fgrates": HITPacking(16, 2672, (167,)), # range 3 foreground rates
87
- "l3bgrates": HITPacking(16, 192, (12,)), # range 3 background rates
88
- "penfgrates": HITPacking(16, 528, (33,)), # range 4 foreground rates
89
- "penbgrates": HITPacking(16, 240, (15,)), # range 4 background rates
90
- "ialirtrates": HITPacking(16, 320, (20,)), # ialirt rates
91
- "sectorates": HITPacking(16, 1920, (120,)), # sectored rates
92
- "l4fgrates": HITPacking(16, 768, (48,)), # all range foreground rates
93
- "l4bgrates": HITPacking(16, 384, (24,)), # all range foreground rates
94
- }
95
-
96
- # Define data structure for pulse height event data
97
- PHA_DATA_STRUCTURE = {
98
- # field: bit_length, section_length, shape
99
- "pha_records": HITPacking(2, 29344, (917,)),
100
- }
101
-
102
- # Define the pattern of grouping flags in a complete science frame.
103
- FLAG_PATTERN = np.array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2])
104
-
105
- # Define size of science frame (num of packets)
106
- FRAME_SIZE = len(FLAG_PATTERN)
13
+ from imap_processing.utils import convert_to_binary_string
107
14
 
108
15
 
109
16
  def parse_data(bin_str: str, bits_per_index: int, start: int, end: int) -> list:
@@ -138,13 +45,13 @@ def parse_count_rates(sci_dataset: xr.Dataset) -> None:
138
45
  Parse binary count rates data and update dataset.
139
46
 
140
47
  This function parses the binary count rates data,
141
- stored as count_rates_binary in the dataset,
48
+ stored as count_rates_raw in the dataset,
142
49
  according to data structure details provided in
143
50
  COUNTS_DATA_STRUCTURE. The parsed data, representing
144
51
  integers, is added to the dataset as new data
145
52
  fields.
146
53
 
147
- Note: count_rates_binary is added to the dataset by
54
+ Note: count_rates_raw is added to the dataset by
148
55
  the assemble_science_frames function, which organizes
149
56
  the binary science data packets by science frames.
150
57
 
@@ -154,7 +61,7 @@ def parse_count_rates(sci_dataset: xr.Dataset) -> None:
154
61
  Xarray dataset containing HIT science packets
155
62
  from a CCSDS file.
156
63
  """
157
- counts_binary = sci_dataset.count_rates_binary
64
+ counts_binary = sci_dataset.count_rates_raw
158
65
  # initialize the starting bit for the sections of data
159
66
  section_start = 0
160
67
  # Decommutate binary data for each counts data field
@@ -176,23 +83,34 @@ def parse_count_rates(sci_dataset: xr.Dataset) -> None:
176
83
  low_gain = data[1::2] # Items at odd indices 1, 3, 5, etc.
177
84
  parsed_data[i] = [high_gain, low_gain]
178
85
 
179
- # TODO
180
- # - status bits needs to be further parsed (table 10 in algorithm doc)
181
- # - subcommutate sectorates
182
- # - decompress data
183
- # - Follow up with HIT team about erates and evrates.
184
- # (i.e.Should these be arrays containing all the sub fields
185
- # or should each subfield be it's own data field/array)
86
+ # Decompress data where needed
87
+ if all(x not in field for x in ["hdr", "spare", "pha"]):
88
+ parsed_data = np.vectorize(decompress_rates_16_to_32)(parsed_data)
186
89
 
187
90
  # Get dims for data variables (yaml file not created yet)
188
91
  if len(field_meta.shape) > 1:
189
- dims = ["epoch", "gain", f"{field}_index"]
92
+ if "sectorates" in field:
93
+ # Reshape data to 8x15 for declination and azimuth look directions
94
+ parsed_data = np.array(parsed_data).reshape((-1, *field_meta.shape))
95
+ dims = ["epoch", "declination", "azimuth"]
96
+ elif "sngrates" in field:
97
+ dims = ["epoch", "gain", f"{field}_index"]
190
98
  elif field_meta.shape[0] > 1:
191
99
  dims = ["epoch", f"{field}_index"]
192
100
  else:
193
101
  dims = ["epoch"]
194
102
 
195
103
  sci_dataset[field] = xr.DataArray(parsed_data, dims=dims, name=field)
104
+ # Add dimensions to coordinates
105
+ # TODO: confirm that dtype int16 is correct
106
+ for dim in dims:
107
+ if dim not in sci_dataset.coords:
108
+ sci_dataset.coords[dim] = xr.DataArray(
109
+ np.arange(sci_dataset.sizes[dim], dtype=np.int16),
110
+ dims=[dim],
111
+ name=dim,
112
+ )
113
+
196
114
  # increment the start of the next section of data to parse
197
115
  section_start += field_meta.section_length
198
116
 
@@ -214,7 +132,7 @@ def is_sequential(counters: np.ndarray) -> np.bool_:
214
132
  return np.all(np.diff(counters) == 1)
215
133
 
216
134
 
217
- def find_valid_starting_indices(flags: np.ndarray, counters: np.ndarray) -> np.ndarray:
135
+ def get_valid_starting_indices(flags: np.ndarray, counters: np.ndarray) -> np.ndarray:
218
136
  """
219
137
  Find valid starting indices for science frames.
220
138
 
@@ -241,9 +159,6 @@ def find_valid_starting_indices(flags: np.ndarray, counters: np.ndarray) -> np.n
241
159
  valid_indices : np.ndarray
242
160
  Array of valid indices for science frames.
243
161
  """
244
- # TODO: consider combining functions to get valid indices to reduce
245
- # code tracing
246
-
247
162
  # Use sliding windows to compare segments of the array (20 packets) with the
248
163
  # pattern. This generates an array of overlapping sub-arrays, each of length
249
164
  # 20, from the flags array and is used to slide the "window" across the array
@@ -254,40 +169,13 @@ def find_valid_starting_indices(flags: np.ndarray, counters: np.ndarray) -> np.n
254
169
  # Get the starting indices of matches
255
170
  match_indices = np.where(matches)[0]
256
171
  # Filter for only indices from valid science frames with sequential counters
257
- valid_indices = get_valid_indices(match_indices, counters, FRAME_SIZE)
172
+ sequential_check = [
173
+ is_sequential(counters[idx : idx + FRAME_SIZE]) for idx in match_indices
174
+ ]
175
+ valid_indices: np.ndarray = np.array(match_indices[sequential_check], dtype=int)
258
176
  return valid_indices
259
177
 
260
178
 
261
- def get_valid_indices(
262
- indices: np.ndarray, counters: np.ndarray, size: int
263
- ) -> np.ndarray:
264
- """
265
- Get valid indices for science frames.
266
-
267
- Check if the packet sequence counters for the science frames
268
- are sequential. If they are, the science frame is valid and
269
- an updated array of valid indices is returned.
270
-
271
- Parameters
272
- ----------
273
- indices : np.ndarray
274
- Array of indices where the packet grouping flags match the pattern.
275
- counters : np.ndarray
276
- Array of packet sequence counters.
277
- size : int
278
- Size of science frame. 20 packets per science frame.
279
-
280
- Returns
281
- -------
282
- valid_indices : np.ndarray
283
- Array of valid indices for science frames.
284
- """
285
- # Check if the packet sequence counters are sequential by getting an array
286
- # of boolean values where True indicates the counters are sequential.
287
- sequential_check = [is_sequential(counters[idx : idx + size]) for idx in indices]
288
- return indices[sequential_check]
289
-
290
-
291
179
  def update_ccsds_header_dims(sci_dataset: xr.Dataset) -> xr.Dataset:
292
180
  """
293
181
  Update dimensions of CCSDS header fields.
@@ -334,8 +222,8 @@ def assemble_science_frames(sci_dataset: xr.Dataset) -> xr.Dataset:
334
222
  The first six packets contain count rates data
335
223
  The last 14 packets contain pulse height event data
336
224
 
337
- These groups are added to the dataset as count_rates_binary
338
- and pha_binary.
225
+ These groups are added to the dataset as count_rates_raw
226
+ and pha_raw.
339
227
 
340
228
  Parameters
341
229
  ----------
@@ -368,7 +256,7 @@ def assemble_science_frames(sci_dataset: xr.Dataset) -> xr.Dataset:
368
256
  total_packets = len(epoch_data)
369
257
 
370
258
  # Find starting indices for valid science frames
371
- starting_indices = find_valid_starting_indices(seq_flgs, seq_ctrs)
259
+ starting_indices = get_valid_starting_indices(seq_flgs, seq_ctrs)
372
260
 
373
261
  # Check for extra packets at start and end of file
374
262
  # TODO: Will need to handle these extra packets when processing multiple files
@@ -400,19 +288,75 @@ def assemble_science_frames(sci_dataset: xr.Dataset) -> xr.Dataset:
400
288
  pha.append("".join(science_data_frame[6:]))
401
289
  # Get first packet's epoch for the science frame
402
290
  epoch_per_science_frame = np.append(epoch_per_science_frame, epoch_data[idx])
403
- # TODO: Filter ccsds header fields to only include packets from the
404
- # valid science frames. Doesn't need to be grouped by frames though
405
291
 
406
292
  # Add new data variables to the dataset
407
293
  sci_dataset = sci_dataset.drop_vars("epoch")
408
294
  sci_dataset.coords["epoch"] = epoch_per_science_frame
409
- sci_dataset["count_rates_binary"] = xr.DataArray(
410
- count_rates, dims=["epoch"], name="count_rates_binary"
295
+ sci_dataset["count_rates_raw"] = xr.DataArray(
296
+ count_rates, dims=["epoch"], name="count_rates_raw"
411
297
  )
412
- sci_dataset["pha_binary"] = xr.DataArray(pha, dims=["epoch"], name="pha_binary")
298
+ sci_dataset["pha_raw"] = xr.DataArray(pha, dims=["epoch"], name="pha_raw")
413
299
  return sci_dataset
414
300
 
415
301
 
302
+ def decompress_rates_16_to_32(packed: int) -> int:
303
+ """
304
+ Will decompress rates data from 16 bits to 32 bits.
305
+
306
+ This function decompresses the rates data from 16-bit integers
307
+ to 32-bit integers. The compressed integer (packed) combines
308
+ two parts:
309
+
310
+ 1. Mantissa: Represents the significant digits of the value.
311
+ 2. Exponent: Determines how much to scale the mantissa (using powers of 2).
312
+
313
+ These parts are packed together into a single 16-bit integer.
314
+
315
+ Parameters
316
+ ----------
317
+ packed : int
318
+ Compressed 16-bit integer.
319
+
320
+ Returns
321
+ -------
322
+ decompressed_int : int
323
+ Decompressed integer.
324
+ """
325
+ # In compressed formats, the exponent and mantissa are tightly packed together.
326
+ # The mask ensures you correctly separate the mantissa (useful for reconstructing
327
+ # the value) from the exponent (used for scaling).
328
+ # set to 16 bits
329
+ output_mask = 0xFFFF
330
+
331
+ # Packed is the compressed integer
332
+ # Right bit shift to get the exponent
333
+ power = packed >> MANTISSA_BITS
334
+
335
+ # Decompress the data depending on the value of the exponent
336
+ # If the exponent (power) extracted from the packed 16-bit integer is greater
337
+ # than 1, the compressed value needs to be decompressed by reconstructing the
338
+ # integer using the mantissa and exponent. If the condition is false, the
339
+ # compressed and uncompressed values are considered the same.
340
+ decompressed_int: int
341
+ if power > 1:
342
+ # Retrieve the "mantissa" portion of the packed value by masking out the
343
+ # exponent bits
344
+ mantissa_mask = output_mask >> EXPONENT_BITS
345
+ mantissa = packed & mantissa_mask
346
+
347
+ # Shift the mantissa to the left by 1 to account for the hidden bit
348
+ # (always set to 1)
349
+ mantissa_with_hidden_bit = mantissa | (0x0001 << MANTISSA_BITS)
350
+
351
+ # Scale the mantissa by the exponent by shifting it to the left by (power - 1)
352
+ decompressed_int = mantissa_with_hidden_bit << (power - 1)
353
+ else:
354
+ # The compressed and uncompressed values are the same
355
+ decompressed_int = packed
356
+
357
+ return decompressed_int
358
+
359
+
416
360
  def decom_hit(sci_dataset: xr.Dataset) -> xr.Dataset:
417
361
  """
418
362
  Group and decode HIT science data packets.
@@ -465,7 +409,7 @@ def decom_hit(sci_dataset: xr.Dataset) -> xr.Dataset:
465
409
  # Parse count rates data from binary and add to dataset
466
410
  parse_count_rates(sci_dataset)
467
411
 
468
- # TODO:
469
- # Parse binary PHA data and add to dataset (function call)
412
+ # Remove raw binary data and unused spare bits from dataset
413
+ sci_dataset = sci_dataset.drop_vars(["count_rates_raw", "science_data", "spare"])
470
414
 
471
415
  return sci_dataset