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,279 +1,135 @@
1
1
  """IMAP-Hi direct event processing."""
2
2
 
3
+ from collections import defaultdict
4
+
3
5
  import numpy as np
6
+ import numpy._typing as npt
4
7
  import xarray as xr
5
8
 
6
9
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
7
10
  from imap_processing.spice.time import met_to_j2000ns
8
- from imap_processing.utils import convert_to_binary_string
9
11
 
10
12
  # TODO: read LOOKED_UP_DURATION_OF_TICK from
11
13
  # instrument status summary later. This value
12
14
  # is rarely change but want to be able to change
13
15
  # it if needed. It stores information about how
14
16
  # fast the time was ticking. It is in microseconds.
15
- LOOKED_UP_DURATION_OF_TICK = 3999
17
+ LOOKED_UP_DURATION_OF_TICK = 1999
16
18
 
17
19
  SECOND_TO_NS = 1e9
18
20
  MILLISECOND_TO_NS = 1e6
19
21
  MICROSECOND_TO_NS = 1e3
20
22
 
21
23
 
22
- def parse_direct_event(event_data: str) -> dict:
24
+ def parse_direct_events(de_data: bytes) -> dict[str, npt.ArrayLike]:
23
25
  """
24
- Parse event data.
26
+ Parse event data from a binary blob.
25
27
 
26
28
  IMAP-Hi direct event data information is stored in
27
- 48-bits as follow:
29
+ 48-bits as follows:
28
30
 
29
- | Read first two bits (start_bitmask_data) to find
30
- | out which type of event it is. start_bitmask_data value mapping:
31
- |
32
- | 1 - A
33
- | 2 - B
34
- | 3 - C
35
- | 0 - META
36
- | If it's a metaevent:
37
- |
38
- | Read 48-bits into 2, 4, 10, 32 bits. Each of these breaks
39
- | down as:
40
- |
41
- | start_bitmask_data - 2 bits (tA=1, tB=2, tC1=3, META=0)
42
- | ESA step - 4 bits
43
- | integer millisecond of MET(subseconds) - 10 bits
44
- | integer MET(seconds) - 32 bits
45
- |
46
- | If it's not a metaevent:
47
- | Read 48-bits into 2, 10, 10, 10, 16 bits. Each of these breaks
31
+ | Read 48-bits into 2, 16, 10, 10, 10, bits. Each of these breaks
48
32
  | down as:
49
33
  |
50
34
  | start_bitmask_data - 2 bits (tA=1, tB=2, tC1=3, META=0)
35
+ | de_tag - 16 bits
51
36
  | tof_1 - 10 bit counter
52
37
  | tof_2 - 10 bit counter
53
38
  | tof_3 - 10 bit counter
54
- | de_tag - 16 bits
55
39
 
56
- There are at most total of 665 of 48-bits in each data packet.
40
+ There are at most total of 664 of 48-bits in each data packet.
57
41
  This data packet is of variable length. If there is one event, then
58
- DE_TOF will contain 48-bits. If there are 665 events, then
59
- DE_TOF will contain 665 x 48-bits. If there is no event, then
42
+ DE_TOF will contain 48-bits. If there are 664 events, then
43
+ DE_TOF will contain 664 x 48-bits. If there is no event, then
60
44
  DE_TOF will contain 0-bits.
61
45
 
62
- Per ESA, there should be two data packets. First one will begin with
63
- metaevent followed by direct events data. Second one will begin with
64
- direct event data only. If there is no event record for certain ESA step,
65
- then as mentioned above, first packet will contain metaevent in DE_TOF
66
- information and second packet will contain 0-bits in DE_TOF. In general,
67
- every two packets will look like this.
68
-
69
- | first packet = [
70
- | (start_bitmask_data, ESA step, int millisecond of MET, int MET),
71
- | (start_bitmask_data, tof_1, tof_2, tof_3, de_tag),
72
- | .....
73
- | ]
74
- | second packet = [
75
- | (start_bitmask_data, tof_1, tof_2, tof_3, de_tag),
76
- | .....
77
- | ]
78
-
79
- In direct event data, if no hit is registered, the tof_x field in
80
- the DE to a value of negative one. However, since the field is described as a
81
- "10-bit unsigned counter," it cannot actually store negative numbers.
82
- Instead, the value negative is represented by the maximum value that can
83
- be stored in a 10-bit unsigned integer, which is 0x3FF (in hexadecimal)
84
- or 1023 in decimal. This value is used as a special marker to
85
- indicate that no hit was registered. Ideally, the system should
86
- not be able to register a hit with a value of 1023 for all
87
- tof_1, tof_2, tof_3, because this is in case of an error. But,
88
- IMAP-Hi like to process it still to investigate the data.
89
- Example of what it will look like if no hit was registered.
90
-
91
- | (start_bitmask_data, 1023, 1023, 1023, de_tag)
92
- | start_bitmask_data will be 1 or 2 or 3.
46
+ There should be two data packets per ESA. Each packet contains meta-event
47
+ data that is identical between the two packets for a common ESA.
48
+ If there is no event record for certain ESA step, then both packets will
49
+ contain 0-bits in DE_TOF.
93
50
 
94
51
  Parameters
95
52
  ----------
96
- event_data : str
97
- 48-bits Event data.
53
+ de_data : bytes
54
+ Binary blob from de_tag field of SCI_DE packet. Must be an integer
55
+ multiple of 48-bits of data.
98
56
 
99
57
  Returns
100
58
  -------
101
- dict
59
+ Dict[str, list]
102
60
  Parsed event data.
103
61
  """
104
- event_type = int(event_data[:2], 2)
105
- metaevent = 0
106
- if event_type == metaevent:
107
- # parse metaevent
108
- esa_step = event_data[2:6]
109
- subseconds = event_data[6:16]
110
- seconds = event_data[16:]
111
-
112
- # return parsed metaevent data
113
- return {
114
- "start_bitmask_data": event_type,
115
- "esa_step": int(esa_step, 2),
116
- "subseconds": int(subseconds, 2),
117
- "seconds": int(seconds, 2),
118
- }
119
-
120
- # parse direct event
121
- trigger_id = event_data[:2]
122
- tof_1 = event_data[2:12]
123
- tof_2 = event_data[12:22]
124
- tof_3 = event_data[22:32]
125
- de_tag = event_data[32:]
126
-
127
- # return parsed direct event data
128
- return {
129
- "start_bitmask_data": int(trigger_id, 2),
130
- "tof_1": int(tof_1, 2),
131
- "tof_2": int(tof_2, 2),
132
- "tof_3": int(tof_3, 2),
133
- "de_tag": int(de_tag, 2),
134
- }
135
-
136
-
137
- def break_into_bits_size(binary_data: str) -> list:
138
- """
139
- Break binary stream data into 48-bits.
140
-
141
- Parameters
142
- ----------
143
- binary_data : str
144
- Binary data.
62
+ # The de_data is a binary blob with Nx6 bytes of data where N = number of
63
+ # direct events encoded into the binary blob. Interpreting the data as
64
+ # big-endian uint16 data and reshaping into a (3, -1) ndarray results
65
+ # in an array with shape (3, N). Indexing the first axis of that array
66
+ # (e.g. data_uint16[i]) gives the ith 2-bytes of data for each of the N
67
+ # direct events.
68
+ # Considering the 6-bytes of data for each DE as 3 2-byte words,
69
+ # each word contains the following:
70
+ # word_0: 2-bits of Trigger ID, upper 14-bits of de_tag
71
+ # word_1: lower 2-bits of de_tag, 10-bits tof_1, upper 4-bits of tof_2
72
+ # word_2: lower 6-bits of tof_2, 10-bits of tof_3
73
+ data_uint16 = np.reshape(
74
+ np.frombuffer(de_data, dtype=">u2"), (3, -1), order="F"
75
+ ).astype(np.uint16)
76
+
77
+ de_dict = dict()
78
+ de_dict["trigger_id"] = (data_uint16[0] >> 14).astype(np.uint8)
79
+ de_dict["de_tag"] = (data_uint16[0] << 2) + (data_uint16[1] >> 14)
80
+ de_dict["tof_1"] = (data_uint16[1] & int(b"00111111_11110000", 2)) >> 4
81
+ de_dict["tof_2"] = ((data_uint16[1] & int(b"00000000_00001111", 2)) << 6) + (
82
+ data_uint16[2] >> 10
83
+ )
84
+ de_dict["tof_3"] = data_uint16[2] & int(b"00000011_11111111", 2)
145
85
 
146
- Returns
147
- -------
148
- list
149
- List of 48-bits.
150
- """
151
- # TODO: ask Paul what to do if the length of
152
- # binary_data is not a multiple of 48
153
- field_bit_length = 48
154
- return [
155
- binary_data[i : i + field_bit_length]
156
- for i in range(0, len(binary_data), field_bit_length)
157
- ]
86
+ return de_dict
158
87
 
159
88
 
160
- def create_dataset(de_data_list: list, packet_met_time: list) -> xr.Dataset:
89
+ def create_dataset(de_data_dict: dict[str, npt.ArrayLike]) -> xr.Dataset:
161
90
  """
162
91
  Create xarray dataset.
163
92
 
164
93
  Parameters
165
94
  ----------
166
- de_data_list : list
167
- Parsed direct event data list.
168
- packet_met_time : list
169
- List of packet MET time.
95
+ de_data_dict : Dict[list]
96
+ Dictionary of packet telemetry and direct event data lists.
170
97
 
171
98
  Returns
172
99
  -------
173
100
  dataset : xarray.Dataset
174
101
  Xarray dataset.
175
102
  """
176
- # These are the variables that we will store in the dataset
177
- data_dict: dict = {
178
- "epoch": list(),
179
- "event_met": list(),
180
- "ccsds_met": list(),
181
- "meta_event_met": list(),
182
- "esa_step": list(),
183
- "trigger_id": list(),
184
- "tof_1": list(),
185
- "tof_2": list(),
186
- "tof_3": list(),
187
- "de_tag": list(),
188
- }
189
-
190
- # How to handle if first event is not metaevent? This
191
- # means that current data file started with direct event.
192
- # Per Paul, log a warning and discard all direct events
193
- # until we see next metaevent because it could mean
194
- # that the instrument was turned off during repoint.
195
-
196
- # Find the index of the first occurrence of the metaevent
197
- first_metaevent_index = next(
198
- (i for i, d in enumerate(de_data_list) if d.get("start_bitmask_data") == 0),
199
- None,
103
+ # Compute the meta-event MET in nanoseconds
104
+ de_data_dict["meta_event_met"] = (
105
+ np.array(de_data_dict.pop("meta_seconds")) * SECOND_TO_NS
106
+ + np.array(de_data_dict.pop("meta_subseconds")) * MILLISECOND_TO_NS
107
+ )
108
+ # Compute the MET of each event in nanoseconds
109
+ # event MET = meta_event_met + de_clock + 1/2 de_clock_tick
110
+ # See Hi Algorithm Document section 2.2.5
111
+ half_tick_ns = LOOKED_UP_DURATION_OF_TICK / 2 * MICROSECOND_TO_NS
112
+ de_data_dict["event_met"] = (
113
+ de_data_dict["meta_event_met"]
114
+ + np.array(de_data_dict["de_tag"])
115
+ * LOOKED_UP_DURATION_OF_TICK
116
+ * MICROSECOND_TO_NS
117
+ + half_tick_ns
200
118
  )
201
-
202
- if first_metaevent_index is None:
203
- return None
204
- elif first_metaevent_index != 0:
205
- # Discard all direct events until we see next metaevent
206
- # TODO: log a warning
207
- de_data_list = de_data_list[first_metaevent_index:]
208
- packet_met_time = packet_met_time[first_metaevent_index:]
209
-
210
- for index, event in enumerate(de_data_list):
211
- if event["start_bitmask_data"] == 0:
212
- # metaevent is a way to store information
213
- # about bigger portion of time information. Eg.
214
- # metaevent stores information about, let's say
215
- # "20240319T09:30:01.000". Then direct event time
216
- # tag stores information of time ticks since
217
- # that time. Then we use those two to combine and
218
- # get exact time information of each event.
219
-
220
- # set time and esa step values to
221
- # be used for direct event followed by
222
- # this metaevent
223
- int_subseconds = event["subseconds"]
224
- int_seconds = event["seconds"]
225
- current_esa_step = event["esa_step"]
226
-
227
- metaevent_time_in_ns = (
228
- int_seconds * SECOND_TO_NS + int_subseconds * MILLISECOND_TO_NS
229
- )
230
-
231
- # Add half a tick once per algorithm document(
232
- # section 2.2.5 and second last bullet point)
233
- # and Paul Janzen.
234
- half_tick = LOOKED_UP_DURATION_OF_TICK / 2
235
- # convert microseconds to nanosecond to
236
- # match other time format
237
- half_tick_ns = half_tick * MICROSECOND_TO_NS
238
- metaevent_time_in_ns += half_tick_ns
239
- continue
240
-
241
- data_dict["meta_event_met"].append(metaevent_time_in_ns)
242
- # calculate direct event time using time information from metaevent
243
- # and de_tag. epoch in this dataset uses this time of the event
244
- de_met_in_ns = (
245
- metaevent_time_in_ns
246
- + event["de_tag"] * LOOKED_UP_DURATION_OF_TICK * MICROSECOND_TO_NS
247
- )
248
- data_dict["event_met"].append(de_met_in_ns)
249
- data_dict["epoch"].append(met_to_j2000ns(de_met_in_ns / 1e9))
250
- data_dict["esa_step"].append(current_esa_step)
251
- # start_bitmask_data is 1, 2, 3 for detector A, B, C
252
- # respectively. This is used to identify which detector
253
- # was hit first for this current direct event.
254
- data_dict["trigger_id"].append(event["start_bitmask_data"])
255
- data_dict["tof_1"].append(event["tof_1"])
256
- data_dict["tof_2"].append(event["tof_2"])
257
- data_dict["tof_3"].append(event["tof_3"])
258
- # IMAP-Hi like to keep de_tag value for diagnostic purposes
259
- data_dict["de_tag"].append(event["de_tag"])
260
- # add packet time to ccsds_met list
261
- data_dict["ccsds_met"].append(packet_met_time[index])
262
119
 
263
120
  # Load the CDF attributes
264
121
  attr_mgr = ImapCdfAttributes()
265
122
  attr_mgr.add_instrument_global_attrs("hi")
266
- attr_mgr.load_variable_attributes("imap_hi_variable_attrs.yaml")
267
- # uncomment this once Maxine's PR is merged
268
- # attr_mgr.add_global_attribute("Data_version", data_version)
123
+ attr_mgr.add_instrument_variable_attrs(instrument="hi", level=None)
269
124
 
270
- epoch_attrs = attr_mgr.get_variable_attributes("epoch")
125
+ # check_schema=False keeps DEPEND_0 = '' from being auto added
126
+ epoch_attrs = attr_mgr.get_variable_attributes("epoch", check_schema=False)
271
127
  epoch_attrs["CATDESC"] = (
272
128
  "Direct Event time, number of nanoseconds since J2000 with leap "
273
129
  "seconds included"
274
130
  )
275
- epoch_time = xr.DataArray(
276
- data_dict.pop("epoch"),
131
+ epoch = xr.DataArray(
132
+ met_to_j2000ns(de_data_dict["event_met"] / 1e9),
277
133
  name="epoch",
278
134
  dims=["epoch"],
279
135
  attrs=epoch_attrs,
@@ -281,11 +137,11 @@ def create_dataset(de_data_list: list, packet_met_time: list) -> xr.Dataset:
281
137
 
282
138
  de_global_attrs = attr_mgr.get_global_attributes("imap_hi_l1a_de_attrs")
283
139
  dataset = xr.Dataset(
284
- coords={"epoch": epoch_time},
140
+ coords={"epoch": epoch},
285
141
  attrs=de_global_attrs,
286
142
  )
287
143
 
288
- for var_name, data in data_dict.items():
144
+ for var_name, data in de_data_dict.items():
289
145
  attrs = attr_mgr.get_variable_attributes(
290
146
  f"hi_de_{var_name}", check_schema=False
291
147
  ).copy()
@@ -321,23 +177,34 @@ def science_direct_event(packets_data: xr.Dataset) -> xr.Dataset:
321
177
  dataset : xarray.Dataset
322
178
  Xarray dataset.
323
179
  """
324
- de_data_list = []
325
- packet_met_time = []
180
+ de_data_dict: dict[str, list] = defaultdict(list)
326
181
 
327
182
  # Because DE_TOF is a variable length data,
328
183
  # I am using extend to add another list to the
329
184
  # end of the list. This way, I don't need to flatten
330
185
  # the list later.
331
186
  for i, data in enumerate(packets_data["de_tof"].data):
332
- binary_str_val = convert_to_binary_string(data)
333
- # break binary stream data into unit of 48-bits
334
- event_48bits_list = break_into_bits_size(binary_str_val)
335
- # parse 48-bits into meaningful data such as metaevent or direct event
336
- de_data_list.extend([parse_direct_event(event) for event in event_48bits_list])
337
- # add packet time to packet_met_time
338
- packet_met_time.extend(
339
- [packets_data["ccsds_met"].data[i]] * len(event_48bits_list)
340
- )
187
+ parsed_de_data = parse_direct_events(data)
188
+ for key, new_data in parsed_de_data.items():
189
+ de_data_dict[key].extend(new_data)
190
+
191
+ # add packet data to keep_packet_data dictionary, repeating values
192
+ # for each direct event encoded in the current packet
193
+ for from_key, to_key in {
194
+ "shcoarse": "ccsds_met",
195
+ "src_seq_ctr": "src_seq_ctr",
196
+ "pkt_len": "pkt_len",
197
+ "last_spin_num": "last_spin_num",
198
+ "spin_invalids": "spin_invalids",
199
+ "esa_step_num": "esa_step",
200
+ "meta_seconds": "meta_seconds",
201
+ "meta_subseconds": "meta_subseconds",
202
+ }.items():
203
+ # Repeat the ith packet from_key value N times, where N is the
204
+ # number of events in the ith packet.
205
+ de_data_dict[to_key].extend(
206
+ [packets_data[from_key].data[i]] * len(parsed_de_data["de_tag"])
207
+ )
341
208
 
342
209
  # create dataset
343
- return create_dataset(de_data_list, packet_met_time)
210
+ return create_dataset(de_data_dict)
@@ -15,7 +15,12 @@ from imap_processing.hi.utils import (
15
15
  create_dataset_variables,
16
16
  parse_sensor_number,
17
17
  )
18
- from imap_processing.spice.geometry import SpiceFrame, instrument_pointing
18
+ from imap_processing.spice.geometry import (
19
+ SpiceFrame,
20
+ get_instrument_spin_phase,
21
+ get_spacecraft_spin_phase,
22
+ instrument_pointing,
23
+ )
19
24
  from imap_processing.spice.time import j2000ns_to_j2000s
20
25
  from imap_processing.utils import convert_raw_to_eu
21
26
 
@@ -40,7 +45,7 @@ class CoincidenceBitmap(IntEnum):
40
45
  logger = logging.getLogger(__name__)
41
46
  ATTR_MGR = ImapCdfAttributes()
42
47
  ATTR_MGR.add_instrument_global_attrs("hi")
43
- ATTR_MGR.load_variable_attributes("imap_hi_variable_attrs.yaml")
48
+ ATTR_MGR.add_instrument_variable_attrs(instrument="hi", level=None)
44
49
 
45
50
 
46
51
  def hi_l1b(l1a_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
@@ -116,17 +121,16 @@ def annotate_direct_events(l1a_dataset: xr.Dataset) -> xr.Dataset:
116
121
  """
117
122
  l1b_dataset = l1a_dataset.copy()
118
123
  l1b_dataset.update(compute_coincidence_type_and_time_deltas(l1b_dataset))
124
+ l1b_dataset.update(de_nominal_bin_and_spin_phase(l1b_dataset))
119
125
  l1b_dataset.update(compute_hae_coordinates(l1b_dataset))
120
- l1b_de_var_names = [
121
- "esa_energy_step",
122
- "spin_phase",
123
- "quality_flag",
124
- "nominal_bin",
125
- ]
126
- new_data_vars = create_dataset_variables(
127
- l1b_de_var_names, l1b_dataset["epoch"].size, att_manager_lookup_str="hi_de_{0}"
126
+ l1b_dataset.update(de_esa_energy_step(l1b_dataset))
127
+ l1b_dataset.update(
128
+ create_dataset_variables(
129
+ ["quality_flag"],
130
+ l1b_dataset["epoch"].size,
131
+ att_manager_lookup_str="hi_de_{0}",
132
+ )
128
133
  )
129
- l1b_dataset.update(new_data_vars)
130
134
  l1b_dataset = l1b_dataset.drop_vars(
131
135
  ["tof_1", "tof_2", "tof_3", "de_tag", "ccsds_met", "meta_event_met"]
132
136
  )
@@ -258,6 +262,47 @@ def compute_coincidence_type_and_time_deltas(
258
262
  return new_vars
259
263
 
260
264
 
265
+ def de_nominal_bin_and_spin_phase(dataset: xr.Dataset) -> dict[str, xr.DataArray]:
266
+ """
267
+ Compute nominal bin and instrument spin-phase for each direct event.
268
+
269
+ Parameters
270
+ ----------
271
+ dataset : xarray.Dataset
272
+ Direct event data to compute instrument spin-phase for.
273
+
274
+ Returns
275
+ -------
276
+ new_vars : dict[str, xarray.DataArray]
277
+ Dictionary containing new "spin_phase" variable.
278
+ """
279
+ new_vars = create_dataset_variables(
280
+ [
281
+ "spin_phase",
282
+ "nominal_bin",
283
+ ],
284
+ len(dataset.epoch),
285
+ att_manager_lookup_str="hi_de_{0}",
286
+ )
287
+
288
+ # nominal_bin is the index number of the 90 4-degree bins that each DE would
289
+ # be binned into in the histogram packet. The Hi histogram data is binned by
290
+ # spacecraft spin-phase, not instrument spin-phase, so the same is done here.
291
+ met_query_times = j2000ns_to_j2000s(dataset.event_met.values)
292
+ imap_spin_phase = get_spacecraft_spin_phase(met_query_times)
293
+ new_vars["nominal_bin"].values = np.asarray(imap_spin_phase * 360 / 4).astype(
294
+ np.uint8
295
+ )
296
+
297
+ sensor_number = parse_sensor_number(dataset.attrs["Logical_source"])
298
+ new_vars["spin_phase"].values = np.asarray(
299
+ get_instrument_spin_phase(
300
+ met_query_times, SpiceFrame[f"IMAP_HI_{sensor_number}"]
301
+ )
302
+ ).astype(np.float32)
303
+ return new_vars
304
+
305
+
261
306
  def compute_hae_coordinates(dataset: xr.Dataset) -> dict[str, xr.DataArray]:
262
307
  """
263
308
  Compute HAE latitude and longitude.
@@ -296,3 +341,32 @@ def compute_hae_coordinates(dataset: xr.Dataset) -> dict[str, xr.DataArray]:
296
341
  new_vars["hae_longitude"].values = pointing_coordinates[:, 1]
297
342
 
298
343
  return new_vars
344
+
345
+
346
+ def de_esa_energy_step(dataset: xr.Dataset) -> dict[str, xr.DataArray]:
347
+ """
348
+ Compute esa_energy_step for each direct event.
349
+
350
+ TODO: For now this function just returns the esa_step from the input dataset.
351
+ Eventually, it will take L1B housekeeping data and determine the esa
352
+ energy steps from that data.
353
+
354
+ Parameters
355
+ ----------
356
+ dataset : xarray.Dataset
357
+ The partial L1B dataset.
358
+
359
+ Returns
360
+ -------
361
+ new_vars : dict[str, xarray.DataArray]
362
+ Keys are variable names and values are `xarray.DataArray`.
363
+ """
364
+ new_vars = create_dataset_variables(
365
+ ["esa_energy_step"],
366
+ len(dataset.epoch),
367
+ att_manager_lookup_str="hi_de_{0}",
368
+ )
369
+ # TODO: Implement this algorithm
370
+ new_vars["esa_energy_step"].values = dataset.esa_step.values
371
+
372
+ return new_vars
@@ -94,7 +94,7 @@ def allocate_pset_dataset(n_esa_steps: int, sensor_str: str) -> xr.Dataset:
94
94
  """
95
95
  attr_mgr = ImapCdfAttributes()
96
96
  attr_mgr.add_instrument_global_attrs("hi")
97
- attr_mgr.load_variable_attributes("imap_hi_variable_attrs.yaml")
97
+ attr_mgr.add_instrument_variable_attrs(instrument="hi", level=None)
98
98
 
99
99
  # preallocate coordinates xr.DataArrays
100
100
  coords = dict()
@@ -115,6 +115,20 @@ def allocate_pset_dataset(n_esa_steps: int, sensor_str: str) -> xr.Dataset:
115
115
  dims=["esa_energy_step"],
116
116
  attrs=attrs,
117
117
  )
118
+ # TODO: define calibration product number to coincidence type mapping and
119
+ # use the number of calibration products here. I believe it will be 5
120
+ # 0 for any, 1-4, for the number of detector hits.
121
+ n_calibration_prod = 5
122
+ attrs = attr_mgr.get_variable_attributes(
123
+ "hi_pset_calibration_prod", check_schema=False
124
+ ).copy()
125
+ dtype = attrs.pop("dtype")
126
+ coords["calibration_prod"] = xr.DataArray(
127
+ np.arange(n_calibration_prod, dtype=dtype),
128
+ name="calibration_prod",
129
+ dims=["calibration_prod"],
130
+ attrs=attrs,
131
+ )
118
132
  # spin angle bins are 0.1 degree bins for full 360 degree spin
119
133
  attrs = attr_mgr.get_variable_attributes(
120
134
  "hi_pset_spin_angle_bin", check_schema=False
@@ -157,6 +171,14 @@ def allocate_pset_dataset(n_esa_steps: int, sensor_str: str) -> xr.Dataset:
157
171
  "hi_pset_esa_energy_step_label", check_schema=False
158
172
  ),
159
173
  )
174
+ data_vars["calibration_prod_label"] = xr.DataArray(
175
+ coords["calibration_prod"].values.astype(str),
176
+ name="calibration_prod_label",
177
+ dims=["calibration_prod"],
178
+ attrs=attr_mgr.get_variable_attributes(
179
+ "hi_pset_calibration_prod_label", check_schema=False
180
+ ),
181
+ )
160
182
  data_vars["spin_bin_label"] = xr.DataArray(
161
183
  coords["spin_angle_bin"].values.astype(str),
162
184
  name="spin_bin_label",