imap-processing 0.11.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 (415) hide show
  1. imap_processing/__init__.py +11 -11
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/ccsds_data.py +1 -2
  4. imap_processing/ccsds/excel_to_xtce.py +66 -18
  5. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +24 -40
  6. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +934 -42
  7. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
  8. imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +0 -5
  9. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +10 -11
  10. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +17 -19
  11. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +27 -14
  12. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +106 -116
  13. imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +120 -145
  14. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +14 -0
  15. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +25 -9
  16. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +6 -4
  17. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +3 -3
  18. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +0 -12
  19. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +1 -1
  20. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +23 -20
  21. imap_processing/cdf/config/imap_mag_l1a_variable_attrs.yaml +361 -0
  22. imap_processing/cdf/config/imap_mag_l1b_variable_attrs.yaml +160 -0
  23. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +160 -0
  24. imap_processing/cdf/config/imap_spacecraft_global_cdf_attrs.yaml +18 -0
  25. imap_processing/cdf/config/imap_spacecraft_variable_attrs.yaml +40 -0
  26. imap_processing/cdf/config/imap_swapi_global_cdf_attrs.yaml +1 -5
  27. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +22 -0
  28. imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +12 -4
  29. imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +16 -2
  30. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +64 -52
  31. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +71 -47
  32. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +180 -19
  33. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
  34. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +80 -17
  35. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +32 -57
  36. imap_processing/cdf/utils.py +52 -38
  37. imap_processing/cli.py +477 -233
  38. imap_processing/codice/codice_l1a.py +466 -131
  39. imap_processing/codice/codice_l1b.py +51 -152
  40. imap_processing/codice/constants.py +1360 -569
  41. imap_processing/codice/decompress.py +2 -6
  42. imap_processing/ena_maps/ena_maps.py +1103 -146
  43. imap_processing/ena_maps/utils/coordinates.py +19 -0
  44. imap_processing/ena_maps/utils/map_utils.py +14 -17
  45. imap_processing/ena_maps/utils/spatial_utils.py +55 -52
  46. imap_processing/glows/l1a/glows_l1a.py +28 -99
  47. imap_processing/glows/l1a/glows_l1a_data.py +2 -2
  48. imap_processing/glows/l1b/glows_l1b.py +1 -4
  49. imap_processing/glows/l1b/glows_l1b_data.py +1 -3
  50. imap_processing/glows/l2/glows_l2.py +2 -5
  51. imap_processing/hi/l1a/hi_l1a.py +54 -29
  52. imap_processing/hi/l1a/histogram.py +0 -1
  53. imap_processing/hi/l1a/science_direct_event.py +6 -8
  54. imap_processing/hi/l1b/hi_l1b.py +111 -82
  55. imap_processing/hi/l1c/hi_l1c.py +416 -32
  56. imap_processing/hi/utils.py +58 -12
  57. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
  58. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt0-factors_20250219_v002.csv +205 -0
  59. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt1-factors_20250219_v002.csv +205 -0
  60. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt2-factors_20250219_v002.csv +205 -0
  61. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt3-factors_20250219_v002.csv +205 -0
  62. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-summed-dt0-factors_20250219_v002.csv +68 -0
  63. imap_processing/hit/hit_utils.py +235 -5
  64. imap_processing/hit/l0/constants.py +20 -11
  65. imap_processing/hit/l0/decom_hit.py +21 -5
  66. imap_processing/hit/l1a/hit_l1a.py +71 -75
  67. imap_processing/hit/l1b/constants.py +321 -0
  68. imap_processing/hit/l1b/hit_l1b.py +377 -67
  69. imap_processing/hit/l2/constants.py +318 -0
  70. imap_processing/hit/l2/hit_l2.py +723 -0
  71. imap_processing/hit/packet_definitions/hit_packet_definitions.xml +1323 -71
  72. imap_processing/ialirt/l0/mag_l0_ialirt_data.py +155 -0
  73. imap_processing/ialirt/l0/parse_mag.py +374 -0
  74. imap_processing/ialirt/l0/process_swapi.py +69 -0
  75. imap_processing/ialirt/l0/process_swe.py +548 -0
  76. imap_processing/ialirt/packet_definitions/ialirt.xml +216 -208
  77. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +1 -1
  78. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +1 -1
  79. imap_processing/ialirt/packet_definitions/ialirt_mag.xml +115 -0
  80. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +14 -14
  81. imap_processing/ialirt/utils/grouping.py +114 -0
  82. imap_processing/ialirt/utils/time.py +29 -0
  83. imap_processing/idex/atomic_masses.csv +22 -0
  84. imap_processing/idex/decode.py +2 -2
  85. imap_processing/idex/idex_constants.py +33 -0
  86. imap_processing/idex/idex_l0.py +22 -8
  87. imap_processing/idex/idex_l1a.py +81 -51
  88. imap_processing/idex/idex_l1b.py +13 -39
  89. imap_processing/idex/idex_l2a.py +823 -0
  90. imap_processing/idex/idex_l2b.py +120 -0
  91. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +11 -11
  92. imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
  93. imap_processing/lo/l0/lo_science.py +7 -2
  94. imap_processing/lo/l1a/lo_l1a.py +1 -5
  95. imap_processing/lo/l1b/lo_l1b.py +702 -29
  96. imap_processing/lo/l1b/tof_conversions.py +11 -0
  97. imap_processing/lo/l1c/lo_l1c.py +1 -4
  98. imap_processing/mag/constants.py +51 -0
  99. imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
  100. imap_processing/mag/l0/decom_mag.py +10 -3
  101. imap_processing/mag/l1a/mag_l1a.py +23 -19
  102. imap_processing/mag/l1a/mag_l1a_data.py +35 -10
  103. imap_processing/mag/l1b/mag_l1b.py +259 -50
  104. imap_processing/mag/l1c/interpolation_methods.py +388 -0
  105. imap_processing/mag/l1c/mag_l1c.py +621 -17
  106. imap_processing/mag/l2/mag_l2.py +140 -0
  107. imap_processing/mag/l2/mag_l2_data.py +288 -0
  108. imap_processing/quality_flags.py +1 -0
  109. imap_processing/spacecraft/packet_definitions/scid_x252.xml +538 -0
  110. imap_processing/spacecraft/quaternions.py +121 -0
  111. imap_processing/spice/geometry.py +19 -22
  112. imap_processing/spice/kernels.py +0 -276
  113. imap_processing/spice/pointing_frame.py +257 -0
  114. imap_processing/spice/repoint.py +149 -0
  115. imap_processing/spice/spin.py +38 -33
  116. imap_processing/spice/time.py +24 -0
  117. imap_processing/swapi/l1/swapi_l1.py +20 -12
  118. imap_processing/swapi/l2/swapi_l2.py +116 -5
  119. imap_processing/swapi/swapi_utils.py +32 -0
  120. imap_processing/swe/l1a/swe_l1a.py +44 -12
  121. imap_processing/swe/l1a/swe_science.py +13 -13
  122. imap_processing/swe/l1b/swe_l1b.py +898 -23
  123. imap_processing/swe/l2/swe_l2.py +75 -136
  124. imap_processing/swe/packet_definitions/swe_packet_definition.xml +1121 -1
  125. imap_processing/swe/utils/swe_constants.py +64 -0
  126. imap_processing/swe/utils/swe_utils.py +85 -28
  127. imap_processing/tests/ccsds/test_data/expected_output.xml +40 -1
  128. imap_processing/tests/ccsds/test_excel_to_xtce.py +24 -21
  129. imap_processing/tests/cdf/test_data/imap_instrument2_global_cdf_attrs.yaml +0 -2
  130. imap_processing/tests/cdf/test_utils.py +14 -16
  131. imap_processing/tests/codice/conftest.py +44 -33
  132. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  133. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  134. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-ialirt_20241110193700_v0.0.0.cdf +0 -0
  135. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-omni_20241110193700_v0.0.0.cdf +0 -0
  136. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
  137. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-priorities_20241110193700_v0.0.0.cdf +0 -0
  138. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-sectored_20241110193700_v0.0.0.cdf +0 -0
  139. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  140. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  141. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  142. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-angular_20241110193700_v0.0.0.cdf +0 -0
  143. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-priority_20241110193700_v0.0.0.cdf +0 -0
  144. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-species_20241110193700_v0.0.0.cdf +0 -0
  145. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
  146. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-angular_20241110193700_v0.0.0.cdf +0 -0
  147. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-priority_20241110193700_v0.0.0.cdf +0 -0
  148. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-species_20241110193700_v0.0.0.cdf +0 -0
  149. imap_processing/tests/codice/test_codice_l1a.py +126 -53
  150. imap_processing/tests/codice/test_codice_l1b.py +6 -7
  151. imap_processing/tests/codice/test_decompress.py +4 -4
  152. imap_processing/tests/conftest.py +239 -27
  153. imap_processing/tests/ena_maps/conftest.py +51 -0
  154. imap_processing/tests/ena_maps/test_ena_maps.py +1068 -110
  155. imap_processing/tests/ena_maps/test_map_utils.py +66 -43
  156. imap_processing/tests/ena_maps/test_spatial_utils.py +17 -21
  157. imap_processing/tests/glows/conftest.py +10 -14
  158. imap_processing/tests/glows/test_glows_decom.py +4 -4
  159. imap_processing/tests/glows/test_glows_l1a_cdf.py +6 -27
  160. imap_processing/tests/glows/test_glows_l1a_data.py +6 -8
  161. imap_processing/tests/glows/test_glows_l1b.py +11 -11
  162. imap_processing/tests/glows/test_glows_l1b_data.py +5 -5
  163. imap_processing/tests/glows/test_glows_l2.py +2 -8
  164. imap_processing/tests/hi/conftest.py +1 -1
  165. imap_processing/tests/hi/data/l0/H45_diag_fee_20250208.bin +0 -0
  166. imap_processing/tests/hi/data/l0/H45_diag_fee_20250208_verify.csv +205 -0
  167. imap_processing/tests/hi/test_hi_l1b.py +22 -27
  168. imap_processing/tests/hi/test_hi_l1c.py +249 -18
  169. imap_processing/tests/hi/test_l1a.py +35 -7
  170. imap_processing/tests/hi/test_science_direct_event.py +3 -3
  171. imap_processing/tests/hi/test_utils.py +24 -2
  172. imap_processing/tests/hit/helpers/l1_validation.py +74 -73
  173. imap_processing/tests/hit/test_data/hskp_sample.ccsds +0 -0
  174. imap_processing/tests/hit/test_data/imap_hit_l0_raw_20100105_v001.pkts +0 -0
  175. imap_processing/tests/hit/test_decom_hit.py +5 -1
  176. imap_processing/tests/hit/test_hit_l1a.py +32 -36
  177. imap_processing/tests/hit/test_hit_l1b.py +300 -81
  178. imap_processing/tests/hit/test_hit_l2.py +716 -0
  179. imap_processing/tests/hit/test_hit_utils.py +184 -7
  180. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
  181. imap_processing/tests/hit/validation_data/hskp_sample_eu_3_6_2025.csv +89 -0
  182. imap_processing/tests/hit/validation_data/hskp_sample_raw.csv +89 -88
  183. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +1 -1
  184. imap_processing/tests/ialirt/data/l0/461971383-404.bin +0 -0
  185. imap_processing/tests/ialirt/data/l0/461971384-405.bin +0 -0
  186. imap_processing/tests/ialirt/data/l0/461971385-406.bin +0 -0
  187. imap_processing/tests/ialirt/data/l0/461971386-407.bin +0 -0
  188. imap_processing/tests/ialirt/data/l0/461971387-408.bin +0 -0
  189. imap_processing/tests/ialirt/data/l0/461971388-409.bin +0 -0
  190. imap_processing/tests/ialirt/data/l0/461971389-410.bin +0 -0
  191. imap_processing/tests/ialirt/data/l0/461971390-411.bin +0 -0
  192. imap_processing/tests/ialirt/data/l0/461971391-412.bin +0 -0
  193. imap_processing/tests/ialirt/data/l0/sample_decoded_i-alirt_data.csv +383 -0
  194. imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
  195. imap_processing/tests/ialirt/unit/test_grouping.py +81 -0
  196. imap_processing/tests/ialirt/unit/test_parse_mag.py +223 -0
  197. imap_processing/tests/ialirt/unit/test_process_codicehi.py +3 -3
  198. imap_processing/tests/ialirt/unit/test_process_codicelo.py +3 -10
  199. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +4 -4
  200. imap_processing/tests/ialirt/unit/test_process_hit.py +3 -3
  201. imap_processing/tests/ialirt/unit/test_process_swapi.py +24 -16
  202. imap_processing/tests/ialirt/unit/test_process_swe.py +319 -6
  203. imap_processing/tests/ialirt/unit/test_time.py +16 -0
  204. imap_processing/tests/idex/conftest.py +127 -6
  205. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231218_v001.pkts +0 -0
  206. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20241206_v001.pkts +0 -0
  207. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20250108_v001.pkts +0 -0
  208. imap_processing/tests/idex/test_data/impact_14_tof_high_data.txt +4508 -4508
  209. imap_processing/tests/idex/test_idex_l0.py +33 -11
  210. imap_processing/tests/idex/test_idex_l1a.py +92 -21
  211. imap_processing/tests/idex/test_idex_l1b.py +106 -27
  212. imap_processing/tests/idex/test_idex_l2a.py +399 -0
  213. imap_processing/tests/idex/test_idex_l2b.py +93 -0
  214. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20241022_v002.cdf +0 -0
  215. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20241022_v002.cdf +0 -0
  216. imap_processing/tests/lo/test_lo_l1a.py +3 -3
  217. imap_processing/tests/lo/test_lo_l1b.py +515 -6
  218. imap_processing/tests/lo/test_lo_l1c.py +1 -1
  219. imap_processing/tests/lo/test_lo_science.py +7 -7
  220. imap_processing/tests/lo/test_star_sensor.py +1 -1
  221. imap_processing/tests/mag/conftest.py +120 -2
  222. imap_processing/tests/mag/test_mag_decom.py +5 -4
  223. imap_processing/tests/mag/test_mag_l1a.py +51 -7
  224. imap_processing/tests/mag/test_mag_l1b.py +40 -59
  225. imap_processing/tests/mag/test_mag_l1c.py +354 -19
  226. imap_processing/tests/mag/test_mag_l2.py +130 -0
  227. imap_processing/tests/mag/test_mag_validation.py +247 -26
  228. imap_processing/tests/mag/validation/L1b/T009/MAGScience-normal-(2,2)-8s-20250204-16h39.csv +17 -0
  229. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +16 -16
  230. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +16 -16
  231. imap_processing/tests/mag/validation/L1b/T010/MAGScience-normal-(2,2)-8s-20250206-12h05.csv +17 -0
  232. imap_processing/tests/mag/validation/L1b/T011/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
  233. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +16 -16
  234. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +16 -16
  235. imap_processing/tests/mag/validation/L1b/T012/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
  236. imap_processing/tests/mag/validation/L1b/T012/data.bin +0 -0
  237. imap_processing/tests/mag/validation/L1b/T012/field_like_all_ranges.txt +19200 -0
  238. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-cal.cdf +0 -0
  239. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-in.csv +17 -0
  240. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-magi-out.csv +17 -0
  241. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-mago-out.csv +17 -0
  242. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-in.csv +1217 -0
  243. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-out.csv +1857 -0
  244. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-in.csv +1217 -0
  245. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-out.csv +1857 -0
  246. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-in.csv +1217 -0
  247. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-out.csv +1793 -0
  248. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-in.csv +1217 -0
  249. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-out.csv +1793 -0
  250. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-burst-in.csv +2561 -0
  251. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-in.csv +961 -0
  252. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-out.csv +1539 -0
  253. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-in.csv +1921 -0
  254. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-out.csv +2499 -0
  255. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-in.csv +865 -0
  256. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-out.csv +1196 -0
  257. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-in.csv +1729 -0
  258. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-out.csv +3053 -0
  259. imap_processing/tests/mag/validation/L2/imap_mag_l1b_norm-mago_20251017_v002.cdf +0 -0
  260. imap_processing/tests/mag/validation/calibration/imap_mag_l1b-calibration_20240229_v001.cdf +0 -0
  261. imap_processing/tests/mag/validation/calibration/imap_mag_l2-calibration-matrices_20251017_v004.cdf +0 -0
  262. imap_processing/tests/mag/validation/calibration/imap_mag_l2-offsets-norm_20251017_20251017_v001.cdf +0 -0
  263. imap_processing/tests/spacecraft/data/SSR_2024_190_20_08_12_0483851794_2_DA_apid0594_1packet.pkts +0 -0
  264. imap_processing/tests/spacecraft/test_quaternions.py +71 -0
  265. imap_processing/tests/spice/test_data/fake_repoint_data.csv +5 -0
  266. imap_processing/tests/spice/test_data/fake_spin_data.csv +11 -11
  267. imap_processing/tests/spice/test_geometry.py +9 -12
  268. imap_processing/tests/spice/test_kernels.py +1 -200
  269. imap_processing/tests/spice/test_pointing_frame.py +185 -0
  270. imap_processing/tests/spice/test_repoint.py +121 -0
  271. imap_processing/tests/spice/test_spin.py +50 -9
  272. imap_processing/tests/spice/test_time.py +14 -0
  273. imap_processing/tests/swapi/lut/imap_swapi_esa-unit-conversion_20250211_v000.csv +73 -0
  274. imap_processing/tests/swapi/lut/imap_swapi_lut-notes_20250211_v000.csv +1025 -0
  275. imap_processing/tests/swapi/test_swapi_l1.py +13 -11
  276. imap_processing/tests/swapi/test_swapi_l2.py +180 -8
  277. imap_processing/tests/swe/l0_data/2024051010_SWE_HK_packet.bin +0 -0
  278. imap_processing/tests/swe/l0_data/2024051011_SWE_CEM_RAW_packet.bin +0 -0
  279. imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_APP_HK_20240510_092742.csv +49 -0
  280. imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_CEM_RAW_20240510_092742.csv +593 -0
  281. imap_processing/tests/swe/lut/checker-board-indices.csv +24 -0
  282. imap_processing/tests/swe/lut/imap_swe_esa-lut_20250301_v000.csv +385 -0
  283. imap_processing/tests/swe/lut/imap_swe_l1b-in-flight-cal_20240510_20260716_v000.csv +3 -0
  284. imap_processing/tests/swe/test_swe_l1a.py +20 -2
  285. imap_processing/tests/swe/test_swe_l1a_cem_raw.py +52 -0
  286. imap_processing/tests/swe/test_swe_l1a_hk.py +68 -0
  287. imap_processing/tests/swe/test_swe_l1a_science.py +3 -3
  288. imap_processing/tests/swe/test_swe_l1b.py +162 -24
  289. imap_processing/tests/swe/test_swe_l2.py +153 -91
  290. imap_processing/tests/test_cli.py +171 -88
  291. imap_processing/tests/test_utils.py +140 -17
  292. imap_processing/tests/ultra/data/l0/FM45_UltraFM45_Functional_2024-01-22T0105_20240122T010548.CCSDS +0 -0
  293. imap_processing/tests/ultra/data/l0/ultra45_raw_sc_ultraimgrates_20220530_00.csv +164 -0
  294. imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3243 -3243
  295. imap_processing/tests/ultra/data/mock_data.py +369 -0
  296. imap_processing/tests/ultra/unit/conftest.py +115 -89
  297. imap_processing/tests/ultra/unit/test_badtimes.py +4 -4
  298. imap_processing/tests/ultra/unit/test_cullingmask.py +8 -6
  299. imap_processing/tests/ultra/unit/test_de.py +14 -13
  300. imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
  301. imap_processing/tests/ultra/unit/test_decom_apid_881.py +54 -11
  302. imap_processing/tests/ultra/unit/test_decom_apid_883.py +12 -10
  303. imap_processing/tests/ultra/unit/test_decom_apid_896.py +202 -55
  304. imap_processing/tests/ultra/unit/test_lookup_utils.py +23 -1
  305. imap_processing/tests/ultra/unit/test_spacecraft_pset.py +77 -0
  306. imap_processing/tests/ultra/unit/test_ultra_l1a.py +98 -305
  307. imap_processing/tests/ultra/unit/test_ultra_l1b.py +60 -14
  308. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
  309. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +26 -27
  310. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +239 -70
  311. imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
  312. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +114 -83
  313. imap_processing/tests/ultra/unit/test_ultra_l2.py +230 -0
  314. imap_processing/ultra/constants.py +1 -1
  315. imap_processing/ultra/l0/decom_tools.py +27 -39
  316. imap_processing/ultra/l0/decom_ultra.py +168 -204
  317. imap_processing/ultra/l0/ultra_utils.py +152 -136
  318. imap_processing/ultra/l1a/ultra_l1a.py +55 -271
  319. imap_processing/ultra/l1b/badtimes.py +1 -4
  320. imap_processing/ultra/l1b/cullingmask.py +2 -6
  321. imap_processing/ultra/l1b/de.py +116 -57
  322. imap_processing/ultra/l1b/extendedspin.py +20 -18
  323. imap_processing/ultra/l1b/lookup_utils.py +72 -9
  324. imap_processing/ultra/l1b/ultra_l1b.py +36 -16
  325. imap_processing/ultra/l1b/ultra_l1b_culling.py +66 -30
  326. imap_processing/ultra/l1b/ultra_l1b_extended.py +297 -94
  327. imap_processing/ultra/l1c/histogram.py +2 -6
  328. imap_processing/ultra/l1c/spacecraft_pset.py +84 -0
  329. imap_processing/ultra/l1c/ultra_l1c.py +8 -9
  330. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +206 -108
  331. imap_processing/ultra/l2/ultra_l2.py +299 -0
  332. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +526 -0
  333. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +526 -0
  334. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +526 -0
  335. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +526 -0
  336. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -2
  337. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -0
  338. imap_processing/ultra/packet_definitions/README.md +38 -0
  339. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +15302 -482
  340. imap_processing/ultra/utils/ultra_l1_utils.py +31 -12
  341. imap_processing/utils.py +69 -29
  342. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +10 -6
  343. imap_processing-0.13.0.dist-info/RECORD +578 -0
  344. imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +0 -237
  345. imap_processing/hi/l1a/housekeeping.py +0 -27
  346. imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
  347. imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
  348. imap_processing/swe/l1b/swe_l1b_science.py +0 -652
  349. imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-aggregated_20240429_v001.cdf +0 -0
  350. imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-singles_20240429_v001.cdf +0 -0
  351. imap_processing/tests/codice/data/imap_codice_l1a_hi-omni_20240429_v001.cdf +0 -0
  352. imap_processing/tests/codice/data/imap_codice_l1a_hi-sectored_20240429_v001.cdf +0 -0
  353. imap_processing/tests/codice/data/imap_codice_l1a_hskp_20100101_v001.cdf +0 -0
  354. imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-aggregated_20240429_v001.cdf +0 -0
  355. imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-singles_20240429_v001.cdf +0 -0
  356. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-angular_20240429_v001.cdf +0 -0
  357. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-priority_20240429_v001.cdf +0 -0
  358. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-species_20240429_v001.cdf +0 -0
  359. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-angular_20240429_v001.cdf +0 -0
  360. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-priority_20240429_v001.cdf +0 -0
  361. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-species_20240429_v001.cdf +0 -0
  362. imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-aggregated_20240429_v001.cdf +0 -0
  363. imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-singles_20240429_v001.cdf +0 -0
  364. imap_processing/tests/codice/data/imap_codice_l1b_hi-omni_20240429_v001.cdf +0 -0
  365. imap_processing/tests/codice/data/imap_codice_l1b_hi-sectored_20240429_v001.cdf +0 -0
  366. imap_processing/tests/codice/data/imap_codice_l1b_hskp_20100101_v001.cdf +0 -0
  367. imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-aggregated_20240429_v001.cdf +0 -0
  368. imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-singles_20240429_v001.cdf +0 -0
  369. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-angular_20240429_v001.cdf +0 -0
  370. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-priority_20240429_v001.cdf +0 -0
  371. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-species_20240429_v001.cdf +0 -0
  372. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-angular_20240429_v001.cdf +0 -0
  373. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-priority_20240429_v001.cdf +0 -0
  374. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-species_20240429_v001.cdf +0 -0
  375. imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
  376. imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1251.pkts +0 -0
  377. imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1252.pkts +0 -0
  378. imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +0 -89
  379. imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +0 -29
  380. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231214_v001.pkts +0 -0
  381. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20100101_v001.cdf +0 -0
  382. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20100101_v001.cdf +0 -0
  383. imap_processing/tests/swe/test_swe_l1b_science.py +0 -84
  384. imap_processing/tests/ultra/test_data/mock_data.py +0 -161
  385. imap_processing/ultra/l1c/pset.py +0 -40
  386. imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
  387. imap_processing-0.11.0.dist-info/RECORD +0 -488
  388. /imap_processing/idex/packet_definitions/{idex_packet_definition.xml → idex_science_packet_definition.xml} +0 -0
  389. /imap_processing/tests/ialirt/{test_data → data}/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  390. /imap_processing/tests/ialirt/{test_data → data}/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  391. /imap_processing/tests/ialirt/{test_data → data}/l0/IALiRT Raw Packet Telemetry.txt +0 -0
  392. /imap_processing/tests/ialirt/{test_data → data}/l0/apid01152.tlm +0 -0
  393. /imap_processing/tests/ialirt/{test_data → data}/l0/eu_SWP_IAL_20240826_152033.csv +0 -0
  394. /imap_processing/tests/ialirt/{test_data → data}/l0/hi_fsw_view_1_ccsds.bin +0 -0
  395. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.ccsds +0 -0
  396. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.csv +0 -0
  397. /imap_processing/tests/ialirt/{test_data → data}/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +0 -0
  398. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  399. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  400. /imap_processing/{mag/l1b → tests/spacecraft}/__init__.py +0 -0
  401. /imap_processing/{swe/l1b/engineering_unit_convert_table.csv → tests/swe/lut/imap_swe_eu-conversion_20240510_v000.csv} +0 -0
  402. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_40P_Phi28p5_BeamCal_LinearScan_phi28.50_theta-0.00_20240207T102740.CCSDS +0 -0
  403. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_7P_Phi0.0_BeamCal_LinearScan_phi0.04_theta-0.01_20230821T121304.CCSDS +0 -0
  404. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.CCSDS +0 -0
  405. /imap_processing/tests/ultra/{test_data → data}/l0/Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.CCSDS +0 -0
  406. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_auxdata_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
  407. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_enaphxtofhangimg_FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.csv +0 -0
  408. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultraimgrates_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
  409. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimgevent_FM45_7P_Phi00_BeamCal_LinearScan_phi004_theta-001_20230821T121304.csv +0 -0
  410. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E1.cdf +0 -0
  411. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E12.cdf +0 -0
  412. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E24.cdf +0 -0
  413. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/LICENSE +0 -0
  414. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
  415. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,257 @@
1
+ """Functions for retrieving repointing table data."""
2
+
3
+ import logging
4
+ import typing
5
+ from collections.abc import Generator
6
+ from contextlib import contextmanager
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import spiceypy
11
+ from numpy.typing import NDArray
12
+
13
+ from imap_processing.spice.kernels import ensure_spice
14
+ from imap_processing.spice.time import met_to_sclkticks, sct_to_et
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @contextmanager
20
+ def open_spice_ck_file(pointing_frame_path: Path) -> Generator[int, None, None]:
21
+ """
22
+ Context manager for handling SPICE CK files.
23
+
24
+ Parameters
25
+ ----------
26
+ pointing_frame_path : str
27
+ Path to the CK file.
28
+
29
+ Yields
30
+ ------
31
+ handle : int
32
+ Handle to the opened CK file.
33
+ """
34
+ if pointing_frame_path.exists():
35
+ handle = spiceypy.dafopw(str(pointing_frame_path))
36
+ else:
37
+ handle = spiceypy.ckopn(str(pointing_frame_path), "CK", 0)
38
+ try:
39
+ yield handle
40
+ finally:
41
+ spiceypy.ckcls(handle)
42
+
43
+
44
+ @typing.no_type_check
45
+ @ensure_spice
46
+ def create_pointing_frame(
47
+ pointing_frame_path: Path,
48
+ ck_path: Path,
49
+ repoint_start_met: NDArray,
50
+ repoint_end_met: NDArray,
51
+ ) -> None:
52
+ """
53
+ Create the pointing frame.
54
+
55
+ Parameters
56
+ ----------
57
+ pointing_frame_path : pathlib.Path
58
+ Location of pointing frame kernel.
59
+ ck_path : pathlib.Path
60
+ Location of the CK kernel.
61
+ repoint_start_met : numpy.ndarray
62
+ Start time of the repointing in MET.
63
+ repoint_end_met : numpy.ndarray
64
+ End time of the repointing in MET.
65
+
66
+ Notes
67
+ -----
68
+ Kernels required to be furnished:
69
+ "imap_science_0001.tf",
70
+ "imap_sclk_0000.tsc",
71
+ "imap_sim_ck_2hr_2secsampling_with_nutation.bc" or
72
+ "sim_1yr_imap_attitude.bc",
73
+ "imap_wkcp.tf",
74
+ "naif0012.tls"
75
+
76
+ Assumptions:
77
+ - The MOC has removed timeframe in which nutation/procession are present.
78
+ TODO: We may come back and have a check for this.
79
+ - The pointing frame kernel is made based on the most recent ck kernel.
80
+ In other words 1:1 ratio.
81
+ """
82
+ # Get IDs.
83
+ # https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.gipool
84
+ id_imap_dps = spiceypy.gipool("FRAME_IMAP_DPS", 0, 1)
85
+ id_imap_sclk = spiceypy.gipool("CK_-43000_SCLK", 0, 1)
86
+
87
+ # Verify that only ck_path kernel is loaded.
88
+ count = spiceypy.ktotal("ck")
89
+ loaded_ck_kernel, _, _, _ = spiceypy.kdata(count - 1, "ck")
90
+
91
+ if count != 1 or str(ck_path) != loaded_ck_kernel:
92
+ raise ValueError(f"Error: Expected CK kernel {ck_path}")
93
+
94
+ id_imap_spacecraft = spiceypy.gipool("FRAME_IMAP_SPACECRAFT", 0, 1)
95
+
96
+ # Select only the pointings within the attitude coverage.
97
+ ck_cover = spiceypy.ckcov(
98
+ str(ck_path), int(id_imap_spacecraft), True, "INTERVAL", 0, "TDB"
99
+ )
100
+ num_intervals = spiceypy.wncard(ck_cover)
101
+ et_start, _ = spiceypy.wnfetd(ck_cover, 0)
102
+ _, et_end = spiceypy.wnfetd(ck_cover, num_intervals - 1)
103
+
104
+ sclk_ticks_start = met_to_sclkticks(repoint_start_met)
105
+ et_start_repoint = sct_to_et(sclk_ticks_start)
106
+ sclk_ticks_end = met_to_sclkticks(repoint_end_met)
107
+ et_end_repoint = sct_to_et(sclk_ticks_end)
108
+
109
+ valid_mask = (et_start_repoint >= et_start) & (et_end_repoint <= et_end)
110
+ et_start_repoint = et_start_repoint[valid_mask]
111
+ et_end_repoint = et_end_repoint[valid_mask]
112
+
113
+ with open_spice_ck_file(pointing_frame_path) as handle:
114
+ for i in range(len(repoint_start_met)):
115
+ # 1 spin/15 seconds; 10 quaternions / spin.
116
+ num_samples = (et_end_repoint[i] - et_start_repoint[i]) / 15 * 10
117
+ # There were rounding errors when using spiceypy.pxform
118
+ # so np.ceil and np.floor were used to ensure the start
119
+ # and end times were within the ck range.
120
+ et_times = np.linspace(
121
+ np.ceil(et_start_repoint[i] * 1e6) / 1e6,
122
+ np.floor(et_end_repoint[i] * 1e6) / 1e6,
123
+ int(num_samples),
124
+ )
125
+
126
+ # Create a rotation matrix
127
+ rotation_matrix = _create_rotation_matrix(et_times)
128
+
129
+ # Convert the rotation matrix to a quaternion.
130
+ # https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.m2q
131
+ q_avg = spiceypy.m2q(rotation_matrix)
132
+
133
+ # https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.sce2c
134
+ # Convert start and end times to SCLK.
135
+ sclk_begtim = spiceypy.sce2c(int(id_imap_sclk), et_times[0])
136
+ sclk_endtim = spiceypy.sce2c(int(id_imap_sclk), et_times[-1])
137
+
138
+ # Create the pointing frame kernel.
139
+ # https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.ckw02
140
+ spiceypy.ckw02(
141
+ # Handle of an open CK file.
142
+ handle,
143
+ # Start time of the segment.
144
+ sclk_begtim,
145
+ # End time of the segment.
146
+ sclk_endtim,
147
+ # Pointing frame ID.
148
+ int(id_imap_dps),
149
+ # Reference frame.
150
+ "ECLIPJ2000", # Reference frame
151
+ # Identifier.
152
+ "IMAP_DPS",
153
+ # Number of pointing intervals.
154
+ 1,
155
+ # Start times of individual pointing records within segment.
156
+ # Since there is only a single record this is equal to sclk_begtim.
157
+ np.array([sclk_begtim]),
158
+ # End times of individual pointing records within segment.
159
+ # Since there is only a single record this is equal to sclk_endtim.
160
+ np.array([sclk_endtim]), # Single stop time
161
+ # Average quaternion.
162
+ q_avg,
163
+ # 0.0 Angular rotation terms.
164
+ np.array([0.0, 0.0, 0.0]),
165
+ # Rates (seconds per tick) at which the quaternion and
166
+ # angular velocity change.
167
+ np.array([1.0]),
168
+ )
169
+
170
+
171
+ @typing.no_type_check
172
+ @ensure_spice
173
+ def _average_quaternions(et_times: np.ndarray) -> NDArray:
174
+ """
175
+ Average the quaternions.
176
+
177
+ Parameters
178
+ ----------
179
+ et_times : numpy.ndarray
180
+ Array of times between et_start and et_end.
181
+
182
+ Returns
183
+ -------
184
+ q_avg : np.ndarray
185
+ Average quaternion.
186
+ """
187
+ aggregate = np.zeros((4, 4))
188
+ for tdb in et_times:
189
+ # we use a quick and dirty method here for grabbing the quaternions
190
+ # from the attitude kernel. Depending on how well the kernel input
191
+ # data is built and sampled, there may or may not be aliasing with this
192
+ # approach. If it turns out that we need to pull the quaternions
193
+ # directly from the CK there are several routines that exist to do this
194
+ # but it's not straight forward. We'll revisit this if needed.
195
+
196
+ # Rotation matrix from IMAP spacecraft frame to ECLIPJ2000.
197
+ # https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.pxform
198
+ body_rots = spiceypy.pxform("IMAP_SPACECRAFT", "ECLIPJ2000", tdb)
199
+ # Convert rotation matrix to quaternion.
200
+ # https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.m2q
201
+ body_quat = spiceypy.m2q(body_rots)
202
+
203
+ # Standardize the quaternion so that they may be compared.
204
+ body_quat = body_quat * np.sign(body_quat[0])
205
+ # Aggregate quaternions into a single matrix.
206
+ aggregate += np.outer(body_quat, body_quat)
207
+
208
+ # Reference: "On Averaging Rotations".
209
+ # Link: https://link.springer.com/content/pdf/10.1023/A:1011129215388.pdf
210
+ aggregate /= len(et_times)
211
+
212
+ # Compute eigen values and vectors of the matrix A
213
+ # Eigenvalues tell you how much "influence" each
214
+ # direction (eigenvector) has.
215
+ # The largest eigenvalue corresponds to the direction
216
+ # that has the most influence.
217
+ # The eigenvector corresponding to the largest
218
+ # eigenvalue points in the direction that has the most
219
+ # combined rotation influence.
220
+ eigvals, eigvecs = np.linalg.eig(aggregate)
221
+ # q0: The scalar part of the quaternion.
222
+ # q1, q2, q3: The vector part of the quaternion.
223
+ q_avg = eigvecs[:, np.argmax(eigvals)]
224
+
225
+ return q_avg
226
+
227
+
228
+ def _create_rotation_matrix(et_times: np.ndarray) -> NDArray:
229
+ """
230
+ Create a rotation matrix.
231
+
232
+ Parameters
233
+ ----------
234
+ et_times : numpy.ndarray
235
+ Array of times between et_start and et_end.
236
+
237
+ Returns
238
+ -------
239
+ rotation_matrix : np.ndarray
240
+ Rotation matrix.
241
+ """
242
+ # Averaged quaternions.
243
+ q_avg = _average_quaternions(et_times)
244
+
245
+ # Converts the averaged quaternion (q_avg) into a rotation matrix
246
+ # and get inertial z axis.
247
+ # https://spiceypy.readthedocs.io/en/main/documentation.html#spiceypy.spiceypy.q2m
248
+ z_avg = spiceypy.q2m(list(q_avg))[:, 2]
249
+ # y_avg is perpendicular to both z_avg and the standard Z-axis.
250
+ y_avg = np.cross(z_avg, [0, 0, 1])
251
+ # x_avg is perpendicular to y_avg and z_avg.
252
+ x_avg = np.cross(y_avg, z_avg)
253
+
254
+ # Construct the rotation matrix from x_avg, y_avg, z_avg
255
+ rotation_matrix = np.asarray([x_avg, y_avg, z_avg])
256
+
257
+ return rotation_matrix
@@ -0,0 +1,149 @@
1
+ """Functions for retrieving repointing table data."""
2
+
3
+ import logging
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Union
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ from numpy import typing as npt
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def get_repoint_data() -> pd.DataFrame:
16
+ """
17
+ Read repointing file using environment variable and return as dataframe.
18
+
19
+ Pointing and repointing nomenclature can be confusing. In this case,
20
+ repoint is taken to mean a repoint maneuver. Thus, repoint_start and repoint_end
21
+ are the times that bound when the spacecraft is performing a repointing maneuver.
22
+ This is different from a pointing which is the time between repointing maneuvers.
23
+
24
+ REPOINT_DATA_FILEPATH environment variable should point to a local
25
+ file where the repointing csv file is located.
26
+
27
+ Returns
28
+ -------
29
+ repoint_df : pandas.DataFrame
30
+ The repointing csv loaded into a pandas dataframe. The dataframe will
31
+ contain the following columns:
32
+
33
+ * `repoint_start_sec_sclk`: Starting MET seconds of repoint maneuver.
34
+ * `repoint_start_subsec_sclk`: Starting MET microseconds of repoint
35
+ maneuver.
36
+ * `repoint_start_met`: Floating point MET of repoint maneuver start time.
37
+ Derived from `repoint_start_sec_sclk` and `repoint_start_subsec_sclk`.
38
+ * `repoint_start_utc`: UTC time of repoint maneuver start time.
39
+ * `repoint_end_sec_sclk`: Ending MET seconds of repoint maneuver.
40
+ * `repoint_end_subsec_sclk`: Ending MET microseconds of repoint maneuver.
41
+ * `repoint_end_met`: Floating point MET of repoint maneuver end time.
42
+ Derived from `repoint_end_sec_sclk` and `repoint_end_subsec_sclk`.
43
+ * `repoint_end_utc`: UTC time of repoint maneuver end time.
44
+ * `repoint_id`: Unique ID number of each repoint maneuver.
45
+ """
46
+ repoint_data_filepath = os.getenv("REPOINT_DATA_FILEPATH")
47
+ if repoint_data_filepath is not None:
48
+ path_to_spin_file = Path(repoint_data_filepath)
49
+ else:
50
+ # Handle the case where the environment variable is not set
51
+ raise ValueError("REPOINT_DATA_FILEPATH environment variable is not set.")
52
+
53
+ logger.info(f"Reading repointing data from {path_to_spin_file}")
54
+ repoint_df = pd.read_csv(path_to_spin_file, comment="#")
55
+
56
+ # Compute times by combining seconds and subseconds fields
57
+ repoint_df["repoint_start_met"] = (
58
+ repoint_df["repoint_start_sec_sclk"]
59
+ + repoint_df["repoint_start_subsec_sclk"] / 1e6
60
+ )
61
+ repoint_df["repoint_end_met"] = (
62
+ repoint_df["repoint_end_sec_sclk"] + repoint_df["repoint_end_subsec_sclk"] / 1e6
63
+ )
64
+
65
+ return repoint_df
66
+
67
+
68
+ def interpolate_repoint_data(
69
+ query_met_times: Union[float, npt.NDArray],
70
+ ) -> pd.DataFrame:
71
+ """
72
+ Interpolate repointing data to the queried MET times.
73
+
74
+ In addition to the repoint start, end, and id values that come directly from
75
+ the universal repointing table, a column is added to the output dataframe
76
+ which indicates whether each query met time occurs during a repoint maneuver
77
+ i.e. between the repoint start and end times of a row in the repointing
78
+ table.
79
+
80
+ Query times that are more than 24-hours after that last repoint start time
81
+ in the repoint table will cause an error to be raised. The assumption here
82
+ is that we shouldn't be processing data that occurs that close to the next
83
+ expected repoint start time before getting an updated repoint table.
84
+
85
+ Parameters
86
+ ----------
87
+ query_met_times : float or np.ndarray
88
+ Query times in Mission Elapsed Time (MET).
89
+
90
+ Returns
91
+ -------
92
+ repoint_df : pandas.DataFrame
93
+ Repoint table data interpolated such that there is one row
94
+ for each of the queried MET times. Output columns are:
95
+
96
+ * `repoint_start_sec_sclk`
97
+ * `repoint_start_subsec_sclk`
98
+ * `repoint_start_met`
99
+ * `repoint_end_sec_sclk`
100
+ * `repoint_end_subsec_sclk`
101
+ * `repoint_end_met`
102
+ * `repoint_id`
103
+ * `repoint_in_progress`
104
+
105
+ Raises
106
+ ------
107
+ ValueError : If any of the query_met_times are before the first repoint
108
+ start time or after the last repoint start time plus 24-hours.
109
+ """
110
+ repoint_df = get_repoint_data()
111
+
112
+ # Ensure query_met_times is an array
113
+ query_met_times = np.atleast_1d(query_met_times)
114
+
115
+ # Make sure no query times are before the first repoint in the dataframe.
116
+ repoint_df_start_met = repoint_df["repoint_start_met"].values[0]
117
+ if np.any(query_met_times < repoint_df_start_met):
118
+ bad_times = query_met_times[query_met_times < repoint_df_start_met]
119
+ raise ValueError(
120
+ f"{bad_times.size} query times are before the first repoint start "
121
+ f" time in the repoint table. {bad_times=}, {repoint_df_start_met=}"
122
+ )
123
+ # Make sure that no query times are after the valid range of the dataframe.
124
+ # We approximate the end time of the table by adding 24 hours to the last
125
+ # known repoint start time.
126
+ repoint_df_end_met = repoint_df["repoint_start_met"].values[-1] + 24 * 60 * 60
127
+ if np.any(query_met_times >= repoint_df_end_met):
128
+ bad_times = query_met_times[query_met_times >= repoint_df_end_met]
129
+ raise ValueError(
130
+ f"{bad_times.size} query times are after the valid time of the "
131
+ f"pointing table. The valid end time is 24-hours after the last "
132
+ f"repoint_start_time. {bad_times=}, {repoint_df_end_met=}"
133
+ )
134
+
135
+ # Find the row index for each queried MET time such that:
136
+ # repoint_start_time[i] <= MET < repoint_start_time[i+1]
137
+ row_indices = (
138
+ np.searchsorted(repoint_df["repoint_start_met"], query_met_times, side="right")
139
+ - 1
140
+ )
141
+ out_df = repoint_df.iloc[row_indices]
142
+
143
+ # Add a column indicating if the query time is during a repoint or not.
144
+ # The table already has the correct row for each query time, so we
145
+ # only need to check if the query time is less than the repoint end time to
146
+ # get the same result as `repoint_start_time <= query_met_times < repoint_end_time`.
147
+ out_df["repoint_in_progress"] = query_met_times < out_df["repoint_end_met"].values
148
+
149
+ return out_df
@@ -22,20 +22,21 @@ def get_spin_data() -> pd.DataFrame:
22
22
  It could be s3 filepath that can be used to download the data
23
23
  through API or it could be path EFS or Batch volume mount path.
24
24
 
25
- Spin data should contain the following fields:
26
- * spin_number
27
- * spin_start_sec
28
- * spin_start_subsec
29
- * spin_period_sec
30
- * spin_period_valid
31
- * spin_phase_valid
32
- * spin_period_source
33
- * thruster_firing
34
-
35
25
  Returns
36
26
  -------
37
27
  spin_data : pandas.DataFrame
38
- Spin data.
28
+ Spin data. The DataFrame will have the following columns:
29
+
30
+ * `spin_number`: Unique integer spin number.
31
+ * `spin_start_sec_sclk`: MET seconds of spin start time.
32
+ * `spin_start_subsec_sclk`: MET microseconds of spin start time.
33
+ * `spin_start_met`: Floating point MET seconds of spin start.
34
+ * `spin_start_utc`: UTC string of spin start time.
35
+ * `spin_period_sec`: Floating point spin period in seconds.
36
+ * `spin_period_valid`: Boolean indicating whether spin period is valid.
37
+ * `spin_phase_valid`: Boolean indicating whether spin phase is valid.
38
+ * `spin_period_source`: Source used for determining spin period.
39
+ * `thruster_firing`: Boolean indicating whether thruster is firing.
39
40
  """
40
41
  spin_data_filepath = os.getenv("SPIN_DATA_FILEPATH")
41
42
  if spin_data_filepath is not None:
@@ -44,11 +45,24 @@ def get_spin_data() -> pd.DataFrame:
44
45
  # Handle the case where the environment variable is not set
45
46
  raise ValueError("SPIN_DATA_FILEPATH environment variable is not set.")
46
47
 
47
- spin_df = pd.read_csv(path_to_spin_file, comment="#")
48
- # Combine spin_start_sec and spin_start_subsec to get the spin start
49
- # time in seconds. The spin start subseconds are in milliseconds.
50
- spin_df["spin_start_time"] = (
51
- spin_df["spin_start_sec"] + spin_df["spin_start_subsec"] / 1e3
48
+ spin_df = pd.read_csv(
49
+ path_to_spin_file,
50
+ comment="#",
51
+ dtype={
52
+ "spin_number": int,
53
+ "spin_start_sec_sclk": int,
54
+ "spin_start_subsec_sclk": int,
55
+ "spin_start_utc": str,
56
+ "spin_period_sec": float,
57
+ "spin_period_valid": bool,
58
+ "spin_period_source": int,
59
+ "thruster_firing": bool,
60
+ },
61
+ )
62
+ # Combine spin_start_sec_sclk and spin_start_subsec_sclk to get the spin start
63
+ # time in seconds. The spin start subseconds are in microseconds.
64
+ spin_df["spin_start_met"] = (
65
+ spin_df["spin_start_sec_sclk"] + spin_df["spin_start_subsec_sclk"] / 1e6
52
66
  )
53
67
 
54
68
  return spin_df
@@ -71,18 +85,9 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
71
85
  Returns
72
86
  -------
73
87
  spin_df : pandas.DataFrame
74
- Spin table data with the spin-phase column added and one row
75
- interpolated for each queried MET time. Output columns are:
76
- * spin_number
77
- * spin_start_sec
78
- * spin_start_subsec
79
- * spin_period_sec
80
- * spin_period_valid
81
- * spin_phase_valid
82
- * spin_period_source
83
- * thruster_firing
84
- * spin_start_met
85
- * sc_spin_phase
88
+ Spin table data interpolated for each queried MET time. In addition to
89
+ the columns output from :py:func:`get_spin_data`, the `sc_spin_phase`
90
+ column is added and is uniquely computed for each queried MET time.
86
91
  """
87
92
  spin_df = get_spin_data()
88
93
 
@@ -95,9 +100,9 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
95
100
  query_met_times = np.atleast_1d(query_met_times)
96
101
 
97
102
  # Make sure input times are within the bounds of spin data
98
- spin_df_start_time = spin_df["spin_start_time"].values[0]
103
+ spin_df_start_time = spin_df["spin_start_met"].values[0]
99
104
  spin_df_end_time = (
100
- spin_df["spin_start_time"].values[-1] + spin_df["spin_period_sec"].values[-1]
105
+ spin_df["spin_start_met"].values[-1] + spin_df["spin_period_sec"].values[-1]
101
106
  )
102
107
  input_start_time = query_met_times.min()
103
108
  input_end_time = query_met_times.max()
@@ -115,13 +120,13 @@ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.Data
115
120
  # >>> np.searchsorted(df['a'], [0, 13, 15, 32, 70], side='right')
116
121
  # array([1, 1, 2, 3, 5])
117
122
  last_spin_indices = (
118
- np.searchsorted(spin_df["spin_start_time"], query_met_times, side="right") - 1
123
+ np.searchsorted(spin_df["spin_start_met"], query_met_times, side="right") - 1
119
124
  )
120
125
  # Generate a dataframe with one row per query time
121
126
  out_df = spin_df.iloc[last_spin_indices]
122
127
 
123
128
  # Calculate spin phase
124
- spin_phases = (query_met_times - out_df["spin_start_time"].values) / out_df[
129
+ spin_phases = (query_met_times - out_df["spin_start_met"].values) / out_df[
125
130
  "spin_period_sec"
126
131
  ].values
127
132
 
@@ -185,7 +190,7 @@ def get_spacecraft_spin_phase(
185
190
  Get the spacecraft spin phase for the input query times.
186
191
 
187
192
  Formula to calculate spin phase:
188
- spin_phase = (query_met_times - spin_start_time) / spin_period_sec
193
+ spin_phase = (query_met_times - spin_start_met) / spin_period_sec
189
194
 
190
195
  Parameters
191
196
  ----------
@@ -2,6 +2,7 @@
2
2
 
3
3
  import typing
4
4
  from collections.abc import Collection, Iterable
5
+ from datetime import datetime
5
6
  from typing import Union
6
7
 
7
8
  import numpy as np
@@ -297,3 +298,26 @@ def et_to_utc(
297
298
  UTC time(s).
298
299
  """
299
300
  return spiceypy.et2utc(et, format_str, precision, utclen)
301
+
302
+
303
+ def epoch_to_doy(epoch: np.ndarray) -> npt.NDArray:
304
+ """
305
+ Convert epoch times to day of year (1-365/366).
306
+
307
+ Parameters
308
+ ----------
309
+ epoch : xarray.DataArray
310
+ Time, number of nanoseconds since J2000 with leap seconds included.
311
+
312
+ Returns
313
+ -------
314
+ day_of_year : numpy.ndarray
315
+ Day of year (1-365/366) for each epoch value.
316
+ """
317
+ et = ttj2000ns_to_et(epoch.data)
318
+ # Get UTC time strings in ISO calendar format
319
+ time_strings = et_to_utc(et, "ISOC")
320
+ # Extract DOY from datetime
321
+ return np.array(
322
+ [datetime.fromisoformat(date).timetuple().tm_yday for date in time_strings]
323
+ )
@@ -72,8 +72,7 @@ def filter_good_data(full_sweep_sci: xr.Dataset) -> npt.NDArray:
72
72
  f"{full_sweep_sci['sweep_table'].data[bad_cycle_indices]}"
73
73
  )
74
74
  logger.debug(
75
- "Plan ID should be same: "
76
- f"{full_sweep_sci['plan_id'].data[bad_cycle_indices]}"
75
+ f"Plan ID should be same: {full_sweep_sci['plan_id'].data[bad_cycle_indices]}"
77
76
  )
78
77
  logger.debug(
79
78
  f"Mode Id should be 3(HVSCI): {full_sweep_sci['mode'].data[bad_cycle_indices]}"
@@ -426,7 +425,7 @@ def process_sweep_data(full_sweep_sci: xr.Dataset, cem_prefix: str) -> xr.Datase
426
425
 
427
426
 
428
427
  def process_swapi_science(
429
- sci_dataset: xr.Dataset, hk_dataset: xr.Dataset, data_version: str
428
+ sci_dataset: xr.Dataset, hk_dataset: xr.Dataset
430
429
  ) -> xr.Dataset:
431
430
  """
432
431
  Will process SWAPI science data and create CDF file.
@@ -437,8 +436,6 @@ def process_swapi_science(
437
436
  L0 data.
438
437
  hk_dataset : xarray.Dataset
439
438
  Housekeeping data.
440
- data_version : str
441
- Version of the data product being created.
442
439
 
443
440
  Returns
444
441
  -------
@@ -509,6 +506,10 @@ def process_swapi_science(
509
506
  # since we are not processing in real-time, the ground processing
510
507
  # algorithm should use the closest timestamp HK packet to fill in
511
508
  # the data quality for the SCI data per SWAPI team.
509
+
510
+ # Drop duplicate epoch values in HK data. Otherwise, the nearest
511
+ # method will not work as expected because .sel requires unique values.
512
+ hk_dataset = hk_dataset.drop_duplicates("epoch")
512
513
  good_sweep_times = good_sweep_sci["epoch"].data
513
514
  good_sweep_hk_data = hk_dataset.sel({"epoch": good_sweep_times}, method="nearest")
514
515
 
@@ -582,8 +583,6 @@ def process_swapi_science(
582
583
  )
583
584
 
584
585
  # Add other global attributes
585
- # TODO: add others like below once add_global_attribute is fixed
586
- cdf_manager.add_global_attribute("Data_version", data_version)
587
586
  l1_global_attrs = cdf_manager.get_global_attributes("imap_swapi_l1_sci")
588
587
  l1_global_attrs["Apid"] = f"{sci_dataset['pkt_apid'].data[0]}"
589
588
 
@@ -628,6 +627,18 @@ def process_swapi_science(
628
627
  dims=["epoch"],
629
628
  attrs=cdf_manager.get_variable_attributes("plan_id"),
630
629
  )
630
+ # Add ESA_LVL5 for L2 and L3 purposes.
631
+ # We need to store ESA_LVL5 at SEQ_NUMBER==11
632
+ # which is 71 energy step's ESA_LVL5 value. ESA_LVL5 gets
633
+ # updated every 6th step. This is used in L2 to calculate last 9 fine
634
+ # energy steps.
635
+ dataset["esa_lvl5"] = xr.DataArray(
636
+ good_sweep_sci["esa_lvl5"].data.reshape(total_full_sweeps, 12)[:, 11],
637
+ name="esa_lvl5",
638
+ dims=["epoch"],
639
+ attrs=cdf_manager.get_variable_attributes("esa_lvl5"),
640
+ )
641
+
631
642
  # Add these additional housekeeping support data
632
643
  # SWP_HK.LUT_CHOICE - Which LUT is in use
633
644
  # SWP_HK.FPGA_TYPE - Type number of the FPGA
@@ -699,7 +710,7 @@ def process_swapi_science(
699
710
  return dataset
700
711
 
701
712
 
702
- def swapi_l1(dependencies: list, data_version: str) -> xr.Dataset:
713
+ def swapi_l1(dependencies: list) -> xr.Dataset:
703
714
  """
704
715
  Will process SWAPI level 0 data to level 1.
705
716
 
@@ -707,8 +718,6 @@ def swapi_l1(dependencies: list, data_version: str) -> xr.Dataset:
707
718
  ----------
708
719
  dependencies : list
709
720
  Input dependencies needed for L1 processing.
710
- data_version : str
711
- Version of the data product being created.
712
721
 
713
722
  Returns
714
723
  -------
@@ -740,7 +749,7 @@ def swapi_l1(dependencies: list, data_version: str) -> xr.Dataset:
740
749
  ):
741
750
  # process science data
742
751
  sci_dataset = process_swapi_science(
743
- l0_unpacked_dict[SWAPIAPID.SWP_SCI], l1_hk_ds, data_version
752
+ l0_unpacked_dict[SWAPIAPID.SWP_SCI], l1_hk_ds
744
753
  )
745
754
  processed_data.append(sci_dataset)
746
755
 
@@ -749,7 +758,6 @@ def swapi_l1(dependencies: list, data_version: str) -> xr.Dataset:
749
758
  # Add HK datalevel attrs
750
759
  imap_attrs = ImapCdfAttributes()
751
760
  imap_attrs.add_instrument_global_attrs("swapi")
752
- imap_attrs.add_global_attribute("Data_version", data_version)
753
761
  imap_attrs.add_instrument_variable_attrs(instrument="swapi", level=None)
754
762
  hk_ds.attrs.update(imap_attrs.get_global_attributes("imap_swapi_l1_hk"))
755
763
  hk_common_attrs = imap_attrs.get_variable_attributes("hk_attrs")