imap-processing 0.9.0__py3-none-any.whl → 0.11.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 (243) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +749 -442
  3. imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +7 -0
  4. imap_processing/cdf/config/imap_glows_l1a_variable_attrs.yaml +8 -2
  5. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +0 -1
  6. imap_processing/cdf/config/imap_glows_l2_variable_attrs.yaml +358 -0
  7. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +59 -25
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +22 -0
  9. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +32 -8
  10. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +94 -5
  11. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +65 -37
  12. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +16 -1
  13. imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +7 -0
  14. imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +14 -14
  15. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +25 -24
  16. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +238 -0
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +100 -92
  18. imap_processing/cdf/utils.py +2 -2
  19. imap_processing/cli.py +45 -9
  20. imap_processing/codice/codice_l1a.py +104 -58
  21. imap_processing/codice/constants.py +111 -155
  22. imap_processing/codice/data/esa_sweep_values.csv +256 -256
  23. imap_processing/codice/data/lo_stepping_values.csv +128 -128
  24. imap_processing/ena_maps/ena_maps.py +519 -0
  25. imap_processing/ena_maps/utils/map_utils.py +145 -0
  26. imap_processing/ena_maps/utils/spatial_utils.py +226 -0
  27. imap_processing/glows/__init__.py +3 -0
  28. imap_processing/glows/ancillary/imap_glows_pipeline_settings_v001.json +52 -0
  29. imap_processing/glows/l1a/glows_l1a.py +72 -14
  30. imap_processing/glows/l1b/glows_l1b.py +2 -1
  31. imap_processing/glows/l1b/glows_l1b_data.py +25 -1
  32. imap_processing/glows/l2/glows_l2.py +324 -0
  33. imap_processing/glows/l2/glows_l2_data.py +156 -51
  34. imap_processing/hi/l1a/science_direct_event.py +57 -51
  35. imap_processing/hi/l1b/hi_l1b.py +43 -28
  36. imap_processing/hi/l1c/hi_l1c.py +225 -42
  37. imap_processing/hi/utils.py +20 -3
  38. imap_processing/hit/l0/constants.py +2 -2
  39. imap_processing/hit/l0/decom_hit.py +1 -1
  40. imap_processing/hit/l1a/hit_l1a.py +94 -13
  41. imap_processing/hit/l1b/hit_l1b.py +158 -9
  42. imap_processing/ialirt/l0/process_codicehi.py +156 -0
  43. imap_processing/ialirt/l0/process_codicelo.py +5 -2
  44. imap_processing/ialirt/packet_definitions/ialirt.xml +28 -20
  45. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +241 -0
  46. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +170 -0
  47. imap_processing/ialirt/packet_definitions/ialirt_swe.xml +258 -0
  48. imap_processing/ialirt/process_ephemeris.py +72 -40
  49. imap_processing/idex/decode.py +241 -0
  50. imap_processing/idex/idex_l1a.py +143 -81
  51. imap_processing/idex/idex_l1b.py +244 -10
  52. imap_processing/lo/l0/lo_science.py +61 -0
  53. imap_processing/lo/l1a/lo_l1a.py +98 -10
  54. imap_processing/lo/l1b/lo_l1b.py +2 -2
  55. imap_processing/lo/l1c/lo_l1c.py +2 -2
  56. imap_processing/lo/packet_definitions/lo_xtce.xml +1082 -9178
  57. imap_processing/mag/l0/decom_mag.py +2 -2
  58. imap_processing/mag/l1a/mag_l1a.py +7 -7
  59. imap_processing/mag/l1a/mag_l1a_data.py +62 -30
  60. imap_processing/mag/l1b/mag_l1b.py +11 -6
  61. imap_processing/quality_flags.py +18 -3
  62. imap_processing/spice/geometry.py +149 -177
  63. imap_processing/spice/kernels.py +26 -26
  64. imap_processing/spice/spin.py +233 -0
  65. imap_processing/spice/time.py +96 -31
  66. imap_processing/swapi/l1/swapi_l1.py +60 -31
  67. imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +363 -384
  68. imap_processing/swe/l1a/swe_l1a.py +8 -3
  69. imap_processing/swe/l1a/swe_science.py +24 -24
  70. imap_processing/swe/l1b/swe_l1b.py +2 -1
  71. imap_processing/swe/l1b/swe_l1b_science.py +181 -122
  72. imap_processing/swe/l2/swe_l2.py +337 -70
  73. imap_processing/swe/utils/swe_utils.py +28 -0
  74. imap_processing/tests/cdf/test_utils.py +2 -2
  75. imap_processing/tests/codice/conftest.py +20 -17
  76. imap_processing/tests/codice/data/validation/imap_codice_l1a_hskp_20241110193622_v0.0.0.cdf +0 -0
  77. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  78. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  79. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-angular_20241110193700_v0.0.0.cdf +0 -0
  80. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-priority_20241110193700_v0.0.0.cdf +0 -0
  81. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-species_20241110193700_v0.0.0.cdf +0 -0
  82. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-angular_20241110193700_v0.0.0.cdf +0 -0
  83. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-priority_20241110193700_v0.0.0.cdf +0 -0
  84. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-species_20241110193700_v0.0.0.cdf +0 -0
  85. imap_processing/tests/codice/test_codice_l0.py +55 -121
  86. imap_processing/tests/codice/test_codice_l1a.py +147 -59
  87. imap_processing/tests/conftest.py +81 -22
  88. imap_processing/tests/ena_maps/test_ena_maps.py +309 -0
  89. imap_processing/tests/ena_maps/test_map_utils.py +286 -0
  90. imap_processing/tests/ena_maps/test_spatial_utils.py +161 -0
  91. imap_processing/tests/glows/conftest.py +7 -1
  92. imap_processing/tests/glows/test_glows_l1a_cdf.py +3 -7
  93. imap_processing/tests/glows/test_glows_l1a_data.py +34 -6
  94. imap_processing/tests/glows/test_glows_l1b_data.py +29 -17
  95. imap_processing/tests/glows/test_glows_l2.py +101 -0
  96. imap_processing/tests/hi/conftest.py +3 -3
  97. imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
  98. imap_processing/tests/hi/data/l1/imap_his_pset-calibration-prod-config_20240101_v001.csv +31 -0
  99. imap_processing/tests/hi/test_hi_l1b.py +14 -9
  100. imap_processing/tests/hi/test_hi_l1c.py +136 -36
  101. imap_processing/tests/hi/test_l1a.py +0 -2
  102. imap_processing/tests/hi/test_science_direct_event.py +18 -14
  103. imap_processing/tests/hi/test_utils.py +16 -11
  104. imap_processing/tests/hit/helpers/__init__.py +0 -0
  105. imap_processing/tests/hit/helpers/l1_validation.py +405 -0
  106. imap_processing/tests/hit/test_data/sci_sample.ccsds +0 -0
  107. imap_processing/tests/hit/test_decom_hit.py +8 -10
  108. imap_processing/tests/hit/test_hit_l1a.py +117 -180
  109. imap_processing/tests/hit/test_hit_l1b.py +149 -55
  110. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -0
  111. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +62 -0
  112. imap_processing/tests/ialirt/test_data/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  113. imap_processing/tests/ialirt/test_data/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  114. imap_processing/tests/ialirt/test_data/l0/eu_SWP_IAL_20240826_152033.csv +644 -0
  115. imap_processing/tests/ialirt/test_data/l0/hi_fsw_view_1_ccsds.bin +0 -0
  116. imap_processing/tests/ialirt/test_data/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +914 -0
  117. imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  118. imap_processing/tests/ialirt/unit/test_process_codicehi.py +106 -0
  119. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +33 -5
  120. imap_processing/tests/ialirt/unit/test_process_swapi.py +85 -0
  121. imap_processing/tests/ialirt/unit/test_process_swe.py +106 -0
  122. imap_processing/tests/idex/conftest.py +29 -1
  123. imap_processing/tests/idex/test_data/compressed_2023_102_14_24_55.pkts +0 -0
  124. imap_processing/tests/idex/test_data/non_compressed_2023_102_14_22_26.pkts +0 -0
  125. imap_processing/tests/idex/test_idex_l0.py +6 -3
  126. imap_processing/tests/idex/test_idex_l1a.py +151 -1
  127. imap_processing/tests/idex/test_idex_l1b.py +124 -2
  128. imap_processing/tests/lo/test_lo_l1a.py +62 -2
  129. imap_processing/tests/lo/test_lo_science.py +85 -0
  130. imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SPIN_EU.csv +2 -0
  131. imap_processing/tests/mag/conftest.py +16 -0
  132. imap_processing/tests/mag/test_mag_decom.py +6 -4
  133. imap_processing/tests/mag/test_mag_l1a.py +36 -7
  134. imap_processing/tests/mag/test_mag_l1b.py +55 -4
  135. imap_processing/tests/mag/test_mag_validation.py +148 -0
  136. imap_processing/tests/mag/validation/L1a/T001/all_p_ones.txt +19200 -0
  137. imap_processing/tests/mag/validation/L1a/T001/mag-l0-l1a-t001-in.bin +0 -0
  138. imap_processing/tests/mag/validation/L1a/T001/mag-l0-l1a-t001-out.csv +17 -0
  139. imap_processing/tests/mag/validation/L1a/T002/all_n_ones.txt +19200 -0
  140. imap_processing/tests/mag/validation/L1a/T002/mag-l0-l1a-t002-in.bin +0 -0
  141. imap_processing/tests/mag/validation/L1a/T002/mag-l0-l1a-t002-out.csv +17 -0
  142. imap_processing/tests/mag/validation/L1a/T003/field_like.txt +19200 -0
  143. imap_processing/tests/mag/validation/L1a/T003/mag-l0-l1a-t003-in.bin +0 -0
  144. imap_processing/tests/mag/validation/L1a/T003/mag-l0-l1a-t003-out.csv +17 -0
  145. imap_processing/tests/mag/validation/L1a/T004/field_like.txt +19200 -0
  146. imap_processing/tests/mag/validation/L1a/T004/mag-l0-l1a-t004-in.bin +0 -0
  147. imap_processing/tests/mag/validation/L1a/T004/mag-l0-l1a-t004-out.csv +17 -0
  148. imap_processing/tests/mag/validation/L1a/T005/field_like_range_change.txt +19200 -0
  149. imap_processing/tests/mag/validation/L1a/T005/mag-l0-l1a-t005-in.bin +0 -0
  150. imap_processing/tests/mag/validation/L1a/T005/mag-l0-l1a-t005-out.csv +17 -0
  151. imap_processing/tests/mag/validation/L1a/T006/hdr_field.txt +19200 -0
  152. imap_processing/tests/mag/validation/L1a/T006/mag-l0-l1a-t006-in.bin +0 -0
  153. imap_processing/tests/mag/validation/L1a/T006/mag-l0-l1a-t006-out.csv +17 -0
  154. imap_processing/tests/mag/validation/L1a/T007/hdr_field_and_range_change.txt +19200 -0
  155. imap_processing/tests/mag/validation/L1a/T007/mag-l0-l1a-t007-in.bin +0 -0
  156. imap_processing/tests/mag/validation/L1a/T007/mag-l0-l1a-t007-out.csv +17 -0
  157. imap_processing/tests/mag/validation/L1a/T008/field_like_range_change.txt +19200 -0
  158. imap_processing/tests/mag/validation/L1a/T008/mag-l0-l1a-t008-in.bin +0 -0
  159. imap_processing/tests/mag/validation/L1a/T008/mag-l0-l1a-t008-out.csv +17 -0
  160. imap_processing/tests/mag/validation/L1b/T009/data.bin +0 -0
  161. imap_processing/tests/mag/validation/L1b/T009/field_like_all_ranges.txt +19200 -0
  162. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-in.csv +17 -0
  163. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +17 -0
  164. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +17 -0
  165. imap_processing/tests/mag/validation/L1b/T010/data.bin +0 -0
  166. imap_processing/tests/mag/validation/L1b/T010/field_like_all_ranges.txt +19200 -0
  167. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-in.csv +17 -0
  168. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-magi-out.csv +17 -0
  169. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-mago-out.csv +17 -0
  170. imap_processing/tests/mag/validation/L1b/T011/data.bin +0 -0
  171. imap_processing/tests/mag/validation/L1b/T011/field_like_all_ranges.txt +19200 -0
  172. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-in.csv +17 -0
  173. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +17 -0
  174. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +17 -0
  175. imap_processing/tests/spice/test_geometry.py +128 -133
  176. imap_processing/tests/spice/test_kernels.py +37 -37
  177. imap_processing/tests/spice/test_spin.py +184 -0
  178. imap_processing/tests/spice/test_time.py +43 -20
  179. imap_processing/tests/swapi/test_swapi_l1.py +11 -10
  180. imap_processing/tests/swapi/test_swapi_l2.py +13 -3
  181. imap_processing/tests/swe/test_swe_l1a.py +1 -1
  182. imap_processing/tests/swe/test_swe_l1b.py +20 -3
  183. imap_processing/tests/swe/test_swe_l1b_science.py +54 -35
  184. imap_processing/tests/swe/test_swe_l2.py +148 -5
  185. imap_processing/tests/test_cli.py +39 -7
  186. imap_processing/tests/test_quality_flags.py +19 -19
  187. imap_processing/tests/test_utils.py +3 -2
  188. imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
  189. imap_processing/tests/ultra/test_data/mock_data.py +161 -0
  190. imap_processing/tests/ultra/unit/conftest.py +73 -0
  191. imap_processing/tests/ultra/unit/test_badtimes.py +58 -0
  192. imap_processing/tests/ultra/unit/test_cullingmask.py +87 -0
  193. imap_processing/tests/ultra/unit/test_de.py +61 -60
  194. imap_processing/tests/ultra/unit/test_ultra_l1a.py +3 -3
  195. imap_processing/tests/ultra/unit/test_ultra_l1b.py +51 -77
  196. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +5 -5
  197. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +114 -0
  198. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +86 -26
  199. imap_processing/tests/ultra/unit/test_ultra_l1c.py +1 -1
  200. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +3 -3
  201. imap_processing/ultra/constants.py +11 -1
  202. imap_processing/ultra/l1a/ultra_l1a.py +2 -2
  203. imap_processing/ultra/l1b/badtimes.py +22 -5
  204. imap_processing/ultra/l1b/cullingmask.py +31 -5
  205. imap_processing/ultra/l1b/de.py +32 -37
  206. imap_processing/ultra/l1b/extendedspin.py +44 -20
  207. imap_processing/ultra/l1b/ultra_l1b.py +21 -22
  208. imap_processing/ultra/l1b/ultra_l1b_culling.py +190 -0
  209. imap_processing/ultra/l1b/ultra_l1b_extended.py +81 -30
  210. imap_processing/ultra/l1c/histogram.py +6 -2
  211. imap_processing/ultra/l1c/pset.py +6 -2
  212. imap_processing/ultra/l1c/ultra_l1c.py +2 -3
  213. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +4 -3
  214. imap_processing/ultra/utils/ultra_l1_utils.py +70 -14
  215. imap_processing/utils.py +2 -2
  216. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/METADATA +7 -2
  217. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/RECORD +235 -152
  218. imap_processing/tests/codice/data/eu_unit_lookup_table.csv +0 -101
  219. imap_processing/tests/codice/data/idle_export_eu.COD_NHK_20230822_122700 2.csv +0 -100
  220. imap_processing/tests/codice/data/idle_export_raw.COD_NHK_20230822_122700.csv +0 -100
  221. imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
  222. imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
  223. imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
  224. imap_processing/tests/ultra/unit/test_spatial_utils.py +0 -125
  225. imap_processing/ultra/utils/spatial_utils.py +0 -221
  226. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_APP_NHK.bin +0 -0
  227. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_APP_NHK.csv +0 -0
  228. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_SCI_CNT.bin +0 -0
  229. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_SCI_DE.bin +0 -0
  230. /imap_processing/tests/hi/{test_data → data}/l0/H90_NHK_20241104.bin +0 -0
  231. /imap_processing/tests/hi/{test_data → data}/l0/H90_sci_cnt_20241104.bin +0 -0
  232. /imap_processing/tests/hi/{test_data → data}/l0/H90_sci_de_20241104.bin +0 -0
  233. /imap_processing/tests/hi/{test_data → data}/l0/README.txt +0 -0
  234. /imap_processing/tests/idex/{imap_idex_l0_raw_20231214_v001.pkts → test_data/imap_idex_l0_raw_20231214_v001.pkts} +0 -0
  235. /imap_processing/tests/idex/{impact_14_tof_high_data.txt → test_data/impact_14_tof_high_data.txt} +0 -0
  236. /imap_processing/tests/mag/{imap_mag_l1a_norm-magi_20251017_v001.cdf → validation/imap_mag_l1a_norm-magi_20251017_v001.cdf} +0 -0
  237. /imap_processing/tests/mag/{mag_l0_test_data.pkts → validation/mag_l0_test_data.pkts} +0 -0
  238. /imap_processing/tests/mag/{mag_l0_test_output.csv → validation/mag_l0_test_output.csv} +0 -0
  239. /imap_processing/tests/mag/{mag_l1_test_data.pkts → validation/mag_l1_test_data.pkts} +0 -0
  240. /imap_processing/tests/mag/{mag_l1a_test_output.csv → validation/mag_l1a_test_output.csv} +0 -0
  241. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/LICENSE +0 -0
  242. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/WHEEL +0 -0
  243. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/entry_points.txt +0 -0
@@ -16,19 +16,47 @@ Examples
16
16
 
17
17
  import logging
18
18
  from enum import Enum
19
+ from typing import Union
19
20
 
20
21
  import pandas as pd
21
22
  import xarray as xr
22
23
 
23
24
  from imap_processing import imap_module_directory
24
25
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
26
+ from imap_processing.spice.geometry import (
27
+ SpiceBody,
28
+ SpiceFrame,
29
+ cartesian_to_spherical,
30
+ imap_state,
31
+ instrument_pointing,
32
+ solar_longitude,
33
+ )
34
+ from imap_processing.spice.spin import get_spacecraft_spin_phase, get_spin_angle
35
+ from imap_processing.spice.time import ttj2000ns_to_et
25
36
  from imap_processing.utils import convert_raw_to_eu
26
37
 
27
38
  logger = logging.getLogger(__name__)
28
39
 
29
40
 
30
41
  class ConversionFactors(float, Enum):
31
- """Enum class for conversion factor values."""
42
+ """
43
+ Enum class for conversion factor values.
44
+
45
+ Attributes
46
+ ----------
47
+ TOF_High : float
48
+ Time of flight high conversion factor.
49
+ TOF_Low : float
50
+ Time of flight low conversion factor.
51
+ TOF_Mid : float
52
+ Time of flight mid conversion factor.
53
+ Target_Low : float
54
+ Target Low conversion factor.
55
+ Target_High : float
56
+ Target High conversion factor.
57
+ Ion_Grid : float
58
+ Ion Grid conversion factor.
59
+ """
32
60
 
33
61
  TOF_High = 2.89e-4
34
62
  TOF_Low = 5.14e-4
@@ -38,6 +66,44 @@ class ConversionFactors(float, Enum):
38
66
  Ion_Grid = 7.46e-4
39
67
 
40
68
 
69
+ class TriggerMode(Enum):
70
+ """
71
+ Enum class for data collection trigger Modes.
72
+
73
+ Attributes
74
+ ----------
75
+ Threshold : int
76
+ Mode 1 - Triggers when signal reaches the threshold value.
77
+ SinglePulse : int
78
+ Mode 2 - Triggers when a single pulse is detected.
79
+ DoublePulse : int
80
+ Mode 3 - Triggers when two pulses are detected.
81
+ """
82
+
83
+ Threshold = 1
84
+ SinglePulse = 2
85
+ DoublePulse = 3
86
+
87
+ @staticmethod
88
+ def get_mode_label(mode: int, channel: str) -> str:
89
+ """
90
+ Return trigger mode label.
91
+
92
+ Parameters
93
+ ----------
94
+ mode : int
95
+ Raw mode value.
96
+ channel : str
97
+ Channel gain level.
98
+
99
+ Returns
100
+ -------
101
+ str
102
+ Mode label.
103
+ """
104
+ return f"{channel.upper()}{TriggerMode(mode).name}"
105
+
106
+
41
107
  def idex_l1b(l1a_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
42
108
  """
43
109
  Will process IDEX l1a data to create l1b data products.
@@ -82,10 +148,23 @@ def idex_l1b(l1a_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
82
148
  dims=["epoch"],
83
149
  attrs=idex_attrs.get_variable_attributes("epoch"),
84
150
  )
151
+ # Get spice data and save them as xr.DataArrays in the output. Spice data is not
152
+ # used for calculations yet but are saved in the CDF for reference.
153
+ spice_data = get_spice_data(l1a_dataset, idex_attrs)
154
+
155
+ trigger_settings = get_trigger_mode_and_level(l1a_dataset)
156
+ if trigger_settings:
157
+ trigger_settings["triggerlevel"].attrs = idex_attrs.get_variable_attributes(
158
+ "trigger_level"
159
+ )
160
+ trigger_settings["triggermode"].attrs = idex_attrs.get_variable_attributes(
161
+ "trigger_mode"
162
+ )
163
+
85
164
  # Create l1b Dataset
86
165
  l1b_dataset = xr.Dataset(
87
166
  coords={"epoch": epoch_da},
88
- data_vars=processed_vars | waveforms_converted,
167
+ data_vars=processed_vars | waveforms_converted | trigger_settings | spice_data,
89
168
  attrs=idex_attrs.get_global_attributes("imap_idex_l1b_sci"),
90
169
  )
91
170
  # Convert variables
@@ -94,19 +173,16 @@ def idex_l1b(l1a_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
94
173
  conversion_table_path=var_information_path,
95
174
  packet_name="IDEX_SCI",
96
175
  )
176
+ prefixes = ["shcoarse", "shfine", "time_high_sample", "time_low_sample"]
97
177
  vars_to_copy = [
98
- "shcoarse",
99
- "shfine",
100
- "time_high_sr",
101
- "time_low_sr",
102
- "time_high_sr_label",
103
- "time_low_sr_label",
178
+ var
179
+ for var in l1a_dataset.variables
180
+ if any(prefix in var for prefix in prefixes)
104
181
  ]
105
182
  # Copy arrays from the l1a_dataset that do not need l1b processing
106
183
  for var in vars_to_copy:
107
184
  l1b_dataset[var] = l1a_dataset[var].copy()
108
185
 
109
- # TODO: Add TriggerMode and TriggerLevel attr
110
186
  # TODO: Spice data?
111
187
 
112
188
  logger.info("IDEX L1B science data processing completed.")
@@ -126,7 +202,7 @@ def unpack_instrument_settings(
126
202
  ----------
127
203
  l1a_dataset : xarray.Dataset
128
204
  IDEX L1a dataset containing the 6 waveform arrays.
129
- var_information_df : pd.DataFrame
205
+ var_information_df : pandas.DataFrame
130
206
  Pandas data frame that contains information about each variable
131
207
  (e.g., bit-size, starting bit, and padding). This is used to unpack raw
132
208
  telemetry data from the input dataset (`l1a_dataset`).
@@ -190,3 +266,161 @@ def convert_waveforms(
190
266
  )
191
267
 
192
268
  return waveforms_pc
269
+
270
+
271
+ def get_trigger_mode_and_level(
272
+ l1a_dataset: xr.Dataset,
273
+ ) -> Union[dict[str, xr.DataArray], dict]:
274
+ """
275
+ Determine the trigger mode and threshold level for each event.
276
+
277
+ Parameters
278
+ ----------
279
+ l1a_dataset : xarray.Dataset
280
+ IDEX L1a dataset containing the six waveform arrays and instrument settings.
281
+
282
+ Returns
283
+ -------
284
+ dict
285
+ A dictionary containing the trigger mode and level values.
286
+ """
287
+ # low, mid, and high gain channels
288
+ channels = ["lg", "mg", "hg"]
289
+ # 10 bit mask
290
+ mask = 0b1111111111
291
+ trigger_modes = []
292
+ trigger_levels = []
293
+
294
+ def compute_trigger_values(
295
+ trigger_mode: int, trigger_controls: int, gain_channel: str
296
+ ) -> Union[tuple[str, Union[int, float]], tuple[None, None]]:
297
+ """
298
+ Compute the trigger mode label and threshold level.
299
+
300
+ Parameters
301
+ ----------
302
+ trigger_mode : float
303
+ Raw trigger mode value.
304
+ trigger_controls : int
305
+ Raw trigger control values.
306
+ gain_channel : float
307
+ Gain channel (low, mid, or high).
308
+
309
+ Returns
310
+ -------
311
+ tuple
312
+ Mode label and threshold level.
313
+ """
314
+ # If the trigger mode is zero, then the channel did not trigger the event and
315
+ # therefore there is no threshold level
316
+ if trigger_mode == 0:
317
+ return None, None
318
+
319
+ mode_label = TriggerMode.get_mode_label(mode=trigger_mode, channel=gain_channel)
320
+ # The trigger control variable is 32 bits with the first 10 bits representing
321
+ # the Threshold level.
322
+ # Bit-shift right 22 places and use a 10-bit mask to extract the level value.
323
+ threshold_level = float((trigger_controls >> 22) & mask)
324
+
325
+ # If it is the high gain channel multiply the level by the conversion factor.
326
+ # TODO: determine why the idex team is only doing this for the high gain channel
327
+ if gain_channel == "hg":
328
+ threshold_level *= ConversionFactors["TOF_High"]
329
+ return mode_label, threshold_level
330
+
331
+ for channel in channels:
332
+ # Get all the modes and controls for each event for the current channel
333
+ modes = l1a_dataset[f"idx__txhdr{channel}trigmode"].copy()
334
+ controls = l1a_dataset[f"idx__txhdr{channel}trigctrl1"].copy()
335
+
336
+ # Apply the function across the arrays
337
+ mode_array, level_array = xr.apply_ufunc(
338
+ compute_trigger_values,
339
+ modes,
340
+ controls,
341
+ channel,
342
+ output_core_dims=([], []),
343
+ vectorize=True,
344
+ output_dtypes=[object, float],
345
+ )
346
+ trigger_modes.append(mode_array.rename("trigger_mode"))
347
+ trigger_levels.append(level_array.rename("trigger_level"))
348
+
349
+ try:
350
+ # There should be an array of modes and threshold levels for each channel.
351
+ # At each index (event) only one of the three arrays should have a value that is
352
+ # not 'None' because each event can only be triggered by one channel.
353
+ # By merging the three arrays, we get value for each event.
354
+ merged_modes = xr.merge([trigger_modes[0], xr.merge(trigger_modes[1:])])
355
+ merged_levels = xr.merge([trigger_levels[0], xr.merge(trigger_levels[1:])])
356
+
357
+ return {
358
+ "triggermode": merged_modes.trigger_mode,
359
+ "triggerlevel": merged_levels.trigger_level,
360
+ }
361
+
362
+ except xr.MergeError as e:
363
+ raise ValueError(
364
+ f"Only one channel can trigger a dust event. Please make sure "
365
+ f"there is only one valid trigger value per event. This "
366
+ f"caused Merge Error: {e}"
367
+ ) from e
368
+
369
+
370
+ def get_spice_data(
371
+ l1a_dataset: xr.Dataset, idex_attrs: ImapCdfAttributes
372
+ ) -> dict[str, xr.DataArray]:
373
+ """
374
+ Use spice to query ephemeris, attitude, celestial coordinates for each dust event.
375
+
376
+ Parameters
377
+ ----------
378
+ l1a_dataset : xarray.Dataset
379
+ IDEX L1a dataset containing the six waveform arrays and instrument settings.
380
+ idex_attrs : ImapCdfAttributes
381
+ CDF attribute manager object.
382
+
383
+ Returns
384
+ -------
385
+ dict
386
+ Spice array names and xr.DataArrays.
387
+ """
388
+ # convert 'epoch' from nanoseconds to seconds since j2000
389
+ et = ttj2000ns_to_et(l1a_dataset["epoch"].data)
390
+ # Get 'shcoarse' (Mission Elapsed Time)
391
+ met = l1a_dataset["shcoarse"].data
392
+ # Get spacecraft spin phase in degrees
393
+ spin_phase = get_spacecraft_spin_phase(query_met_times=met)
394
+ imap_spin_phase = get_spin_angle(spin_phase, degrees=True)
395
+ # Get position and velocity of IMAP in ecliptic frame
396
+ ephemeris = imap_state(et, observer=SpiceBody.SUN)
397
+ # Get Idex pointing in the j2000 equatorial frame
398
+ idex_pointing = instrument_pointing(
399
+ et, SpiceFrame.IMAP_IDEX, SpiceFrame.J2000, cartesian=True
400
+ )
401
+ solar_lon = solar_longitude(et, degrees=True)
402
+
403
+ range_ra_and_dec = cartesian_to_spherical(idex_pointing)
404
+
405
+ spice_data = {
406
+ "ephemeris_position_x": ephemeris[:, 0],
407
+ "ephemeris_position_y": ephemeris[:, 1],
408
+ "ephemeris_position_z": ephemeris[:, 2],
409
+ "ephemeris_velocity_x": ephemeris[:, 3],
410
+ "ephemeris_velocity_y": ephemeris[:, 4],
411
+ "ephemeris_velocity_z": ephemeris[:, 5],
412
+ "right_ascension": range_ra_and_dec[:, 1],
413
+ "declination": range_ra_and_dec[:, 2],
414
+ "spin_phase": imap_spin_phase,
415
+ "solar_longitude": solar_lon,
416
+ }
417
+
418
+ for name, array in spice_data.items():
419
+ spice_data[name] = xr.DataArray(
420
+ name=name,
421
+ data=array,
422
+ dims="epoch",
423
+ attrs=idex_attrs.get_variable_attributes(name),
424
+ )
425
+
426
+ return spice_data
@@ -20,6 +20,7 @@ from imap_processing.lo.l0.utils.bit_decompression import (
20
20
  Decompress,
21
21
  decompress_int,
22
22
  )
23
+ from imap_processing.spice.time import met_to_ttj2000ns
23
24
  from imap_processing.utils import convert_to_binary_string
24
25
 
25
26
  logger = logging.getLogger(__name__)
@@ -439,3 +440,63 @@ def find_valid_groups(
439
440
  ]
440
441
  valid_groups = [is_sequential(seq_ctrs) for seq_ctrs in grouped_seq_ctrs]
441
442
  return valid_groups
443
+
444
+
445
+ def organize_spin_data(dataset: xr.Dataset, attr_mgr: ImapCdfAttributes) -> xr.Dataset:
446
+ """
447
+ Organize the spin data for Lo.
448
+
449
+ The spin data is spread across 28 fields. This function
450
+ combines each of those fields into 2D arrays for each
451
+ epoch and spin.
452
+
453
+ Parameters
454
+ ----------
455
+ dataset : xr.Dataset
456
+ Lo spin data from packets_to_dataset function.
457
+ attr_mgr : ImapCdfAttributes
458
+ CDF attribute manager for Lo L1A.
459
+
460
+ Returns
461
+ -------
462
+ dataset : xr.Dataset
463
+ Updated dataset with the spin data organized.
464
+ """
465
+ # Get the spin data fields
466
+ spin_fields = [
467
+ "start_sec_spin",
468
+ "start_subsec_spin",
469
+ "esa_neg_dac_spin",
470
+ "esa_pos_dac_spin",
471
+ "valid_period_spin",
472
+ "valid_phase_spin",
473
+ "period_source_spin",
474
+ ]
475
+
476
+ # Set epoch to the acq_start time
477
+ # acq_start_sec is in units of seconds
478
+ # acq_start_subsec is in units of microseconds
479
+ acq_start = dataset.acq_start_sec + (1e-6 * dataset.acq_start_subsec)
480
+ epoch = met_to_ttj2000ns(acq_start)
481
+ dataset = dataset.assign_coords(epoch=("epoch", epoch))
482
+ for spin_field in spin_fields:
483
+ # Get the field attributes
484
+ field_attrs = attr_mgr.get_variable_attributes(spin_field, check_schema=False)
485
+ dtype = field_attrs.pop("dtype")
486
+
487
+ packet_fields = [f"{spin_field}_{i}" for i in range(1, 29)]
488
+ # Combine the spin data fields along a new dimension
489
+ combined_spin_data = xr.concat(
490
+ [dataset[field].astype(dtype) for field in packet_fields], dim="spin"
491
+ )
492
+
493
+ # Assign the combined data back to the dataset
494
+ dataset[spin_field] = xr.DataArray(
495
+ combined_spin_data.transpose(),
496
+ dims=["epoch", "spin"],
497
+ attrs=field_attrs,
498
+ )
499
+ # Drop the individual spin data fields
500
+ dataset = dataset.drop_vars(packet_fields)
501
+
502
+ return dataset
@@ -11,6 +11,7 @@ from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
11
11
  from imap_processing.lo.l0.lo_apid import LoAPID
12
12
  from imap_processing.lo.l0.lo_science import (
13
13
  combine_segmented_packets,
14
+ organize_spin_data,
14
15
  parse_events,
15
16
  parse_histogram,
16
17
  )
@@ -52,6 +53,19 @@ def lo_l1a(dependency: Path, data_version: str) -> list[xr.Dataset]:
52
53
  attr_mgr.add_instrument_variable_attrs(instrument="lo", level="l1a")
53
54
  attr_mgr.add_global_attribute("Data_version", data_version)
54
55
 
56
+ if LoAPID.ILO_SPIN in datasets_by_apid:
57
+ logger.info(
58
+ f"\nProcessing {LoAPID(LoAPID.ILO_SPIN).name} "
59
+ f"packet (APID: {LoAPID.ILO_SPIN.value})"
60
+ )
61
+ logical_source = "imap_lo_l1a_spin"
62
+ datasets_by_apid[LoAPID.ILO_SPIN] = organize_spin_data(
63
+ datasets_by_apid[LoAPID.ILO_SPIN], attr_mgr
64
+ )
65
+
66
+ datasets_by_apid[LoAPID.ILO_SPIN] = add_dataset_attrs(
67
+ datasets_by_apid[LoAPID.ILO_SPIN], attr_mgr, logical_source
68
+ )
55
69
  if LoAPID.ILO_SCI_CNT in datasets_by_apid:
56
70
  logger.info(
57
71
  f"\nProcessing {LoAPID(LoAPID.ILO_SCI_CNT).name} "
@@ -90,7 +104,7 @@ def lo_l1a(dependency: Path, data_version: str) -> list[xr.Dataset]:
90
104
  datasets_by_apid[LoAPID.ILO_SCI_DE], attr_mgr, logical_source
91
105
  )
92
106
 
93
- good_apids = [LoAPID.ILO_SCI_CNT, LoAPID.ILO_SCI_DE]
107
+ good_apids = [LoAPID.ILO_SPIN, LoAPID.ILO_SCI_CNT, LoAPID.ILO_SCI_DE]
94
108
  logger.info(f"\nReturning datasets: {[LoAPID(apid) for apid in good_apids]}")
95
109
  return [datasets_by_apid[good_apid] for good_apid in good_apids]
96
110
 
@@ -116,8 +130,63 @@ def add_dataset_attrs(
116
130
  Data with attributes added.
117
131
  """
118
132
  # TODO: may want up split up these if statements into their
119
- # own functions
120
- if logical_source == "imap_lo_l1a_histogram":
133
+ # own functions
134
+ # Get global attributes
135
+ dataset.attrs.update(attr_mgr.get_global_attributes(logical_source))
136
+ # Get attributes for shcoarse and epoch
137
+ dataset.shcoarse.attrs.update(attr_mgr.get_variable_attributes("shcoarse"))
138
+ dataset.epoch.attrs.update(attr_mgr.get_variable_attributes("epoch"))
139
+
140
+ if logical_source == "imap_lo_l1a_spin":
141
+ spin = xr.DataArray(
142
+ data=np.arange(0, 28, dtype=np.uint8),
143
+ name="spin",
144
+ dims=["spin"],
145
+ attrs=attr_mgr.get_variable_attributes("spin"),
146
+ )
147
+ spin_label = xr.DataArray(
148
+ data=spin.values.astype(str),
149
+ name="spin_label",
150
+ dims=["spin_label"],
151
+ attrs=attr_mgr.get_variable_attributes("spin_label"),
152
+ )
153
+
154
+ dataset = dataset.assign_coords(spin=spin, spin_label=spin_label)
155
+ dataset.num_completed.attrs.update(
156
+ attr_mgr.get_variable_attributes("num_completed")
157
+ )
158
+ dataset.acq_start_sec.attrs.update(
159
+ attr_mgr.get_variable_attributes("acq_start_sec")
160
+ )
161
+ dataset.acq_start_subsec.attrs.update(
162
+ attr_mgr.get_variable_attributes("acq_start_subsec")
163
+ )
164
+ dataset.acq_end_sec.attrs.update(
165
+ attr_mgr.get_variable_attributes("acq_end_sec")
166
+ )
167
+ dataset.acq_end_subsec.attrs.update(
168
+ attr_mgr.get_variable_attributes("acq_end_subsec")
169
+ )
170
+
171
+ dataset = dataset.drop_vars(
172
+ [
173
+ "version",
174
+ "type",
175
+ "sec_hdr_flg",
176
+ "pkt_apid",
177
+ "seq_flgs",
178
+ "src_seq_ctr",
179
+ "pkt_len",
180
+ "chksum",
181
+ ]
182
+ )
183
+ # An empty DEPEND_0 is being added to support_data
184
+ # variables that should only have DEPEND_1
185
+ # Removing Depend_0 here.
186
+ # TODO: Should look for a fix to this issue
187
+ del dataset["spin"].attrs["DEPEND_0"]
188
+
189
+ elif logical_source == "imap_lo_l1a_histogram":
121
190
  # Create coordinates for the dataset
122
191
  azimuth_60 = xr.DataArray(
123
192
  data=np.arange(0, 6, dtype=np.uint8),
@@ -157,10 +226,6 @@ def add_dataset_attrs(
157
226
  attrs=attr_mgr.get_variable_attributes("esa_step_label"),
158
227
  )
159
228
 
160
- # Get attributes for shcoarse and epoch
161
- dataset.shcoarse.attrs.update(attr_mgr.get_variable_attributes("shcoarse"))
162
- dataset.epoch.attrs.update(attr_mgr.get_variable_attributes("epoch"))
163
-
164
229
  dataset = dataset.assign_coords(
165
230
  azimuth_60=azimuth_60,
166
231
  azimuth_60_label=azimuth_60_label,
@@ -169,7 +234,6 @@ def add_dataset_attrs(
169
234
  esa_step=esa_step,
170
235
  esa_step_label=esa_step_label,
171
236
  )
172
- dataset.attrs.update(attr_mgr.get_global_attributes(logical_source))
173
237
  # remove the binary field and CCSDS header from the dataset
174
238
  dataset = dataset.drop_vars(
175
239
  [
@@ -184,6 +248,14 @@ def add_dataset_attrs(
184
248
  "pkt_len",
185
249
  ]
186
250
  )
251
+ # An empty DEPEND_0 is being added to support_data
252
+ # variables that should only have DEPEND_1
253
+ # Removing Depend_0 here.
254
+ # TODO: Should look for a fix to this issue
255
+ del dataset["azimuth_60"].attrs["DEPEND_0"]
256
+ del dataset["azimuth_6"].attrs["DEPEND_0"]
257
+ del dataset["esa_step"].attrs["DEPEND_0"]
258
+
187
259
  elif logical_source == "imap_lo_l1a_de":
188
260
  # Create the coordinates for the dataset
189
261
  direct_events = xr.DataArray(
@@ -205,8 +277,6 @@ def add_dataset_attrs(
205
277
  direct_events_label=direct_events_label,
206
278
  )
207
279
  # add the epoch and global attributes
208
- dataset.epoch.attrs.update(attr_mgr.get_variable_attributes("epoch"))
209
- dataset.attrs.update(attr_mgr.get_global_attributes(logical_source))
210
280
  dataset = dataset.drop_vars(
211
281
  [
212
282
  "version",
@@ -221,5 +291,23 @@ def add_dataset_attrs(
221
291
  "events",
222
292
  ]
223
293
  )
294
+ # An empty DEPEND_0 is being added to support_data
295
+ # variables that should only have DEPEND_1
296
+ # Removing Depend_0 here.
297
+ # TODO: Should look for a fix to this issue
298
+ for var in [
299
+ "direct_events",
300
+ "coincidence_type",
301
+ "de_time",
302
+ "mode",
303
+ "esa_step",
304
+ "tof0",
305
+ "tof1",
306
+ "tof2",
307
+ "tof3",
308
+ "pos",
309
+ "cksm",
310
+ ]:
311
+ dataset[var].attrs.pop("DEPEND_0")
224
312
 
225
313
  return dataset
@@ -8,7 +8,7 @@ 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_j2000ns
11
+ from imap_processing.spice.time import met_to_ttj2000ns
12
12
 
13
13
 
14
14
  def lo_l1b(dependencies: dict, data_version: str) -> list[Path]:
@@ -87,7 +87,7 @@ def create_datasets(
87
87
  # and relative L1A DE time to calculate the absolute DE time,
88
88
  # this epoch conversion will go away and the time in the DE dataclass
89
89
  # can be used direction
90
- epoch_converted_time = met_to_j2000ns([0, 1, 2])
90
+ epoch_converted_time = met_to_ttj2000ns([0, 1, 2])
91
91
 
92
92
  # Create a data array for the epoch time
93
93
  # TODO: might need to update the attrs to use new YAML file
@@ -8,7 +8,7 @@ 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_j2000ns
11
+ from imap_processing.spice.time import met_to_ttj2000ns
12
12
 
13
13
 
14
14
  def lo_l1c(dependencies: dict, data_version: str) -> list[Path]:
@@ -86,7 +86,7 @@ def create_datasets(
86
86
  # and relative L1A DE time to calculate the absolute DE time,
87
87
  # this epoch conversion will go away and the time in the DE dataclass
88
88
  # can be used direction
89
- epoch_converted_time = [met_to_j2000ns(1)]
89
+ epoch_converted_time = [met_to_ttj2000ns(1)]
90
90
 
91
91
  # Create a data array for the epoch time
92
92
  # TODO: might need to update the attrs to use new YAML file