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
@@ -4,25 +4,45 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  from pathlib import Path
7
+ from typing import NamedTuple
7
8
 
8
9
  import numpy as np
9
10
  import pandas as pd
10
11
  import xarray as xr
12
+ from numpy import typing as npt
13
+ from numpy._typing import NDArray
11
14
 
12
15
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
13
16
  from imap_processing.cdf.utils import parse_filename_like
14
- from imap_processing.hi.utils import create_dataset_variables, full_dataarray
17
+ from imap_processing.hi.l1a.science_direct_event import (
18
+ DE_CLOCK_TICK_S,
19
+ HALF_CLOCK_TICK_S,
20
+ )
21
+ from imap_processing.hi.utils import (
22
+ CoincidenceBitmap,
23
+ create_dataset_variables,
24
+ full_dataarray,
25
+ parse_sensor_number,
26
+ )
15
27
  from imap_processing.spice.geometry import (
16
28
  SpiceFrame,
17
29
  frame_transform,
18
30
  frame_transform_az_el,
19
31
  )
32
+ from imap_processing.spice.spin import (
33
+ get_instrument_spin_phase,
34
+ get_spin_data,
35
+ )
20
36
  from imap_processing.spice.time import ttj2000ns_to_et
21
37
 
38
+ N_SPIN_BINS = 3600
39
+ SPIN_PHASE_BIN_EDGES = np.linspace(0, 1, N_SPIN_BINS + 1)
40
+ SPIN_PHASE_BIN_CENTERS = (SPIN_PHASE_BIN_EDGES[:-1] + SPIN_PHASE_BIN_EDGES[1:]) / 2
41
+
22
42
  logger = logging.getLogger(__name__)
23
43
 
24
44
 
25
- def hi_l1c(dependencies: list, data_version: str) -> xr.Dataset:
45
+ def hi_l1c(dependencies: list) -> list[xr.Dataset]:
26
46
  """
27
47
  High level IMAP-Hi l1c processing function.
28
48
 
@@ -36,10 +56,6 @@ def hi_l1c(dependencies: list, data_version: str) -> xr.Dataset:
36
56
  dependencies : list
37
57
  Input dependencies needed for l1c processing.
38
58
 
39
- data_version : str
40
- Data version to write to CDF files and the Data_version CDF attribute.
41
- Should be in the format Vxxx.
42
-
43
59
  Returns
44
60
  -------
45
61
  l1c_dataset : xarray.Dataset
@@ -56,9 +72,7 @@ def hi_l1c(dependencies: list, data_version: str) -> xr.Dataset:
56
72
  "Input dependencies not recognized for l1c pset processing."
57
73
  )
58
74
 
59
- # TODO: revisit this
60
- l1c_dataset.attrs["Data_version"] = data_version
61
- return l1c_dataset
75
+ return [l1c_dataset]
62
76
 
63
77
 
64
78
  def generate_pset_dataset(
@@ -88,18 +102,19 @@ def generate_pset_dataset(
88
102
  config_df = CalibrationProductConfig.from_csv(calibration_prod_config_path)
89
103
 
90
104
  pset_dataset = empty_pset_dataset(
105
+ de_dataset.epoch.data[0],
91
106
  de_dataset.esa_energy_step.data,
92
107
  config_df.cal_prod_config.number_of_products,
93
108
  logical_source_parts["sensor"],
94
109
  )
95
- # For ISTP, epoch should be the center of the time bin.
96
- pset_dataset.epoch.data[0] = np.mean(de_dataset.epoch.data[[0, -1]]).astype(
97
- np.int64
98
- )
99
110
  pset_et = ttj2000ns_to_et(pset_dataset.epoch.data[0])
100
111
  # Calculate and add despun_z, hae_latitude, and hae_longitude variables to
101
112
  # the pset_dataset
102
113
  pset_dataset.update(pset_geometry(pset_et, logical_source_parts["sensor"]))
114
+ # Bin the counts into the spin-bins
115
+ pset_dataset.update(pset_counts(pset_dataset.coords, config_df, de_dataset))
116
+ # Calculate and add the exposure time to the pset_dataset
117
+ pset_dataset.update(pset_exposure(pset_dataset.coords, de_dataset))
103
118
 
104
119
  # TODO: The following section will go away as PSET algorithms to populate
105
120
  # these variables are written.
@@ -107,8 +122,6 @@ def generate_pset_dataset(
107
122
  attr_mgr.add_instrument_global_attrs("hi")
108
123
  attr_mgr.add_instrument_variable_attrs(instrument="hi", level=None)
109
124
  for var_name in [
110
- "counts",
111
- "exposure_times",
112
125
  "background_rates",
113
126
  "background_rates_uncertainty",
114
127
  ]:
@@ -122,13 +135,15 @@ def generate_pset_dataset(
122
135
 
123
136
 
124
137
  def empty_pset_dataset(
125
- l1b_energy_steps: np.ndarray, n_cal_prods: int, sensor_str: str
138
+ epoch_val: int, l1b_energy_steps: np.ndarray, n_cal_prods: int, sensor_str: str
126
139
  ) -> xr.Dataset:
127
140
  """
128
141
  Allocate an empty xarray.Dataset with appropriate pset coordinates.
129
142
 
130
143
  Parameters
131
144
  ----------
145
+ epoch_val : int
146
+ The starting epoch in J2000 TT nanoseconds for data in the PSET.
132
147
  l1b_energy_steps : np.ndarray
133
148
  The array of esa_energy_step data from the L1B DE product.
134
149
  n_cal_prods : int
@@ -148,12 +163,12 @@ def empty_pset_dataset(
148
163
  # preallocate coordinates xr.DataArrays
149
164
  coords = dict()
150
165
  # epoch coordinate has only 1 entry for pointing set
151
- epoch_attrs = attr_mgr.get_variable_attributes("epoch")
166
+ epoch_attrs = attr_mgr.get_variable_attributes("epoch", check_schema=False)
152
167
  epoch_attrs.update(
153
168
  attr_mgr.get_variable_attributes("hi_pset_epoch", check_schema=False)
154
169
  )
155
170
  coords["epoch"] = xr.DataArray(
156
- np.empty(1, dtype=np.int64), # TODO: get dtype from cdf attrs?
171
+ np.array([epoch_val], dtype=np.int64), # TODO: get dtype from cdf attrs?
157
172
  name="epoch",
158
173
  dims=["epoch"],
159
174
  attrs=epoch_attrs,
@@ -188,7 +203,7 @@ def empty_pset_dataset(
188
203
  ).copy()
189
204
  dtype = attrs.pop("dtype")
190
205
  coords["spin_angle_bin"] = xr.DataArray(
191
- np.arange(int(360 / 0.1), dtype=dtype),
206
+ np.arange(N_SPIN_BINS, dtype=dtype),
192
207
  name="spin_angle_bin",
193
208
  dims=["spin_angle_bin"],
194
209
  attrs=attrs,
@@ -271,8 +286,8 @@ def pset_geometry(pset_et: float, sensor_str: str) -> dict[str, xr.DataArray]:
271
286
  el = 0 if "90" in sensor_str else -45
272
287
  dps_az_el = np.array(
273
288
  [
274
- np.arange(0.05, 360, 0.1),
275
- np.full(3600, el),
289
+ SPIN_PHASE_BIN_CENTERS * 360,
290
+ np.full(N_SPIN_BINS, el),
276
291
  ]
277
292
  ).T
278
293
  hae_az_el = frame_transform_az_el(
@@ -282,7 +297,7 @@ def pset_geometry(pset_et: float, sensor_str: str) -> dict[str, xr.DataArray]:
282
297
  geometry_vars.update(
283
298
  create_dataset_variables(
284
299
  ["hae_latitude", "hae_longitude"],
285
- (1, 3600),
300
+ (1, N_SPIN_BINS),
286
301
  att_manager_lookup_str="hi_pset_{0}",
287
302
  )
288
303
  )
@@ -295,6 +310,361 @@ def pset_geometry(pset_et: float, sensor_str: str) -> dict[str, xr.DataArray]:
295
310
  return geometry_vars
296
311
 
297
312
 
313
+ def pset_counts(
314
+ pset_coords: dict[str, xr.DataArray],
315
+ config_df: pd.DataFrame,
316
+ l1b_de_dataset: xr.Dataset,
317
+ ) -> dict[str, xr.DataArray]:
318
+ """
319
+ Bin direct events into PSET spin-bins.
320
+
321
+ Parameters
322
+ ----------
323
+ pset_coords : dict[str, xr.DataArray]
324
+ The PSET coordinates from the xr.Dataset.
325
+ config_df : pd.DataFrame
326
+ The calibration product configuration dataframe.
327
+ l1b_de_dataset : xr.Dataset
328
+ The L1B dataset for the pointing being processed.
329
+
330
+ Returns
331
+ -------
332
+ dict[str, xr.DataArray]
333
+ Dictionary containing new exposure_times DataArray to be added to the PSET
334
+ dataset.
335
+ """
336
+ # Generate exposure time variable filled with zeros
337
+ counts_var = create_dataset_variables(
338
+ ["counts"],
339
+ coords=pset_coords,
340
+ att_manager_lookup_str="hi_pset_{0}",
341
+ fill_value=0,
342
+ )
343
+
344
+ # Convert list of DEs to pandas dataframe for ease indexing/filtering
345
+ de_df = l1b_de_dataset.drop_dims("epoch").to_pandas()
346
+
347
+ # Remove DEs not in Goodtimes/angles
348
+ good_mask = good_time_and_phase_mask(
349
+ l1b_de_dataset.event_met.values, l1b_de_dataset.spin_phase.values
350
+ )
351
+ de_df = de_df[good_mask]
352
+
353
+ # The calibration product configuration potentially has different coincidence
354
+ # types for each ESA and different TOF windows for each calibration product,
355
+ # esa energy step combination. Because of this we need to filter DEs that
356
+ # belong to each combo individually.
357
+ # Loop over the esa_energy_step values first
358
+ for esa_energy, esa_df in config_df.groupby(level="esa_energy_step"):
359
+ # Create a mask for all DEs at the current esa_energy_step.
360
+ # esa_energy_step is recorded for each packet rather than for each DE,
361
+ # so we use ccsds_index to get the esa_energy_step for each DE
362
+ esa_mask = (
363
+ l1b_de_dataset["esa_energy_step"].data[de_df["ccsds_index"].to_numpy()]
364
+ == esa_energy
365
+ )
366
+ # Now loop over the calibration products for the current ESA energy
367
+ for config_row in esa_df.itertuples():
368
+ # Remove DEs that are not at the current ESA energy and in the list
369
+ # of coincidence types for the current calibration product
370
+ type_mask = de_df["coincidence_type"].isin(
371
+ config_row.coincidence_type_values
372
+ )
373
+ filtered_de_df = de_df[(esa_mask & type_mask)]
374
+
375
+ # Use the TOF window mask to remove DEs with TOFs outside the allowed range
376
+ tof_fill_vals = {
377
+ f"tof_{detector_pair}": l1b_de_dataset[f"tof_{detector_pair}"].attrs[
378
+ "FILLVAL"
379
+ ]
380
+ for detector_pair in CalibrationProductConfig.tof_detector_pairs
381
+ }
382
+ tof_in_window_mask = get_tof_window_mask(
383
+ filtered_de_df, config_row, tof_fill_vals
384
+ )
385
+ filtered_de_df = filtered_de_df[tof_in_window_mask]
386
+
387
+ # Bin remaining DEs into spin-bins
388
+ i_esa = np.flatnonzero(pset_coords["esa_energy_step"].data == esa_energy)[0]
389
+ # spin_phase is in the range [0, 1). Multiplying by N_SPIN_BINS and
390
+ # truncating to an integer gives the correct bin index
391
+ spin_bin_indices = (
392
+ filtered_de_df["spin_phase"].to_numpy() * N_SPIN_BINS
393
+ ).astype(int)
394
+ # When iterating over rows of a dataframe, the names of the multi-index
395
+ # are not preserved. Below, `config_row.Index[0]` gets the cal_prod_num
396
+ # value from the namedtuple representing the dataframe row.
397
+ np.add.at(
398
+ counts_var["counts"].data[0, i_esa, config_row.Index[0]],
399
+ spin_bin_indices,
400
+ 1,
401
+ )
402
+ return counts_var
403
+
404
+
405
+ def get_tof_window_mask(
406
+ de_df: pd.DataFrame, prod_config_row: NamedTuple, fill_vals: dict
407
+ ) -> NDArray[bool]:
408
+ """
409
+ Generate a mask indicating which DEs to keep based on TOF windows.
410
+
411
+ Parameters
412
+ ----------
413
+ de_df : pd.DataFrame
414
+ The Direct Event dataframe for the DEs to filter based on the TOF
415
+ windows.
416
+ prod_config_row : namedtuple
417
+ A single row of the prod config dataframe represented as a named tuple.
418
+ fill_vals : dict
419
+ A dictionary containing the fill values used in the input DE TOF
420
+ dataframe values. This value should be derived from the L1B DE CDF
421
+ TOF variable attributes.
422
+
423
+ Returns
424
+ -------
425
+ window_mask : np.ndarray
426
+ A mask with one entry per DE in the input `de_df` indicating which DEs
427
+ contain TOF values within the windows specified by `prod_config_row`.
428
+ The mask is intended to directly filter the DE dataframe.
429
+ """
430
+ detector_pairs = CalibrationProductConfig.tof_detector_pairs
431
+ tof_in_window_mask = np.empty((len(detector_pairs), len(de_df)), dtype=bool)
432
+ for i_pair, detector_pair in enumerate(detector_pairs):
433
+ low_limit = getattr(prod_config_row, f"tof_{detector_pair}_low")
434
+ high_limit = getattr(prod_config_row, f"tof_{detector_pair}_high")
435
+ tof_array = de_df[f"tof_{detector_pair}"].to_numpy()
436
+ # The TOF in window mask contains True wherever the TOF is within
437
+ # the configuration low/high bounds OR the FILLVAL is present. The
438
+ # FILLVAL indicates that the detector pair was not hit. DEs with
439
+ # the incorrect coincidence_type are already filtered out and this
440
+ # implementation simplifies combining the tof_in_window_masks in
441
+ # the next step.
442
+ tof_in_window_mask[i_pair] = np.logical_or(
443
+ np.logical_and(low_limit <= tof_array, tof_array <= high_limit),
444
+ tof_array == fill_vals[f"tof_{detector_pair}"],
445
+ )
446
+ return np.all(tof_in_window_mask, axis=0)
447
+
448
+
449
+ def pset_exposure(
450
+ pset_coords: dict[str, xr.DataArray], l1b_de_dataset: xr.Dataset
451
+ ) -> dict[str, xr.DataArray]:
452
+ """
453
+ Calculate PSET exposure time.
454
+
455
+ Parameters
456
+ ----------
457
+ pset_coords : dict[str, xr.DataArray]
458
+ The PSET coordinates from the xr.Dataset.
459
+ l1b_de_dataset : xr.Dataset
460
+ The L1B dataset for the pointing being processed.
461
+
462
+ Returns
463
+ -------
464
+ dict[str, xr.DataArray]
465
+ Dictionary containing new exposure_times DataArray to be added to the PSET
466
+ dataset.
467
+ """
468
+ # Extract the sensor number (45 or 90) for computing spin phase
469
+ sensor_number = parse_sensor_number(l1b_de_dataset.attrs["Logical_source"])
470
+
471
+ # Generate exposure time variable filled with zeros
472
+ exposure_var = create_dataset_variables(
473
+ ["exposure_times"],
474
+ coords=pset_coords,
475
+ att_manager_lookup_str="hi_pset_{0}",
476
+ fill_value=0,
477
+ )
478
+
479
+ # Get a subset of the l1b_de_dataset that contains only the second
480
+ # of each pair of packets at an ESA step.
481
+ data_subset = find_second_de_packet_data(l1b_de_dataset)
482
+
483
+ # Get the pandas dataframe with spin data
484
+ spin_df = get_spin_data()
485
+
486
+ # Loop over each of the CCSDS data rows that have been identified as the second
487
+ # packet at an ESA step.
488
+ # When implementing this, the memory needed to avoid this for loop was computed
489
+ # and determined to be so large that the for loop is warranted.
490
+ for _, packet_row in data_subset.groupby("epoch"):
491
+ clock_tick_mets, clock_tick_weights = get_de_clock_ticks_for_esa_step(
492
+ packet_row["ccsds_met"].values, spin_df
493
+ )
494
+
495
+ # Clock tick MET times are accumulation "edges". To get the mean spin-phase
496
+ # for a given clock tick, add 1/2 clock tick and compute spin-phase.
497
+ spin_phases = np.atleast_1d(
498
+ get_instrument_spin_phase(
499
+ clock_tick_mets + HALF_CLOCK_TICK_S,
500
+ SpiceFrame[f"IMAP_HI_{sensor_number}"],
501
+ )
502
+ )
503
+
504
+ # Remove ticks not in good times/angles
505
+ good_mask = good_time_and_phase_mask(clock_tick_mets, spin_phases)
506
+ spin_phases = spin_phases[good_mask]
507
+ clock_tick_weights = clock_tick_weights[good_mask]
508
+
509
+ # TODO: Account for flyback time. See alg doc section 2.3.5
510
+
511
+ # Bin exposure times into spin-phase bins
512
+ new_exposure_times, _ = np.histogram(
513
+ spin_phases, bins=SPIN_PHASE_BIN_EDGES, weights=clock_tick_weights
514
+ )
515
+ # Accumulate the new exposure times for current esa_step
516
+ i_esa = np.flatnonzero(
517
+ pset_coords["esa_energy_step"].values
518
+ == packet_row["esa_energy_step"].values
519
+ )[0]
520
+ exposure_var["exposure_times"].values[:, i_esa] += new_exposure_times
521
+
522
+ # Convert exposure clock ticks to seconds
523
+ exposure_var["exposure_times"].values *= DE_CLOCK_TICK_S
524
+
525
+ return exposure_var
526
+
527
+
528
+ def find_second_de_packet_data(l1b_dataset: xr.Dataset) -> xr.Dataset:
529
+ """
530
+ Find the telemetry entries for the second packet at an ESA step.
531
+
532
+ Parameters
533
+ ----------
534
+ l1b_dataset : xr.Dataset
535
+ The L1B Direct Event Dataset for the current pointing.
536
+
537
+ Returns
538
+ -------
539
+ reduced_dataset : xr.Dataset
540
+ A dataset containing only the entries for the second packet at an ESA step.
541
+ """
542
+ epoch_dataset = l1b_dataset.drop_dims("event_met")
543
+ # We should get two CCSDS packets per 8-spin ESA step.
544
+ # Get the indices of the packet before each ESA change.
545
+ esa_step = epoch_dataset["esa_step"].values
546
+ second_esa_packet_idx = np.append(
547
+ np.flatnonzero(np.diff(esa_step) != 0), len(esa_step) - 1
548
+ )
549
+ # Remove esa steps at 0 - these are calibrations
550
+ second_esa_packet_idx = second_esa_packet_idx[esa_step[second_esa_packet_idx] != 0]
551
+ # Remove indices where we don't have two consecutive packets at the same ESA
552
+ if second_esa_packet_idx[0] == 0:
553
+ logger.warning(
554
+ f"Removing packet 0 with ESA step: {esa_step[0]} from"
555
+ f"calculation of exposure time due to missing matched pair."
556
+ )
557
+ second_esa_packet_idx = second_esa_packet_idx[1:]
558
+ missing_esa_pair_mask = (
559
+ esa_step[second_esa_packet_idx - 1] != esa_step[second_esa_packet_idx]
560
+ )
561
+ if missing_esa_pair_mask.any():
562
+ logger.warning(
563
+ f"Removing {missing_esa_pair_mask.sum()} packets from exposure "
564
+ f"time calculation due to missing ESA step DE packet pairs."
565
+ )
566
+ second_esa_packet_idx = second_esa_packet_idx[~missing_esa_pair_mask]
567
+ # Reduce the dataset to just the second packet entries
568
+ data_subset = epoch_dataset.isel(epoch=second_esa_packet_idx)
569
+ return data_subset
570
+
571
+
572
+ def get_de_clock_ticks_for_esa_step(
573
+ ccsds_met: float, spin_df: pd.DataFrame
574
+ ) -> tuple[np.ndarray, np.ndarray]:
575
+ """
576
+ Generate an array of clock tick MET times for an 8-spin ESA step.
577
+
578
+ Find the closest spin start time in the input spin dataframe to the packet
579
+ creation time (`ccsds_met`) and generate an array of clock tick MET times
580
+ for the period covered by the previous 8-spin group and an array of weights
581
+ that represent the fraction of each clock tick that occurred in the 8-spin
582
+ group.
583
+
584
+ Parameters
585
+ ----------
586
+ ccsds_met : float
587
+ The CCSDS MET of the second packet in a DE packet pair.
588
+ spin_df : pd.DataFrame
589
+ Universal spin table dataframe.
590
+
591
+ Returns
592
+ -------
593
+ clock_tick_mets : np.ndarray
594
+ Array of MET times that a clock tick occurred in an 8-spin group of spins
595
+ during which the ESA step was constant.
596
+ clock_tick_weights : np.ndarray
597
+ Array of weights to use when binning the clock tick MET times into spin-bins.
598
+ """
599
+ # Find the last spin_table entry with the start less than the CCSDS MET.
600
+ # The CCSDS packet gets created just AFTER the final spin in the 8-spin
601
+ # ESA step group so this match is the end time. The start time is
602
+ # 8-spins earlier.
603
+ spin_start_mets = spin_df.spin_start_met.to_numpy()
604
+ # CCSDS MET has one second resolution, add one to it to make sure it is
605
+ # greater than the spin start time it ended on.
606
+ end_time_ind = np.flatnonzero(ccsds_met + 1 >= spin_start_mets).max()
607
+
608
+ # If the minimum absolute difference is greater than 1/2 the spin-phase
609
+ # we have a problem.
610
+ if (
611
+ ccsds_met - spin_start_mets[end_time_ind]
612
+ > spin_df.iloc[end_time_ind].spin_period_sec / 2
613
+ ):
614
+ raise ValueError(
615
+ "The difference between ccsds_met and spin_start_met, "
616
+ f"{ccsds_met - spin_start_mets[end_time_ind]} seconds, "
617
+ f"is too large. Check the spin table loaded for this pointing."
618
+ )
619
+ # If the end time index less than 8, we don't have enough spins in the
620
+ # spin table to get a start time, so raise an error.
621
+ if end_time_ind < 8:
622
+ raise ValueError(
623
+ "Error determining start/end time for exposure time. "
624
+ f"The CCSDS MET time {ccsds_met} "
625
+ "is less than 8 spins from the loaded spin table data."
626
+ )
627
+ clock_tick_mets = np.arange(
628
+ spin_start_mets[end_time_ind - 8],
629
+ spin_start_mets[end_time_ind],
630
+ DE_CLOCK_TICK_S,
631
+ dtype=float,
632
+ )
633
+ # The final clock-tick bin has less exposure time because the next spin
634
+ # will trigger FSW to change ESA steps part way through that time. To
635
+ # account for this in exposure time calculation, assign an array of
636
+ # weights to use when binnig the clock-ticks to spin-bins. Weights are
637
+ # fractional clock ticks. All weights are 1 except for the last one in
638
+ # the array.
639
+ clock_tick_weights = np.ones_like(clock_tick_mets, dtype=float)
640
+ clock_tick_weights[-1] = (
641
+ spin_start_mets[end_time_ind] - clock_tick_mets[-1]
642
+ ) / DE_CLOCK_TICK_S
643
+ return clock_tick_mets, clock_tick_weights
644
+
645
+
646
+ def good_time_and_phase_mask(
647
+ tick_mets: np.ndarray, spin_phases: np.ndarray
648
+ ) -> npt.NDArray:
649
+ """
650
+ Filter out the clock tick times that are not in good times and angles.
651
+
652
+ Parameters
653
+ ----------
654
+ tick_mets : np.ndarray
655
+ Clock-tick MET times.
656
+ spin_phases : np.ndarray
657
+ Spin phases for each clock tick.
658
+
659
+ Returns
660
+ -------
661
+ keep_mask : np.ndarray
662
+ Boolean mask indicating which clock ticks are in good times/phases.
663
+ """
664
+ # TODO: Implement this once we have Goodtimes data product defined.
665
+ return np.full_like(tick_mets, True, dtype=bool)
666
+
667
+
298
668
  @pd.api.extensions.register_dataframe_accessor("cal_prod_config")
299
669
  class CalibrationProductConfig:
300
670
  """
@@ -310,21 +680,20 @@ class CalibrationProductConfig:
310
680
  "cal_prod_num",
311
681
  "esa_energy_step",
312
682
  )
683
+ tof_detector_pairs = ("ab", "ac1", "bc1", "c1c2")
313
684
  required_columns = (
314
685
  "coincidence_type_list",
315
- "tof_ab_low",
316
- "tof_ab_high",
317
- "tof_ac1_low",
318
- "tof_ac1_high",
319
- "tof_bc1_low",
320
- "tof_bc1_high",
321
- "tof_c1c2_low",
322
- "tof_c1c2_high",
686
+ *[
687
+ f"tof_{det_pair}_{limit}"
688
+ for det_pair in tof_detector_pairs
689
+ for limit in ["low", "high"]
690
+ ],
323
691
  )
324
692
 
325
693
  def __init__(self, pandas_obj: pd.DataFrame) -> None:
326
694
  self._validate(pandas_obj)
327
695
  self._obj = pandas_obj
696
+ self._add_coincidence_values_column()
328
697
 
329
698
  def _validate(self, df: pd.DataFrame) -> None:
330
699
  """
@@ -351,6 +720,18 @@ class CalibrationProductConfig:
351
720
  # TODO: Verify that the same ESA energy steps exist in all unique calibration
352
721
  # product numbers
353
722
 
723
+ def _add_coincidence_values_column(self) -> None:
724
+ """Generate and add the coincidence_type_values column to the dataframe."""
725
+ # Add a column that consists of the coincidence type strings converted
726
+ # to integer values
727
+ self._obj["coincidence_type_values"] = self._obj.apply(
728
+ lambda row: tuple(
729
+ CoincidenceBitmap.detector_hit_str_to_int(entry)
730
+ for entry in row["coincidence_type_list"]
731
+ ),
732
+ axis=1,
733
+ )
734
+
354
735
  @classmethod
355
736
  def from_csv(cls, path: Path) -> pd.DataFrame:
356
737
  """
@@ -366,12 +747,15 @@ class CalibrationProductConfig:
366
747
  dataframe : pandas.DataFrame
367
748
  Validated calibration product configuration data frame.
368
749
  """
369
- return pd.read_csv(
750
+ df = pd.read_csv(
370
751
  path,
371
752
  index_col=cls.index_columns,
372
- converters={"coincidence_type_list": lambda s: s.split("|")},
753
+ converters={"coincidence_type_list": lambda s: tuple(s.split("|"))},
373
754
  comment="#",
374
755
  )
756
+ # Force the _init_ method to run by using the namespace
757
+ _ = df.cal_prod_config.number_of_products
758
+ return df
375
759
 
376
760
  @property
377
761
  def number_of_products(self) -> int: