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,723 @@
1
+ """IMAP-HIT L2 data processing."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ import xarray as xr
9
+
10
+ from imap_processing.hit.hit_utils import (
11
+ add_summed_particle_data_to_dataset,
12
+ get_attribute_manager,
13
+ )
14
+ from imap_processing.hit.l2.constants import (
15
+ FILLVAL_FLOAT32,
16
+ L2_SECTORED_ANCILLARY_PATH_PREFIX,
17
+ L2_STANDARD_ANCILLARY_PATH_PREFIX,
18
+ L2_SUMMED_ANCILLARY_PATH_PREFIX,
19
+ N_AZIMUTH,
20
+ SECONDS_PER_10_MIN,
21
+ SECONDS_PER_MIN,
22
+ STANDARD_PARTICLE_ENERGY_RANGE_MAPPING,
23
+ VALID_SECTORED_SPECIES,
24
+ VALID_SPECIES,
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # TODO:
30
+ # - review logging levels to use (debug vs. info)
31
+ # - determine where to pull ancillary data. Storing it locally for now
32
+
33
+
34
+ def hit_l2(dependency: xr.Dataset) -> list[xr.Dataset]:
35
+ """
36
+ Will process HIT data to L2.
37
+
38
+ Processes dependencies needed to create L2 data products.
39
+
40
+ Parameters
41
+ ----------
42
+ dependency : xr.Dataset
43
+ L1B xarray science dataset that is either summed rates
44
+ standard rates or sector rates.
45
+
46
+ Returns
47
+ -------
48
+ processed_data : list[xarray.Dataset]
49
+ List of one L2 dataset.
50
+ """
51
+ logger.info("Creating HIT L2 science datasets")
52
+
53
+ # Create the attribute manager for this data level
54
+ attr_mgr = get_attribute_manager("l2")
55
+
56
+ l2_datasets: dict = {}
57
+
58
+ # Process science data to L2 datasets
59
+ if "imap_hit_l1b_summed-rates" in dependency.attrs["Logical_source"]:
60
+ l2_datasets["imap_hit_l2_summed-intensity"] = process_summed_intensity_data(
61
+ dependency
62
+ )
63
+ logger.info("HIT L2 summed intensity dataset created")
64
+
65
+ if "imap_hit_l1b_standard-rates" in dependency.attrs["Logical_source"]:
66
+ l2_datasets["imap_hit_l2_standard-intensity"] = process_standard_intensity_data(
67
+ dependency
68
+ )
69
+ logger.info("HIT L2 standard intensity dataset created")
70
+
71
+ if "imap_hit_l1b_sectored-rates" in dependency.attrs["Logical_source"]:
72
+ l2_datasets["imap_hit_l2_macropixel-intensity"] = (
73
+ process_sectored_intensity_data(dependency)
74
+ )
75
+ logger.info("HIT L2 macropixel intensity dataset created")
76
+
77
+ # Update attributes and dimensions
78
+ for logical_source, dataset in l2_datasets.items():
79
+ dataset.attrs = attr_mgr.get_global_attributes(logical_source)
80
+
81
+ # TODO: Add CDF attributes to yaml once they're defined for L2 science data
82
+ # consider moving attribute handling to hit_utils.py
83
+ # Assign attributes and dimensions to each data array in the Dataset
84
+ for field in dataset.data_vars.keys():
85
+ try:
86
+ # Create a dict of dimensions using the DEPEND_I keys in the
87
+ # attributes
88
+ dims = {
89
+ key: value
90
+ for key, value in attr_mgr.get_variable_attributes(field).items()
91
+ if "DEPEND" in key
92
+ }
93
+ dataset[field].attrs = attr_mgr.get_variable_attributes(field)
94
+ dataset[field].assign_coords(dims)
95
+ except KeyError:
96
+ # TODO: consider raising an error after L2 attributes are defined.
97
+ # Until then, continue with processing and log warning
98
+ logger.warning(f"Field {field} not found in attribute manager.")
99
+
100
+ # Skip schema check for epoch to prevent attr_mgr from adding the
101
+ # DEPEND_0 attribute which isn't required for epoch
102
+ dataset.epoch.attrs = attr_mgr.get_variable_attributes(
103
+ "epoch", check_schema=False
104
+ )
105
+
106
+ logger.info(f"HIT L2 dataset created for {logical_source}")
107
+
108
+ return list(l2_datasets.values())
109
+
110
+
111
+ def calculate_intensities(
112
+ rates: xr.DataArray,
113
+ factors: xr.Dataset,
114
+ ) -> xr.DataArray:
115
+ """
116
+ Calculate the intensities for given rates and equation factors.
117
+
118
+ Uses vectorization to calculate the intensities for an array of rates
119
+ for all epochs.
120
+
121
+ This function uses equation 9 and 12 from the HIT algorithm document:
122
+ ((Summed L1B Rates) / (Delta Time * Delta E * Geometry Factor * Efficiency)) - b
123
+
124
+ Parameters
125
+ ----------
126
+ rates : xr.DataArray
127
+ The L1B rates to be converted to intensities.
128
+ factors : xr.Dataset
129
+ The ancillary data factors needed to calculate the intensity.
130
+ This includes delta_e, geometry_factor, efficiency, and b.
131
+
132
+ Returns
133
+ -------
134
+ xr.DataArray
135
+ The calculated intensities for all epochs.
136
+ """
137
+ # Calculate the intensity using vectorized operations
138
+ intensity = (
139
+ rates
140
+ / (
141
+ factors.delta_time
142
+ * factors.delta_e
143
+ * factors.geometry_factor
144
+ * factors.efficiency
145
+ )
146
+ ) - factors.b
147
+
148
+ # Apply intensity where rates are not equal to the fill value
149
+ intensity = xr.where(rates == FILLVAL_FLOAT32, FILLVAL_FLOAT32, intensity)
150
+
151
+ return intensity
152
+
153
+
154
+ def reshape_for_sectored(arr: np.ndarray) -> np.ndarray:
155
+ """
156
+ Reshape the ancillary data for sectored rates.
157
+
158
+ Reshape the 3D arrays (epoch, energy, declination) to 4D arrays
159
+ (epoch, energy, azimuth, declination) by repeating the data
160
+ along the azimuth dimension. This is done to match the dimensions
161
+ of the sectored rates data to allow for proper calculation of
162
+ intensities.
163
+
164
+ Parameters
165
+ ----------
166
+ arr : np.ndarray
167
+ The ancillary data array to reshape.
168
+
169
+ Returns
170
+ -------
171
+ np.ndarray
172
+ The reshaped array.
173
+ """
174
+ return np.repeat(
175
+ arr.reshape((arr.shape[0], arr.shape[1], arr.shape[2]))[:, :, np.newaxis, :],
176
+ N_AZIMUTH,
177
+ axis=2,
178
+ )
179
+
180
+
181
+ def build_ancillary_dataset(
182
+ delta_e: np.ndarray,
183
+ geometry_factors: np.ndarray,
184
+ efficiencies: np.ndarray,
185
+ b: np.ndarray,
186
+ species_array: xr.DataArray,
187
+ ) -> xr.Dataset:
188
+ """
189
+ Build a xarray Dataset containing ancillary data for calculating intensity.
190
+
191
+ This function builds a dataset containing the factors needed for calculating
192
+ intensity for a given species. The dataset is built based on the dimensions
193
+ and coordinates of the species data to align data along the epoch dimension.
194
+
195
+ Parameters
196
+ ----------
197
+ delta_e : np.ndarray
198
+ Delta E values which are energy bin widths.
199
+ geometry_factors : np.ndarray
200
+ Geometry factor values.
201
+ efficiencies : np.ndarray
202
+ Efficiency values.
203
+ b : np.ndarray
204
+ Background intensity values.
205
+ species_array : xr.Dataset
206
+ Data array for the species to extract coordinates from.
207
+
208
+ Returns
209
+ -------
210
+ ancillary_ds : xr.Dataset
211
+ A dataset containing all ancillary data variables and coordinates that
212
+ align with the L2 dataset.
213
+ """
214
+ data_vars = {}
215
+
216
+ # Check if this is sectored data (i.e., has azimuth and declination dims)
217
+ is_sectored = (
218
+ "declination" in species_array.dims or "declination" in species_array.coords
219
+ )
220
+
221
+ # Build variables
222
+ data_vars["delta_e"] = (species_array.dims, delta_e)
223
+ data_vars["geometry_factor"] = (
224
+ species_array.dims,
225
+ geometry_factors,
226
+ )
227
+ data_vars["efficiency"] = (
228
+ species_array.dims,
229
+ efficiencies,
230
+ )
231
+ data_vars["b"] = (species_array.dims, b)
232
+ data_vars["delta_time"] = (
233
+ ["epoch"],
234
+ np.full(
235
+ len(species_array.epoch),
236
+ SECONDS_PER_10_MIN if is_sectored else SECONDS_PER_MIN,
237
+ ),
238
+ )
239
+
240
+ return xr.Dataset(data_vars, coords=species_array.coords)
241
+
242
+
243
+ def calculate_intensities_for_a_species(
244
+ species_variable: str, l2_dataset: xr.Dataset, ancillary_data_frames: dict
245
+ ) -> xr.Dataset:
246
+ """
247
+ Calculate the intensity for a given species in the dataset.
248
+
249
+ This function orchestrates calculating the intensity for a given species
250
+ in the L2 dataset using ancillary data determined by the dynamic threshold
251
+ state (0-3).
252
+
253
+ The intensity is calculated using the equation:
254
+ (L1B Rates) / (Delta Time * Delta E * Geometry Factor * Efficiency) - b
255
+
256
+ where the equation factors are retrieved from the ancillary data for
257
+ the given species and dynamic threshold states.
258
+
259
+ Parameters
260
+ ----------
261
+ species_variable : str
262
+ The species variable to calculate the intensity for, which is either the species
263
+ or a statistical uncertainty.
264
+ (i.e. "h", "h_stat_uncert_minus", or "h_stat_uncert_plus").
265
+ l2_dataset : xr.Dataset
266
+ The L2 dataset containing the L1B rates needed to calculate the intensity.
267
+ ancillary_data_frames : dict
268
+ Dictionary containing ancillary data for each dynamic threshold state where
269
+ the key is the dynamic threshold state and the value is a pandas DataFrame
270
+ containing the ancillary data for all species.
271
+
272
+ Returns
273
+ -------
274
+ updated_ds : xr.Dataset
275
+ The updated dataset with intensities calculated for the given species.
276
+ """
277
+ updated_ds = l2_dataset.copy()
278
+ dynamic_threshold_states = updated_ds["dynamic_threshold_state"].values
279
+ unique_states = np.unique(dynamic_threshold_states)
280
+ species_name = (
281
+ species_variable.split("_")[0]
282
+ if "_uncert_" in species_variable
283
+ else species_variable
284
+ )
285
+
286
+ # Subset ancillary data for this species
287
+ species_ancillary_by_state = {
288
+ state: get_species_ancillary_data(state, ancillary_data_frames, species_name)
289
+ for state in unique_states
290
+ }
291
+
292
+ # Extract parameters - 3D arrays (num_states, energy bins, values)
293
+ delta_e = np.stack(
294
+ [
295
+ species_ancillary_by_state[state]["delta_e"]
296
+ for state in dynamic_threshold_states
297
+ ]
298
+ )
299
+ geometry_factors = np.stack(
300
+ [
301
+ species_ancillary_by_state[state]["geometry_factor"]
302
+ for state in dynamic_threshold_states
303
+ ]
304
+ )
305
+ efficiencies = np.stack(
306
+ [
307
+ species_ancillary_by_state[state]["efficiency"]
308
+ for state in dynamic_threshold_states
309
+ ]
310
+ )
311
+ b = np.stack(
312
+ [species_ancillary_by_state[state]["b"] for state in dynamic_threshold_states]
313
+ )
314
+
315
+ # Reshape parameters for sectored rates to 4D arrays
316
+ if "declination" in updated_ds[species_variable].dims:
317
+ delta_e = reshape_for_sectored(delta_e)
318
+ geometry_factors = reshape_for_sectored(geometry_factors)
319
+ efficiencies = reshape_for_sectored(efficiencies)
320
+ b = reshape_for_sectored(b)
321
+
322
+ # Reshape parameters for summed and standard rates to 2D arrays
323
+ # by removing last dimension of size one, (n, n, 1)
324
+ else:
325
+ delta_e = np.squeeze(delta_e, axis=-1)
326
+ geometry_factors = np.squeeze(geometry_factors, axis=-1)
327
+ efficiencies = np.squeeze(efficiencies, axis=-1)
328
+ b = np.squeeze(b, axis=-1)
329
+
330
+ # Build ancillary xarray dataset
331
+ ancillary_ds = build_ancillary_dataset(
332
+ delta_e, geometry_factors, efficiencies, b, l2_dataset[species_name]
333
+ )
334
+
335
+ # Calculate intensities
336
+ updated_ds[species_variable] = calculate_intensities(
337
+ updated_ds[species_variable], ancillary_ds
338
+ )
339
+
340
+ return updated_ds
341
+
342
+
343
+ def calculate_intensities_for_all_species(
344
+ l2_dataset: xr.Dataset, ancillary_data_frames: dict, valid_data_variables: list
345
+ ) -> xr.Dataset:
346
+ """
347
+ Calculate the intensity for each species in the dataset.
348
+
349
+ Parameters
350
+ ----------
351
+ l2_dataset : xr.Dataset
352
+ The L2 dataset.
353
+ ancillary_data_frames : dict
354
+ Dictionary containing ancillary data for each dynamic threshold state
355
+ where the key is the dynamic threshold state and the value is a pandas
356
+ DataFrame containing the ancillary data.
357
+ valid_data_variables : list
358
+ A list of valid data variables to calculate intensity for.
359
+
360
+ Returns
361
+ -------
362
+ updated_ds : xr.Dataset
363
+ The updated dataset with the intensity calculated for each species.
364
+ """
365
+ updated_ds = l2_dataset.copy()
366
+
367
+ # Add statistical uncertainty variables to the list of valid variables
368
+ data_variables = (
369
+ valid_data_variables
370
+ + [f"{var}_stat_uncert_minus" for var in valid_data_variables]
371
+ + [f"{var}_stat_uncert_plus" for var in valid_data_variables]
372
+ )
373
+
374
+ # Calculate the intensity for each valid data variable
375
+ for species_variable in data_variables:
376
+ if species_variable in updated_ds.data_vars:
377
+ updated_ds = calculate_intensities_for_a_species(
378
+ species_variable, updated_ds, ancillary_data_frames
379
+ )
380
+ else:
381
+ logger.warning(
382
+ f"Variable {species_variable} not found in dataset. "
383
+ f"Skipping intensity calculation."
384
+ )
385
+
386
+ return updated_ds
387
+
388
+
389
+ def add_systematic_uncertainties(dataset: xr.Dataset, particle: str) -> xr.Dataset:
390
+ """
391
+ Add systematic uncertainties to the dataset.
392
+
393
+ Add systematic uncertainties to the dataset. Just zeros for now.
394
+ To change if/when HIT determines there are systematic uncertainties.
395
+
396
+ Parameters
397
+ ----------
398
+ dataset : xr.Dataset
399
+ The dataset to add the systematic uncertainties to
400
+ which contain the particle data variables.
401
+ particle : str
402
+ The particle name.
403
+
404
+ Returns
405
+ -------
406
+ updated_ds : xr.Dataset
407
+ The dataset with the systematic uncertainties added.
408
+ """
409
+ updated_ds = dataset.copy()
410
+
411
+ updated_ds[f"{particle}_sys_err_minus"] = xr.DataArray(
412
+ data=np.zeros(updated_ds[particle].shape, dtype=np.float32),
413
+ dims=updated_ds[particle].dims,
414
+ name=f"{particle}_sys_err_minus",
415
+ )
416
+ updated_ds[f"{particle}_sys_err_plus"] = xr.DataArray(
417
+ data=np.zeros(updated_ds[particle].shape, dtype=np.float32),
418
+ dims=updated_ds[particle].dims,
419
+ name=f"{particle}_sys_err_plus",
420
+ )
421
+
422
+ return updated_ds
423
+
424
+
425
+ def add_total_uncertainties(dataset: xr.Dataset, particle: str) -> xr.Dataset:
426
+ """
427
+ Add total uncertainties to the dataset.
428
+
429
+ This function calculates the total uncertainties for a given particle
430
+ by combining the statistical uncertainties and systematic uncertainties.
431
+
432
+ The total uncertainties are calculated as the square root of the sum
433
+ of the squares of the statistical and systematic uncertainties.
434
+
435
+ Parameters
436
+ ----------
437
+ dataset : xr.Dataset
438
+ The dataset to add the total uncertainties to.
439
+ particle : str
440
+ The particle name.
441
+
442
+ Returns
443
+ -------
444
+ updated_ds : xr.Dataset
445
+ The dataset with the total uncertainties added.
446
+ """
447
+ updated_ds = dataset.copy()
448
+
449
+ # Calculate the total uncertainties
450
+ total_minus = np.sqrt(
451
+ np.square(updated_ds[f"{particle}_stat_uncert_minus"])
452
+ + np.square(updated_ds[f"{particle}_sys_err_minus"])
453
+ )
454
+ total_plus = np.sqrt(
455
+ np.square(updated_ds[f"{particle}_stat_uncert_plus"])
456
+ + np.square(updated_ds[f"{particle}_sys_err_plus"])
457
+ )
458
+
459
+ updated_ds[f"{particle}_total_uncert_minus"] = xr.DataArray(
460
+ data=total_minus.astype(np.float32),
461
+ dims=updated_ds[particle].dims,
462
+ name=f"{particle}_total_uncert_minus",
463
+ )
464
+ updated_ds[f"{particle}_total_uncert_plus"] = xr.DataArray(
465
+ data=total_plus.astype(np.float32),
466
+ dims=updated_ds[particle].dims,
467
+ name=f"{particle}_total_uncert_plus",
468
+ )
469
+
470
+ return updated_ds
471
+
472
+
473
+ def get_species_ancillary_data(
474
+ dynamic_threshold_state: int, ancillary_data_frames: dict, species: str
475
+ ) -> dict:
476
+ """
477
+ Get the ancillary data for a given species and dynamic threshold state.
478
+
479
+ Parameters
480
+ ----------
481
+ dynamic_threshold_state : int
482
+ The dynamic threshold state for the ancillary data (0-3).
483
+ ancillary_data_frames : dict
484
+ Dictionary containing ancillary data for each dynamic threshold state
485
+ where the key is the dynamic threshold state and the value is a pandas
486
+ DataFrame containing the ancillary data.
487
+ species : str
488
+ The species to get the ancillary data for.
489
+
490
+ Returns
491
+ -------
492
+ dict
493
+ The ancillary data for the species and dynamic threshold state.
494
+ """
495
+ ancillary_df = ancillary_data_frames[dynamic_threshold_state]
496
+
497
+ # Remove any trailing spaces from all values in the DataFrame
498
+ ancillary_df = ancillary_df.map(lambda x: x.strip() if isinstance(x, str) else x)
499
+
500
+ # Get the ancillary data for the species and group by lower energy
501
+ species_ancillary_df = ancillary_df[ancillary_df["species"] == species]
502
+ grouped = species_ancillary_df.groupby("lower energy (mev)")
503
+ return {
504
+ "delta_e": np.array(grouped["delta e (mev)"].apply(list).tolist()),
505
+ "geometry_factor": np.array(
506
+ grouped["geometry factor (cm2 sr)"].apply(list).tolist()
507
+ ),
508
+ "efficiency": np.array(grouped["efficiency"].apply(list).tolist()),
509
+ "b": np.array(grouped["b"].apply(list).tolist()),
510
+ }
511
+
512
+
513
+ def load_ancillary_data(dynamic_threshold_states: set, path_prefix: Path) -> dict:
514
+ """
515
+ Load ancillary data based on the dynamic threshold state.
516
+
517
+ The dynamic threshold state (0-3) determines which ancillary file to use.
518
+ This function returns a dictionary with ancillary data for each state in
519
+ the dataset.
520
+
521
+ Parameters
522
+ ----------
523
+ dynamic_threshold_states : set
524
+ A set of dynamic threshold states in the L2 dataset.
525
+ path_prefix : Path
526
+ The path prefix for ancillary data files.
527
+
528
+ Returns
529
+ -------
530
+ dict
531
+ A dictionary with ancillary data for each dynamic threshold state.
532
+ """
533
+ # Load ancillary data
534
+ ancillary_data_frames = {
535
+ int(state): pd.read_csv(f"{path_prefix}{state}-factors_20250219_v002.csv")
536
+ for state in dynamic_threshold_states
537
+ }
538
+
539
+ # Convert column names and species values to lowercase
540
+ for df in ancillary_data_frames.values():
541
+ df.columns = df.columns.str.lower().str.strip()
542
+ df["species"] = df["species"].str.lower()
543
+
544
+ return ancillary_data_frames
545
+
546
+
547
+ def process_summed_intensity_data(l1b_summed_rates_dataset: xr.Dataset) -> xr.Dataset:
548
+ """
549
+ Will process L2 HIT summed intensity data from L1B summed rates.
550
+
551
+ This function converts the L1B summed rates to L2 summed intensities
552
+ using ancillary tables containing factors needed to calculate the
553
+ intensity (energy bin width, geometry factor, efficiency, and b).
554
+
555
+ Equation 12 from the HIT algorithm document:
556
+ Summed Intensity = (L1B Summed Rate) /
557
+ (60 * Delta E * Geometry Factor * Efficiency) - b
558
+
559
+ Parameters
560
+ ----------
561
+ l1b_summed_rates_dataset : xarray.Dataset
562
+ HIT L1B summed rates dataset.
563
+
564
+ Returns
565
+ -------
566
+ xr.Dataset
567
+ The processed L2 summed intensity dataset.
568
+ """
569
+ # Create a new dataset to store the L2 summed intensity data
570
+ l2_summed_intensity_dataset = l1b_summed_rates_dataset.copy(deep=True)
571
+
572
+ # Load ancillary data for each dynamic threshold state into a dictionary
573
+ ancillary_data_frames = load_ancillary_data(
574
+ set(l2_summed_intensity_dataset["dynamic_threshold_state"].values),
575
+ L2_SUMMED_ANCILLARY_PATH_PREFIX,
576
+ )
577
+
578
+ # Calculate the intensity for each species
579
+ l2_summed_intensity_dataset = calculate_intensities_for_all_species(
580
+ l2_summed_intensity_dataset, ancillary_data_frames, VALID_SPECIES
581
+ )
582
+
583
+ # Add total and systematic uncertainties to the dataset
584
+ for var in l2_summed_intensity_dataset.data_vars:
585
+ if var in VALID_SPECIES:
586
+ l2_summed_intensity_dataset = add_systematic_uncertainties(
587
+ l2_summed_intensity_dataset, var
588
+ )
589
+ l2_summed_intensity_dataset = add_total_uncertainties(
590
+ l2_summed_intensity_dataset, var
591
+ )
592
+
593
+ return l2_summed_intensity_dataset
594
+
595
+
596
+ def process_standard_intensity_data(
597
+ l1b_standard_rates_dataset: xr.Dataset,
598
+ ) -> xr.Dataset:
599
+ """
600
+ Will process L2 standard intensity data from L1B standard rates data.
601
+
602
+ This function converts L1B standard rates to L2 standard intensities for each
603
+ particle type and energy range using ancillary tables containing factors
604
+ needed to calculate the intensity (energy bin width, geometry factor, efficiency
605
+ and b).
606
+
607
+ First, rates from the l2fgrates, l3fgrates, and penfgrates data variables
608
+ in the L1B standard rates data are summed. These variables represent rates
609
+ for different detector penetration ranges (Range 2, Range 3, and Range 4
610
+ respectively). Only the energy ranges specified in the
611
+ STANDARD_PARTICLE_ENERGY_RANGE_MAPPING dictionary are included in this
612
+ product.
613
+
614
+ Intensity is then calculated from the summed standard rates:
615
+
616
+ Equation 9 from the HIT algorithm document:
617
+ Standard Intensity = (Summed L1B Standard Rates) /
618
+ (60 * Delta E * Geometry Factor * Efficiency) - b
619
+
620
+ Parameters
621
+ ----------
622
+ l1b_standard_rates_dataset : xr.Dataset
623
+ The L1B standard rates dataset.
624
+
625
+ Returns
626
+ -------
627
+ xr.Dataset
628
+ The L2 standard intensity dataset.
629
+ """
630
+ # Create a new dataset to store the L2 standard intensity data
631
+ l2_standard_intensity_dataset = xr.Dataset()
632
+
633
+ # Assign the epoch coordinate from the l1B dataset
634
+ l2_standard_intensity_dataset = l2_standard_intensity_dataset.assign_coords(
635
+ {"epoch": l1b_standard_rates_dataset.coords["epoch"]}
636
+ )
637
+
638
+ # Add dynamic threshold state to the dataset
639
+ l2_standard_intensity_dataset["dynamic_threshold_state"] = (
640
+ l1b_standard_rates_dataset["dynamic_threshold_state"]
641
+ )
642
+
643
+ # Load ancillary data for each dynamic threshold state into a dictionary
644
+ ancillary_data_frames = load_ancillary_data(
645
+ set(l2_standard_intensity_dataset["dynamic_threshold_state"].values),
646
+ L2_STANDARD_ANCILLARY_PATH_PREFIX,
647
+ )
648
+
649
+ # Process each particle type and add rates and uncertainties to the dataset
650
+ for particle, energy_ranges in STANDARD_PARTICLE_ENERGY_RANGE_MAPPING.items():
651
+ # Add standard particle rates and statistical uncertainties to the dataset
652
+ l2_standard_intensity_dataset = add_summed_particle_data_to_dataset(
653
+ l2_standard_intensity_dataset,
654
+ l1b_standard_rates_dataset,
655
+ particle,
656
+ energy_ranges,
657
+ )
658
+
659
+ l2_standard_intensity_dataset = calculate_intensities_for_all_species(
660
+ l2_standard_intensity_dataset, ancillary_data_frames, VALID_SPECIES
661
+ )
662
+
663
+ # Add total and systematic uncertainties to the dataset
664
+ for particle in STANDARD_PARTICLE_ENERGY_RANGE_MAPPING.keys():
665
+ l2_standard_intensity_dataset = add_systematic_uncertainties(
666
+ l2_standard_intensity_dataset, particle
667
+ )
668
+ l2_standard_intensity_dataset = add_total_uncertainties(
669
+ l2_standard_intensity_dataset, particle
670
+ )
671
+
672
+ return l2_standard_intensity_dataset
673
+
674
+
675
+ def process_sectored_intensity_data(
676
+ l1b_sectored_rates_dataset: xr.Dataset,
677
+ ) -> xr.Dataset:
678
+ """
679
+ Will process L2 HIT sectored intensity data from L1B sectored rates data.
680
+
681
+ This function converts the L1B sectored rates to L2 sectored intensities
682
+ using ancillary tables containing factors needed to calculate the
683
+ intensity (energy bin width, geometry factor, efficiency, and b).
684
+
685
+ Equation 12 from the HIT algorithm document:
686
+ Sectored Intensity = (Summed L1B Sectored Rates) /
687
+ (600 * Delta E * Geometry Factor * Efficiency) - b
688
+
689
+ Parameters
690
+ ----------
691
+ l1b_sectored_rates_dataset : xr.Dataset
692
+ The L1B sectored rates dataset.
693
+
694
+ Returns
695
+ -------
696
+ xr.Dataset
697
+ The processed L2 sectored intensity dataset.
698
+ """
699
+ # Create a new dataset to store the L2 sectored intensity data
700
+ l2_sectored_intensity_dataset = l1b_sectored_rates_dataset.copy(deep=True)
701
+
702
+ # Load ancillary data for each dynamic threshold state into a dictionary
703
+ ancillary_data_frames = load_ancillary_data(
704
+ set(l2_sectored_intensity_dataset["dynamic_threshold_state"].values),
705
+ L2_SECTORED_ANCILLARY_PATH_PREFIX,
706
+ )
707
+
708
+ # Calculate the intensity for each species
709
+ l2_sectored_intensity_dataset = calculate_intensities_for_all_species(
710
+ l2_sectored_intensity_dataset, ancillary_data_frames, VALID_SECTORED_SPECIES
711
+ )
712
+
713
+ # Add total and systematic uncertainties to the dataset
714
+ for var in l2_sectored_intensity_dataset.data_vars:
715
+ if var in VALID_SECTORED_SPECIES:
716
+ l2_sectored_intensity_dataset = add_systematic_uncertainties(
717
+ l2_sectored_intensity_dataset, var
718
+ )
719
+ l2_sectored_intensity_dataset = add_total_uncertainties(
720
+ l2_sectored_intensity_dataset, var
721
+ )
722
+
723
+ return l2_sectored_intensity_dataset