imap-processing 0.12.0__py3-none-any.whl → 0.13.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 (272) hide show
  1. imap_processing/__init__.py +1 -0
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/ccsds_data.py +1 -2
  4. imap_processing/ccsds/excel_to_xtce.py +1 -2
  5. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +18 -12
  6. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +569 -0
  7. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +5 -5
  9. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +20 -1
  10. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +6 -4
  11. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +3 -3
  12. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +15 -0
  13. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +22 -0
  14. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +16 -0
  15. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +178 -5
  16. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -19
  18. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +8 -48
  19. imap_processing/cdf/utils.py +41 -33
  20. imap_processing/cli.py +463 -234
  21. imap_processing/codice/codice_l1a.py +260 -47
  22. imap_processing/codice/codice_l1b.py +51 -152
  23. imap_processing/codice/constants.py +38 -1
  24. imap_processing/ena_maps/ena_maps.py +658 -65
  25. imap_processing/ena_maps/utils/coordinates.py +1 -1
  26. imap_processing/ena_maps/utils/spatial_utils.py +10 -5
  27. imap_processing/glows/l1a/glows_l1a.py +28 -99
  28. imap_processing/glows/l1a/glows_l1a_data.py +2 -2
  29. imap_processing/glows/l1b/glows_l1b.py +1 -4
  30. imap_processing/glows/l1b/glows_l1b_data.py +1 -3
  31. imap_processing/glows/l2/glows_l2.py +2 -5
  32. imap_processing/hi/l1a/hi_l1a.py +31 -12
  33. imap_processing/hi/l1b/hi_l1b.py +80 -43
  34. imap_processing/hi/l1c/hi_l1c.py +12 -16
  35. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
  36. imap_processing/hit/hit_utils.py +93 -35
  37. imap_processing/hit/l0/decom_hit.py +3 -1
  38. imap_processing/hit/l1a/hit_l1a.py +30 -25
  39. imap_processing/hit/l1b/constants.py +6 -2
  40. imap_processing/hit/l1b/hit_l1b.py +279 -318
  41. imap_processing/hit/l2/constants.py +37 -0
  42. imap_processing/hit/l2/hit_l2.py +373 -264
  43. imap_processing/ialirt/l0/parse_mag.py +138 -10
  44. imap_processing/ialirt/l0/process_swapi.py +69 -0
  45. imap_processing/ialirt/l0/process_swe.py +318 -22
  46. imap_processing/ialirt/packet_definitions/ialirt.xml +216 -212
  47. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +1 -1
  48. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +1 -1
  49. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +14 -14
  50. imap_processing/ialirt/utils/grouping.py +1 -1
  51. imap_processing/idex/idex_constants.py +9 -1
  52. imap_processing/idex/idex_l0.py +22 -8
  53. imap_processing/idex/idex_l1a.py +75 -44
  54. imap_processing/idex/idex_l1b.py +9 -8
  55. imap_processing/idex/idex_l2a.py +79 -45
  56. imap_processing/idex/idex_l2b.py +120 -0
  57. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -39
  58. imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
  59. imap_processing/lo/l0/lo_science.py +1 -2
  60. imap_processing/lo/l1a/lo_l1a.py +1 -4
  61. imap_processing/lo/l1b/lo_l1b.py +527 -6
  62. imap_processing/lo/l1b/tof_conversions.py +11 -0
  63. imap_processing/lo/l1c/lo_l1c.py +1 -4
  64. imap_processing/mag/constants.py +43 -0
  65. imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
  66. imap_processing/mag/l1a/mag_l1a.py +2 -9
  67. imap_processing/mag/l1a/mag_l1a_data.py +10 -10
  68. imap_processing/mag/l1b/mag_l1b.py +84 -17
  69. imap_processing/mag/l1c/interpolation_methods.py +180 -3
  70. imap_processing/mag/l1c/mag_l1c.py +236 -70
  71. imap_processing/mag/l2/mag_l2.py +140 -0
  72. imap_processing/mag/l2/mag_l2_data.py +288 -0
  73. imap_processing/spacecraft/quaternions.py +1 -3
  74. imap_processing/spice/geometry.py +3 -3
  75. imap_processing/spice/kernels.py +0 -276
  76. imap_processing/spice/pointing_frame.py +257 -0
  77. imap_processing/spice/repoint.py +48 -19
  78. imap_processing/spice/spin.py +38 -33
  79. imap_processing/spice/time.py +24 -0
  80. imap_processing/swapi/l1/swapi_l1.py +16 -12
  81. imap_processing/swapi/l2/swapi_l2.py +116 -4
  82. imap_processing/swapi/swapi_utils.py +32 -0
  83. imap_processing/swe/l1a/swe_l1a.py +2 -9
  84. imap_processing/swe/l1a/swe_science.py +8 -11
  85. imap_processing/swe/l1b/swe_l1b.py +898 -23
  86. imap_processing/swe/l2/swe_l2.py +21 -77
  87. imap_processing/swe/utils/swe_constants.py +1 -0
  88. imap_processing/tests/ccsds/test_excel_to_xtce.py +1 -1
  89. imap_processing/tests/cdf/test_utils.py +14 -16
  90. imap_processing/tests/codice/conftest.py +44 -33
  91. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
  92. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
  93. imap_processing/tests/codice/test_codice_l1a.py +20 -11
  94. imap_processing/tests/codice/test_codice_l1b.py +6 -7
  95. imap_processing/tests/conftest.py +78 -22
  96. imap_processing/tests/ena_maps/test_ena_maps.py +462 -33
  97. imap_processing/tests/ena_maps/test_spatial_utils.py +1 -1
  98. imap_processing/tests/glows/conftest.py +10 -14
  99. imap_processing/tests/glows/test_glows_decom.py +4 -4
  100. imap_processing/tests/glows/test_glows_l1a_cdf.py +6 -27
  101. imap_processing/tests/glows/test_glows_l1a_data.py +6 -8
  102. imap_processing/tests/glows/test_glows_l1b.py +11 -11
  103. imap_processing/tests/glows/test_glows_l1b_data.py +5 -5
  104. imap_processing/tests/glows/test_glows_l2.py +2 -8
  105. imap_processing/tests/hi/conftest.py +1 -1
  106. imap_processing/tests/hi/test_hi_l1b.py +10 -12
  107. imap_processing/tests/hi/test_hi_l1c.py +27 -24
  108. imap_processing/tests/hi/test_l1a.py +7 -9
  109. imap_processing/tests/hi/test_science_direct_event.py +2 -2
  110. imap_processing/tests/hit/helpers/l1_validation.py +44 -43
  111. imap_processing/tests/hit/test_decom_hit.py +1 -1
  112. imap_processing/tests/hit/test_hit_l1a.py +9 -9
  113. imap_processing/tests/hit/test_hit_l1b.py +172 -217
  114. imap_processing/tests/hit/test_hit_l2.py +380 -118
  115. imap_processing/tests/hit/test_hit_utils.py +122 -55
  116. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
  117. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +1 -1
  118. imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
  119. imap_processing/tests/ialirt/unit/test_grouping.py +2 -2
  120. imap_processing/tests/ialirt/unit/test_parse_mag.py +71 -16
  121. imap_processing/tests/ialirt/unit/test_process_codicehi.py +3 -3
  122. imap_processing/tests/ialirt/unit/test_process_codicelo.py +3 -10
  123. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +4 -4
  124. imap_processing/tests/ialirt/unit/test_process_hit.py +3 -3
  125. imap_processing/tests/ialirt/unit/test_process_swapi.py +24 -16
  126. imap_processing/tests/ialirt/unit/test_process_swe.py +115 -7
  127. imap_processing/tests/idex/conftest.py +72 -7
  128. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20241206_v001.pkts +0 -0
  129. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20250108_v001.pkts +0 -0
  130. imap_processing/tests/idex/test_idex_l0.py +33 -11
  131. imap_processing/tests/idex/test_idex_l1a.py +50 -23
  132. imap_processing/tests/idex/test_idex_l1b.py +104 -25
  133. imap_processing/tests/idex/test_idex_l2a.py +48 -32
  134. imap_processing/tests/idex/test_idex_l2b.py +93 -0
  135. imap_processing/tests/lo/test_lo_l1a.py +3 -3
  136. imap_processing/tests/lo/test_lo_l1b.py +371 -6
  137. imap_processing/tests/lo/test_lo_l1c.py +1 -1
  138. imap_processing/tests/lo/test_lo_science.py +6 -7
  139. imap_processing/tests/lo/test_star_sensor.py +1 -1
  140. imap_processing/tests/mag/conftest.py +58 -9
  141. imap_processing/tests/mag/test_mag_decom.py +4 -3
  142. imap_processing/tests/mag/test_mag_l1a.py +13 -7
  143. imap_processing/tests/mag/test_mag_l1b.py +9 -9
  144. imap_processing/tests/mag/test_mag_l1c.py +151 -47
  145. imap_processing/tests/mag/test_mag_l2.py +130 -0
  146. imap_processing/tests/mag/test_mag_validation.py +144 -7
  147. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-in.csv +1217 -0
  148. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-out.csv +1857 -0
  149. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-in.csv +1217 -0
  150. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-out.csv +1857 -0
  151. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-in.csv +1217 -0
  152. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-out.csv +1793 -0
  153. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-in.csv +1217 -0
  154. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-out.csv +1793 -0
  155. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-burst-in.csv +2561 -0
  156. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-in.csv +961 -0
  157. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-out.csv +1539 -0
  158. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-in.csv +1921 -0
  159. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-out.csv +2499 -0
  160. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-in.csv +865 -0
  161. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-out.csv +1196 -0
  162. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-in.csv +1729 -0
  163. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-out.csv +3053 -0
  164. imap_processing/tests/mag/validation/L2/imap_mag_l1b_norm-mago_20251017_v002.cdf +0 -0
  165. imap_processing/tests/mag/validation/calibration/imap_mag_l2-calibration-matrices_20251017_v004.cdf +0 -0
  166. imap_processing/tests/mag/validation/calibration/imap_mag_l2-offsets-norm_20251017_20251017_v001.cdf +0 -0
  167. imap_processing/tests/spacecraft/test_quaternions.py +1 -1
  168. imap_processing/tests/spice/test_data/fake_repoint_data.csv +4 -4
  169. imap_processing/tests/spice/test_data/fake_spin_data.csv +11 -11
  170. imap_processing/tests/spice/test_geometry.py +3 -3
  171. imap_processing/tests/spice/test_kernels.py +1 -200
  172. imap_processing/tests/spice/test_pointing_frame.py +185 -0
  173. imap_processing/tests/spice/test_repoint.py +20 -10
  174. imap_processing/tests/spice/test_spin.py +50 -9
  175. imap_processing/tests/spice/test_time.py +14 -0
  176. imap_processing/tests/swapi/lut/imap_swapi_esa-unit-conversion_20250211_v000.csv +73 -0
  177. imap_processing/tests/swapi/lut/imap_swapi_lut-notes_20250211_v000.csv +1025 -0
  178. imap_processing/tests/swapi/test_swapi_l1.py +7 -9
  179. imap_processing/tests/swapi/test_swapi_l2.py +180 -8
  180. imap_processing/tests/swe/lut/checker-board-indices.csv +24 -0
  181. imap_processing/tests/swe/lut/imap_swe_esa-lut_20250301_v000.csv +385 -0
  182. imap_processing/tests/swe/lut/imap_swe_l1b-in-flight-cal_20240510_20260716_v000.csv +3 -0
  183. imap_processing/tests/swe/test_swe_l1a.py +6 -6
  184. imap_processing/tests/swe/test_swe_l1a_science.py +3 -3
  185. imap_processing/tests/swe/test_swe_l1b.py +162 -24
  186. imap_processing/tests/swe/test_swe_l2.py +82 -102
  187. imap_processing/tests/test_cli.py +171 -88
  188. imap_processing/tests/test_utils.py +2 -1
  189. imap_processing/tests/ultra/data/mock_data.py +49 -21
  190. imap_processing/tests/ultra/unit/conftest.py +53 -70
  191. imap_processing/tests/ultra/unit/test_badtimes.py +2 -4
  192. imap_processing/tests/ultra/unit/test_cullingmask.py +4 -6
  193. imap_processing/tests/ultra/unit/test_de.py +3 -10
  194. imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
  195. imap_processing/tests/ultra/unit/test_decom_apid_881.py +15 -16
  196. imap_processing/tests/ultra/unit/test_decom_apid_883.py +12 -10
  197. imap_processing/tests/ultra/unit/test_decom_apid_896.py +202 -55
  198. imap_processing/tests/ultra/unit/test_lookup_utils.py +23 -1
  199. imap_processing/tests/ultra/unit/test_spacecraft_pset.py +3 -4
  200. imap_processing/tests/ultra/unit/test_ultra_l1a.py +84 -307
  201. imap_processing/tests/ultra/unit/test_ultra_l1b.py +30 -12
  202. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
  203. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +4 -1
  204. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +163 -29
  205. imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
  206. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +32 -43
  207. imap_processing/tests/ultra/unit/test_ultra_l2.py +230 -0
  208. imap_processing/ultra/constants.py +1 -1
  209. imap_processing/ultra/l0/decom_tools.py +21 -34
  210. imap_processing/ultra/l0/decom_ultra.py +168 -204
  211. imap_processing/ultra/l0/ultra_utils.py +152 -136
  212. imap_processing/ultra/l1a/ultra_l1a.py +55 -243
  213. imap_processing/ultra/l1b/badtimes.py +1 -4
  214. imap_processing/ultra/l1b/cullingmask.py +2 -6
  215. imap_processing/ultra/l1b/de.py +62 -47
  216. imap_processing/ultra/l1b/extendedspin.py +8 -4
  217. imap_processing/ultra/l1b/lookup_utils.py +72 -9
  218. imap_processing/ultra/l1b/ultra_l1b.py +3 -8
  219. imap_processing/ultra/l1b/ultra_l1b_culling.py +4 -4
  220. imap_processing/ultra/l1b/ultra_l1b_extended.py +236 -78
  221. imap_processing/ultra/l1c/histogram.py +2 -6
  222. imap_processing/ultra/l1c/spacecraft_pset.py +2 -4
  223. imap_processing/ultra/l1c/ultra_l1c.py +1 -5
  224. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +107 -60
  225. imap_processing/ultra/l2/ultra_l2.py +299 -0
  226. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +526 -0
  227. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +526 -0
  228. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +526 -0
  229. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +526 -0
  230. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -2
  231. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -0
  232. imap_processing/ultra/packet_definitions/README.md +38 -0
  233. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +15302 -482
  234. imap_processing/ultra/utils/ultra_l1_utils.py +13 -12
  235. imap_processing/utils.py +1 -1
  236. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +3 -2
  237. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/RECORD +264 -225
  238. imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
  239. imap_processing/mag/imap_mag_sdc-configuration_v001.yaml +0 -6
  240. imap_processing/mag/l1b/__init__.py +0 -0
  241. imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
  242. imap_processing/swe/l1b/swe_l1b_science.py +0 -699
  243. imap_processing/tests/swe/test_swe_l1b_science.py +0 -103
  244. imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
  245. imap_processing/ultra/lookup_tables/ultra_90_dps_exposure_compressed.cdf +0 -0
  246. /imap_processing/idex/packet_definitions/{idex_packet_definition.xml → idex_science_packet_definition.xml} +0 -0
  247. /imap_processing/tests/ialirt/{test_data → data}/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  248. /imap_processing/tests/ialirt/{test_data → data}/l0/461971383-404.bin +0 -0
  249. /imap_processing/tests/ialirt/{test_data → data}/l0/461971384-405.bin +0 -0
  250. /imap_processing/tests/ialirt/{test_data → data}/l0/461971385-406.bin +0 -0
  251. /imap_processing/tests/ialirt/{test_data → data}/l0/461971386-407.bin +0 -0
  252. /imap_processing/tests/ialirt/{test_data → data}/l0/461971387-408.bin +0 -0
  253. /imap_processing/tests/ialirt/{test_data → data}/l0/461971388-409.bin +0 -0
  254. /imap_processing/tests/ialirt/{test_data → data}/l0/461971389-410.bin +0 -0
  255. /imap_processing/tests/ialirt/{test_data → data}/l0/461971390-411.bin +0 -0
  256. /imap_processing/tests/ialirt/{test_data → data}/l0/461971391-412.bin +0 -0
  257. /imap_processing/tests/ialirt/{test_data → data}/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  258. /imap_processing/tests/ialirt/{test_data → data}/l0/IALiRT Raw Packet Telemetry.txt +0 -0
  259. /imap_processing/tests/ialirt/{test_data → data}/l0/apid01152.tlm +0 -0
  260. /imap_processing/tests/ialirt/{test_data → data}/l0/eu_SWP_IAL_20240826_152033.csv +0 -0
  261. /imap_processing/tests/ialirt/{test_data → data}/l0/hi_fsw_view_1_ccsds.bin +0 -0
  262. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.ccsds +0 -0
  263. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.csv +0 -0
  264. /imap_processing/tests/ialirt/{test_data → data}/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +0 -0
  265. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  266. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  267. /imap_processing/tests/ialirt/{test_data → data}/l0/sample_decoded_i-alirt_data.csv +0 -0
  268. /imap_processing/tests/mag/validation/{imap_calibration_mag_20240229_v01.cdf → calibration/imap_mag_l1b-calibration_20240229_v001.cdf} +0 -0
  269. /imap_processing/{swe/l1b/engineering_unit_convert_table.csv → tests/swe/lut/imap_swe_eu-conversion_20240510_v000.csv} +0 -0
  270. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/LICENSE +0 -0
  271. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
  272. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
@@ -83,8 +83,7 @@ def parse_histogram(dataset: xr.Dataset, attr_mgr: ImapCdfAttributes) -> xr.Data
83
83
  # initialize the starting bit for the sections of data
84
84
  section_start = 0
85
85
  # for each field type in the histogram data
86
- for field in HIST_DATA_META:
87
- data_meta = HIST_DATA_META[field]
86
+ for field, data_meta in HIST_DATA_META.items():
88
87
  # for each histogram binary string decompress
89
88
  # the data
90
89
  decompressed_data = [
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
21
21
  logger.setLevel(logging.INFO)
22
22
 
23
23
 
24
- def lo_l1a(dependency: Path, data_version: str) -> list[xr.Dataset]:
24
+ def lo_l1a(dependency: Path) -> list[xr.Dataset]:
25
25
  """
26
26
  Will process IMAP-Lo L0 data into L1A CDF data products.
27
27
 
@@ -30,8 +30,6 @@ def lo_l1a(dependency: Path, data_version: str) -> list[xr.Dataset]:
30
30
  dependency : Path
31
31
  Dependency file needed for data product creation.
32
32
  Should always be only one for L1A.
33
- data_version : str
34
- Version of the data product being created.
35
33
 
36
34
  Returns
37
35
  -------
@@ -51,7 +49,6 @@ def lo_l1a(dependency: Path, data_version: str) -> list[xr.Dataset]:
51
49
  attr_mgr = ImapCdfAttributes()
52
50
  attr_mgr.add_instrument_global_attrs(instrument="lo")
53
51
  attr_mgr.add_instrument_variable_attrs(instrument="lo", level="l1a")
54
- attr_mgr.add_global_attribute("Data_version", data_version)
55
52
 
56
53
  if LoAPID.ILO_SPIN in datasets_by_apid:
57
54
  logger.info(
@@ -8,10 +8,17 @@ import numpy as np
8
8
  import xarray as xr
9
9
 
10
10
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
11
- from imap_processing.spice.time import met_to_ttj2000ns
11
+ from imap_processing.lo.l1b.tof_conversions import (
12
+ TOF0_CONV,
13
+ TOF1_CONV,
14
+ TOF2_CONV,
15
+ TOF3_CONV,
16
+ )
17
+ from imap_processing.spice.geometry import SpiceFrame, instrument_pointing
18
+ from imap_processing.spice.time import met_to_ttj2000ns, ttj2000ns_to_et
12
19
 
13
20
 
14
- def lo_l1b(dependencies: dict, data_version: str) -> list[Path]:
21
+ def lo_l1b(dependencies: dict) -> list[Path]:
15
22
  """
16
23
  Will process IMAP-Lo L1A data into L1B CDF data products.
17
24
 
@@ -19,8 +26,6 @@ def lo_l1b(dependencies: dict, data_version: str) -> list[Path]:
19
26
  ----------
20
27
  dependencies : dict
21
28
  Dictionary of datasets needed for L1B data product creation in xarray Datasets.
22
- data_version : str
23
- Version of the data product being created.
24
29
 
25
30
  Returns
26
31
  -------
@@ -31,7 +36,6 @@ def lo_l1b(dependencies: dict, data_version: str) -> list[Path]:
31
36
  attr_mgr_l1b = ImapCdfAttributes()
32
37
  attr_mgr_l1b.add_instrument_global_attrs(instrument="lo")
33
38
  attr_mgr_l1b.add_instrument_variable_attrs(instrument="lo", level="l1b")
34
- attr_mgr_l1b.add_global_attribute("Data_version", data_version)
35
39
  # create the attribute manager to access L1A fillval attributes
36
40
  attr_mgr_l1a = ImapCdfAttributes()
37
41
  attr_mgr_l1a.add_instrument_variable_attrs(instrument="lo", level="l1a")
@@ -48,12 +52,36 @@ def lo_l1b(dependencies: dict, data_version: str) -> list[Path]:
48
52
  # Get the start and end times for each spin epoch
49
53
  acq_start, acq_end = convert_start_end_acq_times(spin_data)
50
54
  # Get the average spin durations for each epoch
51
- avg_spin_durations = get_avg_spin_durations(acq_start, acq_end) # noqa: F841
55
+ avg_spin_durations = get_avg_spin_durations(acq_start, acq_end)
52
56
  # get spin angle (0 - 360 degrees) for each DE
53
57
  spin_angle = get_spin_angle(l1a_de)
54
58
  # calculate and set the spin bin based on the spin angle
55
59
  # spin bins are 0 - 60 bins
56
60
  l1b_de = set_spin_bin(l1b_de, spin_angle)
61
+ # set the spin cycle for each direct event
62
+ l1b_de = set_spin_cycle(l1a_de, l1b_de)
63
+ # get spin start times for each event
64
+ spin_start_time = get_spin_start_times(l1a_de, l1b_de, spin_data, acq_end)
65
+ # get the absolute met for each event
66
+ l1b_de = set_event_met(l1a_de, l1b_de, spin_start_time, avg_spin_durations)
67
+ # set the epoch for each event
68
+ l1b_de = set_each_event_epoch(l1b_de)
69
+ # calculate the TOF1 for golden triples
70
+ # store in the l1a dataset to use in l1b calculations
71
+ l1a_de = calculate_tof1_for_golden_triples(l1a_de)
72
+ # set the coincidence type string for each direct event
73
+ l1b_de = set_coincidence_type(l1a_de, l1b_de, attr_mgr_l1a)
74
+ # convert the TOFs to engineering units
75
+ l1b_de = convert_tofs_to_eu(l1a_de, l1b_de, attr_mgr_l1a, attr_mgr_l1b)
76
+ # set the species for each direct event
77
+ l1b_de = identify_species(l1b_de)
78
+ # set the badtimes
79
+ l1b_de = set_bad_times(l1b_de)
80
+ # set the pointing direction for each direct event
81
+ l1b_de = set_pointing_direction(l1b_de)
82
+ # calculate and set the pointing bin based on the spin phase
83
+ # pointing bin is 3600 x 40 bins
84
+ l1b_de = set_pointing_bin(l1b_de)
57
85
 
58
86
  return [l1b_de]
59
87
 
@@ -211,6 +239,499 @@ def set_spin_bin(l1b_de: xr.Dataset, spin_angle: np.ndarray) -> xr.Dataset:
211
239
  return l1b_de
212
240
 
213
241
 
242
+ def set_spin_cycle(l1a_de: xr.Dataset, l1b_de: xr.Dataset) -> xr.Dataset:
243
+ """
244
+ Set the spin cycle for each direct event.
245
+
246
+ spin_cycle = spin_start + 7 + (esa_step - 1) * 2
247
+
248
+ where spin_start is the spin number for the first spin
249
+ in an Aggregated Science Cycle (ASC) and esa_step is the esa_step for a direct event
250
+
251
+ The 28 spins in a spin epoch spans one ASC.
252
+
253
+ Parameters
254
+ ----------
255
+ l1a_de : xarray.Dataset
256
+ The L1A DE dataset.
257
+ l1b_de : xarray.Dataset
258
+ The L1B DE dataset.
259
+
260
+ Returns
261
+ -------
262
+ l1b_de : xarray.Dataset
263
+ The L1B DE dataset with the spin cycle added for each direct event.
264
+ """
265
+ counts = l1a_de["de_count"].values
266
+ # split the esa_steps into ASC groups
267
+ de_asc_groups = np.split(l1a_de["esa_step"].values, np.cumsum(counts)[:-1])
268
+ spin_cycle = []
269
+ for i, esa_asc_group in enumerate(de_asc_groups):
270
+ # TODO: Spin Number does not reset for each pointing. Need to figure out
271
+ # how to retain this information across days
272
+ # increment the spin_start by 28 after each aggregated science cycle
273
+ spin_start = i * 28
274
+ # calculate the spin cycle for each DE in the ASC group
275
+ # TODO: Add equation number in algorithm document when new version is
276
+ # available. Add to docstring as well
277
+ spin_cycle.extend(spin_start + 7 + (esa_asc_group - 1) * 2)
278
+
279
+ l1b_de["spin_cycle"] = xr.DataArray(
280
+ spin_cycle,
281
+ dims=["epoch"],
282
+ # TODO: Add spin cycle to YAML file
283
+ # attrs=attr_mgr.get_variable_attributes("spin_cycle"),
284
+ )
285
+
286
+ return l1b_de
287
+
288
+
289
+ def get_spin_start_times(
290
+ l1a_de: xr.Dataset, l1b_de: xr.Dataset, spin_data: xr.Dataset, acq_end: xr.DataArray
291
+ ) -> xr.DataArray:
292
+ """
293
+ Get the start time for the spin that each direct event is in.
294
+
295
+ The resulting array of spin start times will be equal to the length of the direct
296
+ events. If two direct events occurred in the same spin, then there will be repeating
297
+ spin start times.
298
+
299
+ Parameters
300
+ ----------
301
+ l1a_de : xr.Dataset
302
+ The L1A DE dataset.
303
+ l1b_de : xr.Dataset
304
+ The L1B DE dataset.
305
+ spin_data : xr.Dataset
306
+ The L1A Spin dataset.
307
+ acq_end : xr.DataArray
308
+ The end acquisition times for each spin ASC.
309
+
310
+ Returns
311
+ -------
312
+ spin_start_time : xr.DataArray
313
+ The start time for the spin that each direct event is in.
314
+ """
315
+ met = l1a_de["met"].values
316
+ # Find the closest stop_acq for each shcoarse
317
+ closest_stop_acq_indices = np.abs(met[:, None] - acq_end.values).argmin(axis=1)
318
+ # There are 28 spins per epoch (1 aggregated science cycle)
319
+ # Set the spin_cycle_num to the spin number relative to the
320
+ # start of the ASC
321
+ spin_cycle_num = l1b_de["spin_cycle"] % 28
322
+ # Get the seconds portion of the start time for each spin
323
+ start_sec_spins = spin_data["start_sec_spin"].values[
324
+ closest_stop_acq_indices, spin_cycle_num
325
+ ]
326
+ # Get the subseconds portion of the spin start time and convert from
327
+ # microseconds to seconds
328
+ start_subsec_spins = (
329
+ spin_data["start_subsec_spin"].values[closest_stop_acq_indices, spin_cycle_num]
330
+ * 1e-6
331
+ )
332
+
333
+ # Combine the seconds and subseconds to get the start time for each spin
334
+ spin_start_time = start_sec_spins + start_subsec_spins
335
+ return xr.DataArray(spin_start_time)
336
+
337
+
338
+ def set_event_met(
339
+ l1a_de: xr.Dataset,
340
+ l1b_de: xr.Dataset,
341
+ spin_start_time: xr.DataArray,
342
+ avg_spin_durations: xr.DataArray,
343
+ ) -> xr.Dataset:
344
+ """
345
+ Get the event MET for each direct event.
346
+
347
+ Each direct event is converted from a data number to engineering unit in seconds.
348
+ de_eu_time de_dn_time / 4096 * avg_spin_duration
349
+ where de_time is the direct event time Data Number (DN) and avg_spin_duration
350
+ is the average spin duration for the ASC that the event was measured in.
351
+
352
+ The direct event time is the time of direct event relative to the start of the spin.
353
+ The event MET is the sum of the start time of the spin and the
354
+ direct event EU time.
355
+
356
+ Parameters
357
+ ----------
358
+ l1a_de : xr.Dataset
359
+ The L1A DE dataset.
360
+ l1b_de : xr.Dataset
361
+ The L1B DE dataset.
362
+ spin_start_time : np.ndarray
363
+ The start time for the spin that each direct event is in.
364
+ avg_spin_durations : xr.DataArray
365
+ The average spin duration for each epoch.
366
+
367
+ Returns
368
+ -------
369
+ l1b_de : xr.Dataset
370
+ The L1B DE dataset with the event MET.
371
+ """
372
+ counts = l1a_de["de_count"].values
373
+ de_time_asc_groups = np.split(l1a_de["de_time"].values, np.cumsum(counts)[:-1])
374
+ de_times_eu = []
375
+ for i, de_time_asc in enumerate(de_time_asc_groups):
376
+ # DE Time is 12 bit DN. The max possible value is 4095
377
+ # divide by 4096 to get fraction of a spin duration
378
+ de_times_eu.extend(de_time_asc / 4096 * avg_spin_durations[i].values)
379
+
380
+ l1b_de["event_met"] = xr.DataArray(
381
+ spin_start_time + de_times_eu,
382
+ dims=["epoch"],
383
+ # attrs=attr_mgr.get_variable_attributes("epoch")
384
+ )
385
+ return l1b_de
386
+
387
+
388
+ def set_each_event_epoch(l1b_de: xr.Dataset) -> xr.Dataset:
389
+ """
390
+ Set the epoch for each direct event.
391
+
392
+ Parameters
393
+ ----------
394
+ l1b_de : xr.Dataset
395
+ The L1B DE dataset.
396
+
397
+ Returns
398
+ -------
399
+ l1b_de : xr.Dataset
400
+ The L1B DE dataset with the epoch set for each event.
401
+ """
402
+ l1b_de["epoch"] = xr.DataArray(
403
+ met_to_ttj2000ns(l1b_de["event_met"].values),
404
+ dims=["epoch"],
405
+ # attrs=attr_mgr.get_variable_attributes("epoch")
406
+ )
407
+ return l1b_de
408
+
409
+
410
+ def calculate_tof1_for_golden_triples(l1a_de: xr.Dataset) -> xr.Dataset:
411
+ """
412
+ Calculate the TOF1 for golden triples.
413
+
414
+ TOF1 is not transmitted for golden triples, but is recovered on the
415
+ ground using the TOF0, TOF2, TOF3, and CKSUM values. The equation is:
416
+ TOF1 = (TOF0 + TOF3 - TOF2 - CKSUM - left_cksm_bound) << 1
417
+
418
+ where left_cksm_bound is the left checksum boundary value. This is a
419
+ constant value that is not transmitted in the telemetry.
420
+
421
+ Parameters
422
+ ----------
423
+ l1a_de : xr.Dataset
424
+ The L1A DE dataset.
425
+
426
+ Returns
427
+ -------
428
+ l1a_de : xr.Dataset
429
+ The L1A DE dataset with the TOF1 calculated for golden triples.
430
+ """
431
+ for idx, coin_type in enumerate(l1a_de["coincidence_type"].values):
432
+ if coin_type == 0 and l1a_de["mode"][idx] == 0:
433
+ # Calculate TOF1
434
+ # TOF1 equation requires values to be right bit shifted. These values were
435
+ # originally right bit shifted when packed in the telemetry packet, but were
436
+ # left bit shifted for the L1A product. Need to right bit shift them again
437
+ # to apply the TOF1 equation
438
+ tof0 = l1a_de["tof0"][idx] >> 1
439
+ tof2 = l1a_de["tof2"][idx] >> 1
440
+ tof3 = l1a_de["tof3"][idx] >> 1
441
+ cksm = l1a_de["cksm"][idx] >> 1
442
+ # TODO: will get left checksum boundary from LUT table when available
443
+ left_cksm_bound = -21
444
+ # Calculate TOF1, then left bit shift it to store it with the rest of the
445
+ # left shifted L1A dataset data.
446
+ l1a_de["tof1"][idx] = (tof0 + tof3 - tof2 - cksm - left_cksm_bound) << 1
447
+ return l1a_de
448
+
449
+
450
+ def set_coincidence_type(
451
+ l1a_de: xr.Dataset,
452
+ l1b_de: xr.Dataset,
453
+ attr_mgr_l1a: ImapCdfAttributes,
454
+ ) -> xr.Dataset:
455
+ """
456
+ Set the coincidence type for each direct event.
457
+
458
+ The coincidence type is a string that indicates the type of coincidence
459
+ for each direct event. The string is a combination of the following depending
460
+ on whether the TOF or CKSM value is present (1) or absent (0) and the value
461
+ of the mode for each direct event:
462
+ "<TOF0><TOF1><TOF2><TOF3><CKSM><Mode>"
463
+
464
+ Parameters
465
+ ----------
466
+ l1a_de : xarray.Dataset
467
+ The L1A DE dataset.
468
+ l1b_de : xarray.Dataset
469
+ The L1B DE dataset.
470
+ attr_mgr_l1a : ImapCdfAttributes
471
+ Attribute manager used to get the fill values for the L1A DE dataset.
472
+
473
+ Returns
474
+ -------
475
+ l1b_de : xarray.Dataset
476
+ The L1B DE dataset with the coincidence type added.
477
+ """
478
+ tof0_fill = attr_mgr_l1a.get_variable_attributes("tof0")["FILLVAL"]
479
+ tof0_mask = (l1a_de["tof0"].values != tof0_fill).astype(int)
480
+ tof1_fill = attr_mgr_l1a.get_variable_attributes("tof1")["FILLVAL"]
481
+ tof1_mask = (l1a_de["tof1"].values != tof1_fill).astype(int)
482
+ tof2_fill = attr_mgr_l1a.get_variable_attributes("tof2")["FILLVAL"]
483
+ tof2_mask = (l1a_de["tof2"].values != tof2_fill).astype(int)
484
+ tof3_fill = attr_mgr_l1a.get_variable_attributes("tof3")["FILLVAL"]
485
+ tof3_mask = (l1a_de["tof3"].values != tof3_fill).astype(int)
486
+ cksm_fill = attr_mgr_l1a.get_variable_attributes("cksm")["FILLVAL"]
487
+ cksm_mask = (l1a_de["cksm"].values != cksm_fill).astype(int)
488
+
489
+ coincidence_type = [
490
+ f"{tof0_mask[i]}{tof1_mask[i]}{tof2_mask[i]}{tof3_mask[i]}{cksm_mask[i]}{l1a_de['mode'].values[i]}"
491
+ for i in range(len(l1a_de["direct_events"]))
492
+ ]
493
+
494
+ l1b_de["coincidence_type"] = xr.DataArray(
495
+ coincidence_type,
496
+ dims=["epoch"],
497
+ # TODO: Add coincidence_type to YAML file
498
+ # attrs=attr_mgr.get_variable_attributes("spin_cycle"),
499
+ )
500
+
501
+ return l1b_de
502
+
503
+
504
+ def convert_tofs_to_eu(
505
+ l1a_de: xr.Dataset,
506
+ l1b_de: xr.Dataset,
507
+ attr_mgr_l1a: ImapCdfAttributes,
508
+ attr_mgr_l1b: ImapCdfAttributes,
509
+ ) -> xr.Dataset:
510
+ """
511
+ Convert the TOFs to engineering units.
512
+
513
+ The TOFs are converted from data numbers (DN) to engineering units (EU) using the
514
+ following equation:
515
+ TOF_EU = C0 + C1 * TOF_DN
516
+
517
+ where C0 and C1 are the conversion coefficients for each TOF.
518
+
519
+ This equation is applied to all four TOFs (TOF0, TOF1, TOF2, TOF3).
520
+
521
+ Parameters
522
+ ----------
523
+ l1a_de : xarray.Dataset
524
+ The L1A DE dataset.
525
+ l1b_de : xarray.Dataset
526
+ The L1B DE dataset.
527
+ attr_mgr_l1a : ImapCdfAttributes
528
+ Attribute manager used to get the fill values for the L1A DE dataset.
529
+ attr_mgr_l1b : ImapCdfAttributes
530
+ Attribute manager used to get the fill values for the L1B DE dataset.
531
+
532
+ Returns
533
+ -------
534
+ l1b_de : xarray.Dataset
535
+ The L1B DE dataset with the TOFs converted to engineering units.
536
+ """
537
+ tof_fields = ["tof0", "tof1", "tof2", "tof3"]
538
+ tof_conversions = [TOF0_CONV, TOF1_CONV, TOF2_CONV, TOF3_CONV]
539
+
540
+ # Loop through the TOF fields and convert them to engineering units
541
+ for tof, conv in zip(tof_fields, tof_conversions):
542
+ # Get the fill value for the L1A and L1B TOF
543
+ fillval_1a = attr_mgr_l1a.get_variable_attributes(tof)["FILLVAL"]
544
+ fillval_1b = attr_mgr_l1b.get_variable_attributes(tof)["FILLVAL"]
545
+ # Create a mask for the TOF
546
+ mask = l1a_de[tof] != fillval_1a
547
+ # Convert the DN TOF to EU and add the EU TOF to the dataset.
548
+ # If the TOF is not present, set it to the fill value for the L1B TOF data.
549
+ tof_eu = np.where(
550
+ mask,
551
+ conv.C0 + conv.C1 * l1a_de[tof],
552
+ fillval_1b,
553
+ )
554
+ l1b_de[tof] = xr.DataArray(
555
+ tof_eu,
556
+ dims=["epoch"],
557
+ attrs=attr_mgr_l1b.get_variable_attributes(tof),
558
+ )
559
+
560
+ return l1b_de
561
+
562
+
563
+ def identify_species(l1b_de: xr.Dataset) -> xr.Dataset:
564
+ """
565
+ Identify the species for each direct event.
566
+
567
+ The species are determined using the U_PAC 7-13kV range table with the TOF2 value.
568
+ Each event is set to "H" for Hydrogen, "O" for Oxygen, or "U" for Unknown.
569
+
570
+ See the species identification section in the Lo algorithm document for more
571
+ information on the ranges used to identify the species.
572
+
573
+ Parameters
574
+ ----------
575
+ l1b_de : xarray.Dataset
576
+ The L1B DE dataset.
577
+
578
+ Returns
579
+ -------
580
+ l1b_de : xarray.Dataset
581
+ The L1B DE dataset with the species identified.
582
+ """
583
+ # Define upper and lower ranges for Hydrogen and Oxygen
584
+ # Table defined in 9.3.4.4 of the Lo algorithm document
585
+ # UNH-IMAP-Lo-27850-6002-Data-Product-Algorithms-v9_&_IMAP-LoMappingAlgorithm
586
+ # The ranges are used for U_PAC voltages 7-12kV. Lo does not expect to use
587
+ # voltages outside of that range.
588
+ range_hydrogen = (13, 40)
589
+ range_oxygen = (75, 200)
590
+
591
+ # Initialize the species array with U for Unknown
592
+ species = np.full(len(l1b_de["epoch"]), "U")
593
+
594
+ tof2 = l1b_de["tof2"]
595
+ # Check for range Hydrogen using the TOF2 value
596
+ mask_h = (tof2 >= range_hydrogen[0]) & (tof2 <= range_hydrogen[1])
597
+ species[mask_h] = "H"
598
+
599
+ # Check for range Oxygen using the TOF2 value
600
+ mask_oxygen = (tof2 >= range_oxygen[0]) & (tof2 <= range_oxygen[1])
601
+ species[mask_oxygen] = "O"
602
+
603
+ # Add species to the dataset
604
+ l1b_de["species"] = xr.DataArray(
605
+ species,
606
+ dims=["epoch"],
607
+ # TODO: Add to yaml
608
+ # attrs=attr_mgr.get_variable_attributes("species"),
609
+ )
610
+
611
+ return l1b_de
612
+
613
+
614
+ def set_bad_times(l1b_de: xr.Dataset) -> xr.Dataset:
615
+ """
616
+ Set the bad times for each direct event.
617
+
618
+ Parameters
619
+ ----------
620
+ l1b_de : xarray.Dataset
621
+ The L1B DE dataset.
622
+
623
+ Returns
624
+ -------
625
+ l1b_de : xarray.Dataset
626
+ The L1B DE dataset with the bad times added.
627
+ """
628
+ # Initialize all times as not bad for now
629
+ # TODO: Update to set badtimes based on criteria that
630
+ # will be defined in the algorithm document
631
+ # 1 = badtime, 0 = not badtime
632
+ l1b_de["badtimes"] = xr.DataArray(
633
+ np.zeros(len(l1b_de["epoch"]), dtype=int),
634
+ dims=["epoch"],
635
+ # TODO: Add to yaml
636
+ # attrs=attr_mgr.get_variable_attributes("bad_times"),
637
+ )
638
+
639
+ return l1b_de
640
+
641
+
642
+ def set_pointing_direction(l1b_de: xr.Dataset) -> xr.Dataset:
643
+ """
644
+ Set the pointing direction for each direct event.
645
+
646
+ The pointing direction is determined using the SPICE instrument pointing
647
+ function. The pointing direction are two 1D vectors in units of degrees
648
+ for longitude and latitude sharing the same epoch dimension.
649
+
650
+ Parameters
651
+ ----------
652
+ l1b_de : xarray.Dataset
653
+ The L1B DE dataset.
654
+
655
+ Returns
656
+ -------
657
+ l1b_de : xarray.Dataset
658
+ The L1B DE dataset with the pointing direction added.
659
+ """
660
+ # Get the pointing bin for each DE
661
+ et = ttj2000ns_to_et(l1b_de["epoch"])
662
+
663
+ direction = instrument_pointing(et, SpiceFrame.IMAP_LO_BASE, SpiceFrame.IMAP_DPS)
664
+ # TODO: Need to ask Lo what to do if a latitude is outside of the
665
+ # +/-2 degree range. Is that possible?
666
+ l1b_de["direction_lon"] = xr.DataArray(
667
+ direction[:, 0],
668
+ dims=["epoch"],
669
+ # TODO: Add direction_lon to YAML file
670
+ # attrs=attr_mgr.get_variable_attributes("direction_lon"),
671
+ )
672
+
673
+ l1b_de["direction_lat"] = xr.DataArray(
674
+ direction[:, 1],
675
+ dims=["epoch"],
676
+ # TODO: Add direction_lat to YAML file
677
+ # attrs=attr_mgr.get_variable_attributes("direction_lat"),
678
+ )
679
+
680
+ return l1b_de
681
+
682
+
683
+ def set_pointing_bin(l1b_de: xr.Dataset) -> xr.Dataset:
684
+ """
685
+ Set the pointing bin for each direct event.
686
+
687
+ The pointing bins are defined as 3600 bins for longitude and 40 bins for latitude.
688
+ Each bin is 0.1 degrees. The bins are defined as follows:
689
+ Longitude bins: -180 to 180 degrees
690
+ Latitude bins: -2 to 2 degrees
691
+
692
+ Parameters
693
+ ----------
694
+ l1b_de : xarray.Dataset
695
+ The L1B DE dataset.
696
+
697
+ Returns
698
+ -------
699
+ l1b_de : xarray.Dataset
700
+ The L1B DE dataset with the pointing bins added.
701
+ """
702
+ # First column: latitudes
703
+ lats = l1b_de["direction_lat"]
704
+ # Second column: longitudes
705
+ lons = l1b_de["direction_lon"]
706
+
707
+ # Define bin edges
708
+ # 3600 bins, 0.1° each
709
+ lon_bins = np.linspace(-180, 180, 3601)
710
+ # 40 bins, 0.1° each
711
+ lat_bins = np.linspace(-2, 2, 41)
712
+
713
+ # put the lons and lats into bins
714
+ # shift to 0-based index
715
+ lon_bins = np.digitize(lons, lon_bins) - 1
716
+ lat_bins = np.digitize(lats, lat_bins) - 1
717
+
718
+ l1b_de["pointing_bin_lon"] = xr.DataArray(
719
+ lon_bins,
720
+ dims=["epoch"],
721
+ # TODO: Add pointing_bin_lon to YAML file
722
+ # attrs=attr_mgr.get_variable_attributes("pointing_bin_lon"),
723
+ )
724
+
725
+ l1b_de["pointing_bin_lat"] = xr.DataArray(
726
+ lat_bins,
727
+ dims=["epoch"],
728
+ # TODO: Add point_bin_lat to YAML file
729
+ # attrs=attr_mgr.get_variable_attributes("pointing_bin_lat"),
730
+ )
731
+
732
+ return l1b_de
733
+
734
+
214
735
  # TODO: This is going to work differently when I sample data.
215
736
  # The data_fields input is temporary.
216
737
  def create_datasets(
@@ -0,0 +1,11 @@
1
+ """Lo TOF EU Conversions."""
2
+
3
+ from collections import namedtuple
4
+
5
+ tof_conv = namedtuple("tof_conv", ["C0", "C1"])
6
+ # TOF conversion coefficients from Lo's TOF Conversion_annotated.docx
7
+ # TODO: Ask Lo to put these in the algorithm document for better reference
8
+ TOF0_CONV = tof_conv(C0=5.52524e-01, C1=1.68374e-01)
9
+ TOF1_CONV = tof_conv(C0=-7.20181e-01, C1=1.65124e-01)
10
+ TOF2_CONV = tof_conv(C0=3.74422e-01, C1=1.66409e-01)
11
+ TOF3_CONV = tof_conv(C0=4.41970e-01, C1=1.72024e-01)
@@ -11,7 +11,7 @@ from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
11
11
  from imap_processing.spice.time import met_to_ttj2000ns
12
12
 
13
13
 
14
- def lo_l1c(dependencies: dict, data_version: str) -> list[Path]:
14
+ def lo_l1c(dependencies: dict) -> list[Path]:
15
15
  """
16
16
  Will process IMAP-Lo L1B data into L1C CDF data products.
17
17
 
@@ -19,8 +19,6 @@ def lo_l1c(dependencies: dict, data_version: str) -> list[Path]:
19
19
  ----------
20
20
  dependencies : dict
21
21
  Dictionary of datasets needed for L1C data product creation in xarray Datasets.
22
- data_version : str
23
- Version of the data product being created.
24
22
 
25
23
  Returns
26
24
  -------
@@ -31,7 +29,6 @@ def lo_l1c(dependencies: dict, data_version: str) -> list[Path]:
31
29
  attr_mgr = ImapCdfAttributes()
32
30
  attr_mgr.add_instrument_global_attrs(instrument="lo")
33
31
  attr_mgr.add_instrument_variable_attrs(instrument="lo", level="l1c")
34
- attr_mgr.add_global_attribute("Data_version", data_version)
35
32
 
36
33
  # if the dependencies are used to create Annotated Direct Events
37
34
  if "imap_lo_l1b_de" in dependencies:
@@ -59,6 +59,23 @@ class PrimarySensor(Enum):
59
59
  MAGI = 1
60
60
 
61
61
 
62
+ class VecSec(Enum):
63
+ """Enum for all valid vector rates (Vectors per second)."""
64
+
65
+ ONE_VEC_PER_S = 1
66
+ TWO_VECS_PER_S = 2
67
+ FOUR_VECS_PER_S = 4
68
+ EIGHT_VECS_PER_S = 8
69
+ SIXTEEN_VECS_PER_S = 16
70
+ THIRTY_TWO_VECS_PER_S = 32
71
+ SIXTY_FOUR_VECS_PER_S = 64
72
+ ONE_TWENTY_EIGHT_VECS_PER_S = 128
73
+
74
+
75
+ # Possible sensor rates
76
+ POSSIBLE_RATES = [e.value for e in VecSec]
77
+
78
+
62
79
  class ModeFlags(Enum):
63
80
  """Enum for MAG mode flags: burst and normal (BURST + NORM)."""
64
81
 
@@ -114,3 +131,29 @@ MAX_FINE_TIME = np.iinfo(np.uint16).max # maximum 16 bit unsigned int
114
131
  AXIS_COUNT = 3
115
132
  RANGE_BIT_WIDTH = 2
116
133
  MAX_COMPRESSED_VECTOR_BITS = 60
134
+
135
+
136
+ def vectors_per_second_from_string(vecsec_string: str) -> dict:
137
+ """
138
+ Extract the vectors per second from a string into a dictionary.
139
+
140
+ Dictionary format: {start_time: vecsec, start_time: vecsec}.
141
+
142
+ Parameters
143
+ ----------
144
+ vecsec_string : str
145
+ A string of the form "start:vecsec,start:vecsec" where start is the time in
146
+ nanoseconds and vecsec is the number of vectors per second.
147
+
148
+ Returns
149
+ -------
150
+ dict
151
+ A dictionary of the form {start_time: vecsec, start_time: vecsec}.
152
+ """
153
+ vecsec_dict = {}
154
+ vecsec_segments = vecsec_string.split(",")
155
+ for vecsec_segment in vecsec_segments:
156
+ start_time, vecsec = vecsec_segment.split(":")
157
+ vecsec_dict[int(start_time)] = int(vecsec)
158
+
159
+ return vecsec_dict