imap-processing 0.12.0__py3-none-any.whl → 0.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of imap-processing might be problematic. Click here for more details.

Files changed (272) hide show
  1. imap_processing/__init__.py +1 -0
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/ccsds_data.py +1 -2
  4. imap_processing/ccsds/excel_to_xtce.py +1 -2
  5. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +18 -12
  6. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +569 -0
  7. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +5 -5
  9. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +20 -1
  10. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +6 -4
  11. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +3 -3
  12. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +15 -0
  13. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +22 -0
  14. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +16 -0
  15. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +178 -5
  16. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -19
  18. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +8 -48
  19. imap_processing/cdf/utils.py +41 -33
  20. imap_processing/cli.py +463 -234
  21. imap_processing/codice/codice_l1a.py +260 -47
  22. imap_processing/codice/codice_l1b.py +51 -152
  23. imap_processing/codice/constants.py +38 -1
  24. imap_processing/ena_maps/ena_maps.py +658 -65
  25. imap_processing/ena_maps/utils/coordinates.py +1 -1
  26. imap_processing/ena_maps/utils/spatial_utils.py +10 -5
  27. imap_processing/glows/l1a/glows_l1a.py +28 -99
  28. imap_processing/glows/l1a/glows_l1a_data.py +2 -2
  29. imap_processing/glows/l1b/glows_l1b.py +1 -4
  30. imap_processing/glows/l1b/glows_l1b_data.py +1 -3
  31. imap_processing/glows/l2/glows_l2.py +2 -5
  32. imap_processing/hi/l1a/hi_l1a.py +31 -12
  33. imap_processing/hi/l1b/hi_l1b.py +80 -43
  34. imap_processing/hi/l1c/hi_l1c.py +12 -16
  35. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
  36. imap_processing/hit/hit_utils.py +93 -35
  37. imap_processing/hit/l0/decom_hit.py +3 -1
  38. imap_processing/hit/l1a/hit_l1a.py +30 -25
  39. imap_processing/hit/l1b/constants.py +6 -2
  40. imap_processing/hit/l1b/hit_l1b.py +279 -318
  41. imap_processing/hit/l2/constants.py +37 -0
  42. imap_processing/hit/l2/hit_l2.py +373 -264
  43. imap_processing/ialirt/l0/parse_mag.py +138 -10
  44. imap_processing/ialirt/l0/process_swapi.py +69 -0
  45. imap_processing/ialirt/l0/process_swe.py +318 -22
  46. imap_processing/ialirt/packet_definitions/ialirt.xml +216 -212
  47. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +1 -1
  48. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +1 -1
  49. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +14 -14
  50. imap_processing/ialirt/utils/grouping.py +1 -1
  51. imap_processing/idex/idex_constants.py +9 -1
  52. imap_processing/idex/idex_l0.py +22 -8
  53. imap_processing/idex/idex_l1a.py +75 -44
  54. imap_processing/idex/idex_l1b.py +9 -8
  55. imap_processing/idex/idex_l2a.py +79 -45
  56. imap_processing/idex/idex_l2b.py +120 -0
  57. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -39
  58. imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
  59. imap_processing/lo/l0/lo_science.py +1 -2
  60. imap_processing/lo/l1a/lo_l1a.py +1 -4
  61. imap_processing/lo/l1b/lo_l1b.py +527 -6
  62. imap_processing/lo/l1b/tof_conversions.py +11 -0
  63. imap_processing/lo/l1c/lo_l1c.py +1 -4
  64. imap_processing/mag/constants.py +43 -0
  65. imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
  66. imap_processing/mag/l1a/mag_l1a.py +2 -9
  67. imap_processing/mag/l1a/mag_l1a_data.py +10 -10
  68. imap_processing/mag/l1b/mag_l1b.py +84 -17
  69. imap_processing/mag/l1c/interpolation_methods.py +180 -3
  70. imap_processing/mag/l1c/mag_l1c.py +236 -70
  71. imap_processing/mag/l2/mag_l2.py +140 -0
  72. imap_processing/mag/l2/mag_l2_data.py +288 -0
  73. imap_processing/spacecraft/quaternions.py +1 -3
  74. imap_processing/spice/geometry.py +3 -3
  75. imap_processing/spice/kernels.py +0 -276
  76. imap_processing/spice/pointing_frame.py +257 -0
  77. imap_processing/spice/repoint.py +48 -19
  78. imap_processing/spice/spin.py +38 -33
  79. imap_processing/spice/time.py +24 -0
  80. imap_processing/swapi/l1/swapi_l1.py +16 -12
  81. imap_processing/swapi/l2/swapi_l2.py +116 -4
  82. imap_processing/swapi/swapi_utils.py +32 -0
  83. imap_processing/swe/l1a/swe_l1a.py +2 -9
  84. imap_processing/swe/l1a/swe_science.py +8 -11
  85. imap_processing/swe/l1b/swe_l1b.py +898 -23
  86. imap_processing/swe/l2/swe_l2.py +21 -77
  87. imap_processing/swe/utils/swe_constants.py +1 -0
  88. imap_processing/tests/ccsds/test_excel_to_xtce.py +1 -1
  89. imap_processing/tests/cdf/test_utils.py +14 -16
  90. imap_processing/tests/codice/conftest.py +44 -33
  91. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
  92. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
  93. imap_processing/tests/codice/test_codice_l1a.py +20 -11
  94. imap_processing/tests/codice/test_codice_l1b.py +6 -7
  95. imap_processing/tests/conftest.py +78 -22
  96. imap_processing/tests/ena_maps/test_ena_maps.py +462 -33
  97. imap_processing/tests/ena_maps/test_spatial_utils.py +1 -1
  98. imap_processing/tests/glows/conftest.py +10 -14
  99. imap_processing/tests/glows/test_glows_decom.py +4 -4
  100. imap_processing/tests/glows/test_glows_l1a_cdf.py +6 -27
  101. imap_processing/tests/glows/test_glows_l1a_data.py +6 -8
  102. imap_processing/tests/glows/test_glows_l1b.py +11 -11
  103. imap_processing/tests/glows/test_glows_l1b_data.py +5 -5
  104. imap_processing/tests/glows/test_glows_l2.py +2 -8
  105. imap_processing/tests/hi/conftest.py +1 -1
  106. imap_processing/tests/hi/test_hi_l1b.py +10 -12
  107. imap_processing/tests/hi/test_hi_l1c.py +27 -24
  108. imap_processing/tests/hi/test_l1a.py +7 -9
  109. imap_processing/tests/hi/test_science_direct_event.py +2 -2
  110. imap_processing/tests/hit/helpers/l1_validation.py +44 -43
  111. imap_processing/tests/hit/test_decom_hit.py +1 -1
  112. imap_processing/tests/hit/test_hit_l1a.py +9 -9
  113. imap_processing/tests/hit/test_hit_l1b.py +172 -217
  114. imap_processing/tests/hit/test_hit_l2.py +380 -118
  115. imap_processing/tests/hit/test_hit_utils.py +122 -55
  116. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
  117. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +1 -1
  118. imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
  119. imap_processing/tests/ialirt/unit/test_grouping.py +2 -2
  120. imap_processing/tests/ialirt/unit/test_parse_mag.py +71 -16
  121. imap_processing/tests/ialirt/unit/test_process_codicehi.py +3 -3
  122. imap_processing/tests/ialirt/unit/test_process_codicelo.py +3 -10
  123. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +4 -4
  124. imap_processing/tests/ialirt/unit/test_process_hit.py +3 -3
  125. imap_processing/tests/ialirt/unit/test_process_swapi.py +24 -16
  126. imap_processing/tests/ialirt/unit/test_process_swe.py +115 -7
  127. imap_processing/tests/idex/conftest.py +72 -7
  128. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20241206_v001.pkts +0 -0
  129. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20250108_v001.pkts +0 -0
  130. imap_processing/tests/idex/test_idex_l0.py +33 -11
  131. imap_processing/tests/idex/test_idex_l1a.py +50 -23
  132. imap_processing/tests/idex/test_idex_l1b.py +104 -25
  133. imap_processing/tests/idex/test_idex_l2a.py +48 -32
  134. imap_processing/tests/idex/test_idex_l2b.py +93 -0
  135. imap_processing/tests/lo/test_lo_l1a.py +3 -3
  136. imap_processing/tests/lo/test_lo_l1b.py +371 -6
  137. imap_processing/tests/lo/test_lo_l1c.py +1 -1
  138. imap_processing/tests/lo/test_lo_science.py +6 -7
  139. imap_processing/tests/lo/test_star_sensor.py +1 -1
  140. imap_processing/tests/mag/conftest.py +58 -9
  141. imap_processing/tests/mag/test_mag_decom.py +4 -3
  142. imap_processing/tests/mag/test_mag_l1a.py +13 -7
  143. imap_processing/tests/mag/test_mag_l1b.py +9 -9
  144. imap_processing/tests/mag/test_mag_l1c.py +151 -47
  145. imap_processing/tests/mag/test_mag_l2.py +130 -0
  146. imap_processing/tests/mag/test_mag_validation.py +144 -7
  147. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-in.csv +1217 -0
  148. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-out.csv +1857 -0
  149. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-in.csv +1217 -0
  150. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-out.csv +1857 -0
  151. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-in.csv +1217 -0
  152. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-out.csv +1793 -0
  153. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-in.csv +1217 -0
  154. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-out.csv +1793 -0
  155. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-burst-in.csv +2561 -0
  156. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-in.csv +961 -0
  157. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-out.csv +1539 -0
  158. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-in.csv +1921 -0
  159. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-out.csv +2499 -0
  160. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-in.csv +865 -0
  161. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-out.csv +1196 -0
  162. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-in.csv +1729 -0
  163. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-out.csv +3053 -0
  164. imap_processing/tests/mag/validation/L2/imap_mag_l1b_norm-mago_20251017_v002.cdf +0 -0
  165. imap_processing/tests/mag/validation/calibration/imap_mag_l2-calibration-matrices_20251017_v004.cdf +0 -0
  166. imap_processing/tests/mag/validation/calibration/imap_mag_l2-offsets-norm_20251017_20251017_v001.cdf +0 -0
  167. imap_processing/tests/spacecraft/test_quaternions.py +1 -1
  168. imap_processing/tests/spice/test_data/fake_repoint_data.csv +4 -4
  169. imap_processing/tests/spice/test_data/fake_spin_data.csv +11 -11
  170. imap_processing/tests/spice/test_geometry.py +3 -3
  171. imap_processing/tests/spice/test_kernels.py +1 -200
  172. imap_processing/tests/spice/test_pointing_frame.py +185 -0
  173. imap_processing/tests/spice/test_repoint.py +20 -10
  174. imap_processing/tests/spice/test_spin.py +50 -9
  175. imap_processing/tests/spice/test_time.py +14 -0
  176. imap_processing/tests/swapi/lut/imap_swapi_esa-unit-conversion_20250211_v000.csv +73 -0
  177. imap_processing/tests/swapi/lut/imap_swapi_lut-notes_20250211_v000.csv +1025 -0
  178. imap_processing/tests/swapi/test_swapi_l1.py +7 -9
  179. imap_processing/tests/swapi/test_swapi_l2.py +180 -8
  180. imap_processing/tests/swe/lut/checker-board-indices.csv +24 -0
  181. imap_processing/tests/swe/lut/imap_swe_esa-lut_20250301_v000.csv +385 -0
  182. imap_processing/tests/swe/lut/imap_swe_l1b-in-flight-cal_20240510_20260716_v000.csv +3 -0
  183. imap_processing/tests/swe/test_swe_l1a.py +6 -6
  184. imap_processing/tests/swe/test_swe_l1a_science.py +3 -3
  185. imap_processing/tests/swe/test_swe_l1b.py +162 -24
  186. imap_processing/tests/swe/test_swe_l2.py +82 -102
  187. imap_processing/tests/test_cli.py +171 -88
  188. imap_processing/tests/test_utils.py +2 -1
  189. imap_processing/tests/ultra/data/mock_data.py +49 -21
  190. imap_processing/tests/ultra/unit/conftest.py +53 -70
  191. imap_processing/tests/ultra/unit/test_badtimes.py +2 -4
  192. imap_processing/tests/ultra/unit/test_cullingmask.py +4 -6
  193. imap_processing/tests/ultra/unit/test_de.py +3 -10
  194. imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
  195. imap_processing/tests/ultra/unit/test_decom_apid_881.py +15 -16
  196. imap_processing/tests/ultra/unit/test_decom_apid_883.py +12 -10
  197. imap_processing/tests/ultra/unit/test_decom_apid_896.py +202 -55
  198. imap_processing/tests/ultra/unit/test_lookup_utils.py +23 -1
  199. imap_processing/tests/ultra/unit/test_spacecraft_pset.py +3 -4
  200. imap_processing/tests/ultra/unit/test_ultra_l1a.py +84 -307
  201. imap_processing/tests/ultra/unit/test_ultra_l1b.py +30 -12
  202. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
  203. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +4 -1
  204. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +163 -29
  205. imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
  206. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +32 -43
  207. imap_processing/tests/ultra/unit/test_ultra_l2.py +230 -0
  208. imap_processing/ultra/constants.py +1 -1
  209. imap_processing/ultra/l0/decom_tools.py +21 -34
  210. imap_processing/ultra/l0/decom_ultra.py +168 -204
  211. imap_processing/ultra/l0/ultra_utils.py +152 -136
  212. imap_processing/ultra/l1a/ultra_l1a.py +55 -243
  213. imap_processing/ultra/l1b/badtimes.py +1 -4
  214. imap_processing/ultra/l1b/cullingmask.py +2 -6
  215. imap_processing/ultra/l1b/de.py +62 -47
  216. imap_processing/ultra/l1b/extendedspin.py +8 -4
  217. imap_processing/ultra/l1b/lookup_utils.py +72 -9
  218. imap_processing/ultra/l1b/ultra_l1b.py +3 -8
  219. imap_processing/ultra/l1b/ultra_l1b_culling.py +4 -4
  220. imap_processing/ultra/l1b/ultra_l1b_extended.py +236 -78
  221. imap_processing/ultra/l1c/histogram.py +2 -6
  222. imap_processing/ultra/l1c/spacecraft_pset.py +2 -4
  223. imap_processing/ultra/l1c/ultra_l1c.py +1 -5
  224. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +107 -60
  225. imap_processing/ultra/l2/ultra_l2.py +299 -0
  226. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +526 -0
  227. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +526 -0
  228. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +526 -0
  229. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +526 -0
  230. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -2
  231. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -0
  232. imap_processing/ultra/packet_definitions/README.md +38 -0
  233. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +15302 -482
  234. imap_processing/ultra/utils/ultra_l1_utils.py +13 -12
  235. imap_processing/utils.py +1 -1
  236. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +3 -2
  237. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/RECORD +264 -225
  238. imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
  239. imap_processing/mag/imap_mag_sdc-configuration_v001.yaml +0 -6
  240. imap_processing/mag/l1b/__init__.py +0 -0
  241. imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
  242. imap_processing/swe/l1b/swe_l1b_science.py +0 -699
  243. imap_processing/tests/swe/test_swe_l1b_science.py +0 -103
  244. imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
  245. imap_processing/ultra/lookup_tables/ultra_90_dps_exposure_compressed.cdf +0 -0
  246. /imap_processing/idex/packet_definitions/{idex_packet_definition.xml → idex_science_packet_definition.xml} +0 -0
  247. /imap_processing/tests/ialirt/{test_data → data}/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  248. /imap_processing/tests/ialirt/{test_data → data}/l0/461971383-404.bin +0 -0
  249. /imap_processing/tests/ialirt/{test_data → data}/l0/461971384-405.bin +0 -0
  250. /imap_processing/tests/ialirt/{test_data → data}/l0/461971385-406.bin +0 -0
  251. /imap_processing/tests/ialirt/{test_data → data}/l0/461971386-407.bin +0 -0
  252. /imap_processing/tests/ialirt/{test_data → data}/l0/461971387-408.bin +0 -0
  253. /imap_processing/tests/ialirt/{test_data → data}/l0/461971388-409.bin +0 -0
  254. /imap_processing/tests/ialirt/{test_data → data}/l0/461971389-410.bin +0 -0
  255. /imap_processing/tests/ialirt/{test_data → data}/l0/461971390-411.bin +0 -0
  256. /imap_processing/tests/ialirt/{test_data → data}/l0/461971391-412.bin +0 -0
  257. /imap_processing/tests/ialirt/{test_data → data}/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  258. /imap_processing/tests/ialirt/{test_data → data}/l0/IALiRT Raw Packet Telemetry.txt +0 -0
  259. /imap_processing/tests/ialirt/{test_data → data}/l0/apid01152.tlm +0 -0
  260. /imap_processing/tests/ialirt/{test_data → data}/l0/eu_SWP_IAL_20240826_152033.csv +0 -0
  261. /imap_processing/tests/ialirt/{test_data → data}/l0/hi_fsw_view_1_ccsds.bin +0 -0
  262. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.ccsds +0 -0
  263. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.csv +0 -0
  264. /imap_processing/tests/ialirt/{test_data → data}/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +0 -0
  265. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  266. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  267. /imap_processing/tests/ialirt/{test_data → data}/l0/sample_decoded_i-alirt_data.csv +0 -0
  268. /imap_processing/tests/mag/validation/{imap_calibration_mag_20240229_v01.cdf → calibration/imap_mag_l1b-calibration_20240229_v001.cdf} +0 -0
  269. /imap_processing/{swe/l1b/engineering_unit_convert_table.csv → tests/swe/lut/imap_swe_eu-conversion_20240510_v000.csv} +0 -0
  270. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/LICENSE +0 -0
  271. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
  272. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
@@ -2,22 +2,26 @@
2
2
 
3
3
  import logging
4
4
  from pathlib import Path
5
- from typing import NamedTuple
6
5
 
7
6
  import numpy as np
8
7
  import pandas as pd
9
8
  import xarray as xr
10
9
 
11
10
  from imap_processing.hit.hit_utils import (
12
- add_energy_variables,
11
+ add_summed_particle_data_to_dataset,
13
12
  get_attribute_manager,
14
- initialize_particle_data_arrays,
15
- sum_particle_data,
16
13
  )
17
14
  from imap_processing.hit.l2.constants import (
15
+ FILLVAL_FLOAT32,
16
+ L2_SECTORED_ANCILLARY_PATH_PREFIX,
18
17
  L2_STANDARD_ANCILLARY_PATH_PREFIX,
19
18
  L2_SUMMED_ANCILLARY_PATH_PREFIX,
19
+ N_AZIMUTH,
20
+ SECONDS_PER_10_MIN,
21
+ SECONDS_PER_MIN,
20
22
  STANDARD_PARTICLE_ENERGY_RANGE_MAPPING,
23
+ VALID_SECTORED_SPECIES,
24
+ VALID_SPECIES,
21
25
  )
22
26
 
23
27
  logger = logging.getLogger(__name__)
@@ -27,7 +31,7 @@ logger = logging.getLogger(__name__)
27
31
  # - determine where to pull ancillary data. Storing it locally for now
28
32
 
29
33
 
30
- def hit_l2(dependency: xr.Dataset, data_version: str) -> list[xr.Dataset]:
34
+ def hit_l2(dependency: xr.Dataset) -> list[xr.Dataset]:
31
35
  """
32
36
  Will process HIT data to L2.
33
37
 
@@ -38,8 +42,6 @@ def hit_l2(dependency: xr.Dataset, data_version: str) -> list[xr.Dataset]:
38
42
  dependency : xr.Dataset
39
43
  L1B xarray science dataset that is either summed rates
40
44
  standard rates or sector rates.
41
- data_version : str
42
- Version of the data product being created.
43
45
 
44
46
  Returns
45
47
  -------
@@ -47,11 +49,9 @@ def hit_l2(dependency: xr.Dataset, data_version: str) -> list[xr.Dataset]:
47
49
  List of one L2 dataset.
48
50
  """
49
51
  logger.info("Creating HIT L2 science datasets")
50
- # Create the attribute manager for this data level
51
- attr_mgr = get_attribute_manager(data_version, "l2")
52
52
 
53
- # TODO: Write functions to process sectored rates dataset
54
- # with logical source: "imap_hit_l2_macropixel-intensity"
53
+ # Create the attribute manager for this data level
54
+ attr_mgr = get_attribute_manager("l2")
55
55
 
56
56
  l2_datasets: dict = {}
57
57
 
@@ -68,6 +68,12 @@ def hit_l2(dependency: xr.Dataset, data_version: str) -> list[xr.Dataset]:
68
68
  )
69
69
  logger.info("HIT L2 standard intensity dataset created")
70
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
+
71
77
  # Update attributes and dimensions
72
78
  for logical_source, dataset in l2_datasets.items():
73
79
  dataset.attrs = attr_mgr.get_global_attributes(logical_source)
@@ -102,149 +108,241 @@ def hit_l2(dependency: xr.Dataset, data_version: str) -> list[xr.Dataset]:
102
108
  return list(l2_datasets.values())
103
109
 
104
110
 
105
- class IntensityFactors(NamedTuple):
106
- """A namedtuple to store factors for the intensity equation."""
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)
107
150
 
108
- delta_e_factor: np.ndarray
109
- geometry_factor: np.ndarray
110
- efficiency: np.ndarray
111
- b: np.ndarray
151
+ return intensity
112
152
 
113
153
 
114
- def get_intensity_factors(
115
- energy_min: np.ndarray, species_ancillary_data: pd.DataFrame
116
- ) -> IntensityFactors:
154
+ def reshape_for_sectored(arr: np.ndarray) -> np.ndarray:
117
155
  """
118
- Get the intensity factors for all energy bins of the given species ancillary data.
156
+ Reshape the ancillary data for sectored rates.
119
157
 
120
- This function gets the factors needed for the equation to convert rates to
121
- intensities for all energy bins for the given species.
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.
122
163
 
123
164
  Parameters
124
165
  ----------
125
- energy_min : np.ndarray
126
- All energy min values for the species.
127
- species_ancillary_data : pd.DataFrame
128
- The subset of ancillary data for the given species.
166
+ arr : np.ndarray
167
+ The ancillary data array to reshape.
129
168
 
130
169
  Returns
131
170
  -------
132
- IntensityFactors
133
- The factors needed to convert rates to intensities for all energy bins
134
- for the given species.
171
+ np.ndarray
172
+ The reshaped array.
135
173
  """
136
- # Get factors needed to convert rates to intensities for
137
- # all energy bins for the given species ancillary data
138
- intensity_factors = species_ancillary_data.set_index(
139
- species_ancillary_data["lower energy (mev)"].astype(np.float32)
140
- ).loc[energy_min]
141
-
142
- return IntensityFactors(
143
- delta_e_factor=intensity_factors["delta e (mev)"].values,
144
- geometry_factor=intensity_factors["geometry factor (cm2 sr)"].values,
145
- efficiency=intensity_factors["efficiency"].values,
146
- b=intensity_factors["b"].values,
174
+ return np.repeat(
175
+ arr.reshape((arr.shape[0], arr.shape[1], arr.shape[2]))[:, :, np.newaxis, :],
176
+ N_AZIMUTH,
177
+ axis=2,
147
178
  )
148
179
 
149
180
 
150
- def calculate_intensities(
151
- rate: xr.DataArray,
152
- delta_e_factor: np.ndarray,
153
- geometry_factor: np.ndarray,
154
- efficiency: np.ndarray,
181
+ def build_ancillary_dataset(
182
+ delta_e: np.ndarray,
183
+ geometry_factors: np.ndarray,
184
+ efficiencies: np.ndarray,
155
185
  b: np.ndarray,
156
- ) -> xr.DataArray:
186
+ species_array: xr.DataArray,
187
+ ) -> xr.Dataset:
157
188
  """
158
- Calculate the intensities for given arrays of rates and ancillary factors.
159
-
160
- Uses vectorization to calculate the intensities for an array of rates
161
- at an epoch.
189
+ Build a xarray Dataset containing ancillary data for calculating intensity.
162
190
 
163
- This function uses equation 9 and 11 from the HIT algorithm document:
164
- (Summed L1B Rates) / (60 * Delta E * Geometry Factor * Efficiency) - b
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.
165
194
 
166
195
  Parameters
167
196
  ----------
168
- rate : xr.DataArray
169
- The L1B rates to be converted to intensities for an epoch.
170
- delta_e_factor : np.ndarray
171
- The energy bin width factors for an epoch.
172
- geometry_factor : np.ndarray
173
- The geometry factors for an epoch.
174
- efficiency : np.ndarray
175
- The efficiency factors for an epoch.
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.
176
203
  b : np.ndarray
177
- The b factors for an epoch.
204
+ Background intensity values.
205
+ species_array : xr.Dataset
206
+ Data array for the species to extract coordinates from.
178
207
 
179
208
  Returns
180
209
  -------
181
- xr.DataArray
182
- The calculated intensities for an epoch.
210
+ ancillary_ds : xr.Dataset
211
+ A dataset containing all ancillary data variables and coordinates that
212
+ align with the L2 dataset.
183
213
  """
184
- # Calculate the intensities for this energy bin using vectorization
185
- return (rate / (60 * delta_e_factor * geometry_factor * efficiency)) - b
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)
186
241
 
187
242
 
188
243
  def calculate_intensities_for_a_species(
189
244
  species_variable: str, l2_dataset: xr.Dataset, ancillary_data_frames: dict
190
- ) -> None:
245
+ ) -> xr.Dataset:
191
246
  """
192
247
  Calculate the intensity for a given species in the dataset.
193
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
+
194
259
  Parameters
195
260
  ----------
196
261
  species_variable : str
197
- The species variable to calculate the intensity for which is either the species
198
- or a statistical uncertainty. (i.e. "h", "h_delta_minus", or "h_delta_plus").
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").
199
265
  l2_dataset : xr.Dataset
200
- The L2 dataset containing the summed L1B rates to calculate the intensity.
266
+ The L2 dataset containing the L1B rates needed to calculate the intensity.
201
267
  ancillary_data_frames : dict
202
268
  Dictionary containing ancillary data for each dynamic threshold state where
203
269
  the key is the dynamic threshold state and the value is a pandas DataFrame
204
- containing the ancillary data.
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.
205
276
  """
206
- species = (
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 = (
207
281
  species_variable.split("_")[0]
208
- if "_delta_" in species_variable
282
+ if "_uncert_" in species_variable
209
283
  else species_variable
210
284
  )
211
- energy_min = (
212
- l2_dataset[f"{species}_energy_mean"].values
213
- - l2_dataset[f"{species}_energy_delta_minus"].values
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]
214
313
  )
215
- # TODO: Add check for energy max after ancillary file is updated
216
- # to fix errors
217
-
218
- # Calculate the intensity for each epoch and energy bin since the
219
- # dynamic threshold state can vary by epoch and that determines the
220
- # ancillary data to use.
221
- for epoch in range(l2_dataset[species_variable].shape[0]):
222
- # Get ancillary data using the dynamic threshold state for this epoch
223
- species_ancillary_data = get_species_ancillary_data(
224
- int(l2_dataset["dynamic_threshold_state"][epoch].values),
225
- ancillary_data_frames,
226
- species,
227
- )
228
314
 
229
- # Calculate the intensity for this energy bin using vectorization
230
- # and replace rates with intensities in the dataset
231
- factors: IntensityFactors = get_intensity_factors(
232
- energy_min, species_ancillary_data
233
- )
234
- rates: xr.DataArray = l2_dataset[species_variable][epoch]
235
-
236
- l2_dataset[species_variable][epoch] = calculate_intensities(
237
- rates,
238
- factors.delta_e_factor,
239
- factors.geometry_factor,
240
- factors.efficiency,
241
- factors.b,
242
- )
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
243
341
 
244
342
 
245
343
  def calculate_intensities_for_all_species(
246
- l2_dataset: xr.Dataset, ancillary_data_frames: dict
247
- ) -> None:
344
+ l2_dataset: xr.Dataset, ancillary_data_frames: dict, valid_data_variables: list
345
+ ) -> xr.Dataset:
248
346
  """
249
347
  Calculate the intensity for each species in the dataset.
250
348
 
@@ -256,39 +354,28 @@ def calculate_intensities_for_all_species(
256
354
  Dictionary containing ancillary data for each dynamic threshold state
257
355
  where the key is the dynamic threshold state and the value is a pandas
258
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.
259
364
  """
260
- # TODO: update to also calculate intensity for sectorates?
261
- # List of valid species data variables to calculate intensity for
262
- valid_data_variables = [
263
- "h",
264
- "he3",
265
- "he4",
266
- "he",
267
- "c",
268
- "n",
269
- "o",
270
- "ne",
271
- "na",
272
- "mg",
273
- "al",
274
- "si",
275
- "s",
276
- "ar",
277
- "ca",
278
- "fe",
279
- "ni",
280
- ]
365
+ updated_ds = l2_dataset.copy()
281
366
 
282
367
  # Add statistical uncertainty variables to the list of valid variables
283
- valid_data_variables += [f"{var}_delta_minus" for var in valid_data_variables] + [
284
- f"{var}_delta_plus" for var in valid_data_variables
285
- ]
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
+ )
286
373
 
287
374
  # Calculate the intensity for each valid data variable
288
- for species_variable in valid_data_variables:
289
- if species_variable in l2_dataset.data_vars:
290
- calculate_intensities_for_a_species(
291
- species_variable, l2_dataset, ancillary_data_frames
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
292
379
  )
293
380
  else:
294
381
  logger.warning(
@@ -296,10 +383,10 @@ def calculate_intensities_for_all_species(
296
383
  f"Skipping intensity calculation."
297
384
  )
298
385
 
386
+ return updated_ds
299
387
 
300
- def add_systematic_uncertainties(
301
- dataset: xr.Dataset, particle: str, energy_bins: int
302
- ) -> None:
388
+
389
+ def add_systematic_uncertainties(dataset: xr.Dataset, particle: str) -> xr.Dataset:
303
390
  """
304
391
  Add systematic uncertainties to the dataset.
305
392
 
@@ -309,94 +396,83 @@ def add_systematic_uncertainties(
309
396
  Parameters
310
397
  ----------
311
398
  dataset : xr.Dataset
312
- The dataset to add the systematic uncertainties to.
399
+ The dataset to add the systematic uncertainties to
400
+ which contain the particle data variables.
313
401
  particle : str
314
402
  The particle name.
315
- energy_bins : int
316
- Number of energy bins for the particle.
403
+
404
+ Returns
405
+ -------
406
+ updated_ds : xr.Dataset
407
+ The dataset with the systematic uncertainties added.
317
408
  """
318
- dataset[f"{particle}_sys_delta_minus"] = xr.DataArray(
319
- data=np.zeros(energy_bins, dtype=np.float32),
320
- dims=[f"{particle}_energy_mean"],
321
- name=f"{particle}_sys_delta_minus",
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",
322
415
  )
323
- dataset[f"{particle}_sys_delta_plus"] = xr.DataArray(
324
- data=np.zeros(energy_bins, dtype=np.float32),
325
- dims=[f"{particle}_energy_mean"],
326
- name=f"{particle}_sys_delta_plus",
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",
327
420
  )
328
421
 
422
+ return updated_ds
329
423
 
330
- def add_standard_particle_rates_to_dataset(
331
- l2_standard_intensity_dataset: xr.Dataset,
332
- l1b_standard_rates_dataset: xr.Dataset,
333
- particle: str,
334
- energy_ranges: list,
335
- ) -> None:
424
+
425
+ def add_total_uncertainties(dataset: xr.Dataset, particle: str) -> xr.Dataset:
336
426
  """
337
- Add summed standard particle rates to the dataset.
427
+ Add total uncertainties to the dataset.
338
428
 
339
- This function performs the following steps:
340
- 1) sum the standard rates, including statistical uncertainties,
341
- from the l2fgrates, l3fgrates, and penfgrates data variables in the L1B
342
- standard rates data.
343
- 2) add the summed rates to the L2 standard intensity dataset by particle type
344
- and energy range.
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.
345
434
 
346
435
  Parameters
347
436
  ----------
348
- l2_standard_intensity_dataset : xr.Dataset
349
- The L2 standard intensity dataset to add the rates to.
350
- l1b_standard_rates_dataset : xr.Dataset
351
- The L1B standard rates dataset containing rates to sum.
437
+ dataset : xr.Dataset
438
+ The dataset to add the total uncertainties to.
352
439
  particle : str
353
440
  The particle name.
354
- energy_ranges : list
355
- A list of energy range dictionaries for the particle.
356
- For example:
357
- {'energy_min': 1.8, 'energy_max': 2.2, "R2": [1], "R3": [], "R4": []}.
358
- """
359
- # Initialize arrays to store summed rates and statistical uncertainties
360
- l2_standard_intensity_dataset = initialize_particle_data_arrays(
361
- l2_standard_intensity_dataset,
362
- particle,
363
- len(energy_ranges),
364
- l1b_standard_rates_dataset.sizes["epoch"],
365
- )
366
-
367
- # initialize arrays to store energy min and max values
368
- energy_min = np.zeros(len(energy_ranges), dtype=np.float32)
369
- energy_max = np.zeros(len(energy_ranges), dtype=np.float32)
370
441
 
371
- # Sum particle rates and statistical uncertainties for each energy range
372
- # and add them to the dataset
373
- for i, energy_range_dict in enumerate(energy_ranges):
374
- summed_rates, summed_rates_delta_minus, summed_rates_delta_plus = (
375
- sum_particle_data(l1b_standard_rates_dataset, energy_range_dict)
376
- )
377
-
378
- l2_standard_intensity_dataset[f"{particle}"][:, i] = summed_rates.astype(
379
- np.float32
380
- )
381
- l2_standard_intensity_dataset[f"{particle}_delta_minus"][:, i] = (
382
- summed_rates_delta_minus.astype(np.float32)
383
- )
384
- l2_standard_intensity_dataset[f"{particle}_delta_plus"][:, i] = (
385
- summed_rates_delta_plus.astype(np.float32)
386
- )
442
+ Returns
443
+ -------
444
+ updated_ds : xr.Dataset
445
+ The dataset with the total uncertainties added.
446
+ """
447
+ updated_ds = dataset.copy()
387
448
 
388
- # Fill energy min and max values for each energy range
389
- energy_min[i] = energy_range_dict["energy_min"]
390
- energy_max[i] = energy_range_dict["energy_max"]
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
+ )
391
458
 
392
- l2_standard_intensity_dataset = add_energy_variables(
393
- l2_standard_intensity_dataset, particle, energy_min, energy_max
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",
394
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
395
471
 
396
472
 
397
473
  def get_species_ancillary_data(
398
474
  dynamic_threshold_state: int, ancillary_data_frames: dict, species: str
399
- ) -> pd.DataFrame:
475
+ ) -> dict:
400
476
  """
401
477
  Get the ancillary data for a given species and dynamic threshold state.
402
478
 
@@ -413,14 +489,25 @@ def get_species_ancillary_data(
413
489
 
414
490
  Returns
415
491
  -------
416
- pd.DataFrame
492
+ dict
417
493
  The ancillary data for the species and dynamic threshold state.
418
494
  """
419
- ancillary_data = ancillary_data_frames[dynamic_threshold_state]
420
-
421
- # Get the ancillary data for the species
422
- species_ancillary_data = ancillary_data[ancillary_data["species"] == species]
423
- return species_ancillary_data
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
+ }
424
511
 
425
512
 
426
513
  def load_ancillary_data(dynamic_threshold_states: set, path_prefix: Path) -> dict:
@@ -488,51 +575,21 @@ def process_summed_intensity_data(l1b_summed_rates_dataset: xr.Dataset) -> xr.Da
488
575
  L2_SUMMED_ANCILLARY_PATH_PREFIX,
489
576
  )
490
577
 
491
- # Add systematic uncertainties and energy variables to the dataset
492
- for var in l2_summed_intensity_dataset.data_vars:
493
- if "_" not in var:
494
- particle = str(var)
495
- # Add systematic uncertainties to the dataset. These will not have the
496
- # intensity calculation applied to them and values will be zeros
497
- add_systematic_uncertainties(
498
- l2_summed_intensity_dataset,
499
- particle,
500
- l2_summed_intensity_dataset[var].shape[1],
501
- )
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
+ )
502
582
 
503
- # TODO: remove this code after L1B is updated to have energy mean and deltas
504
- # instead of energy index, min, and max
505
- # Add energy variables to the dataset (energy mean and deltas)
506
- l2_summed_intensity_dataset = add_energy_variables(
507
- l2_summed_intensity_dataset,
508
- particle,
509
- l2_summed_intensity_dataset[f"{particle}_energy_min"].values,
510
- l2_summed_intensity_dataset[f"{particle}_energy_max"].values,
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
511
588
  )
512
-
513
- # Replace energy index with energy mean as a coordinate
514
- l2_summed_intensity_dataset = l2_summed_intensity_dataset.assign_coords(
515
- {
516
- f"{particle}_energy_mean": (
517
- f"{particle}_energy_index",
518
- l2_summed_intensity_dataset[f"{particle}_energy_mean"].values,
519
- )
520
- }
521
- ).swap_dims({f"{particle}_energy_index": f"{particle}_energy_mean"})
522
-
523
- # Drop energy min, max, and index variables
524
- l2_summed_intensity_dataset = l2_summed_intensity_dataset.drop_vars(
525
- [
526
- f"{particle}_energy_min",
527
- f"{particle}_energy_max",
528
- f"{particle}_energy_index",
529
- ]
589
+ l2_summed_intensity_dataset = add_total_uncertainties(
590
+ l2_summed_intensity_dataset, var
530
591
  )
531
592
 
532
- calculate_intensities_for_all_species(
533
- l2_summed_intensity_dataset, ancillary_data_frames
534
- )
535
-
536
593
  return l2_summed_intensity_dataset
537
594
 
538
595
 
@@ -554,9 +611,9 @@ def process_standard_intensity_data(
554
611
  STANDARD_PARTICLE_ENERGY_RANGE_MAPPING dictionary are included in this
555
612
  product.
556
613
 
557
- Intensity is then calculated from the summed rates using the following equation:
614
+ Intensity is then calculated from the summed standard rates:
558
615
 
559
- Equation 10 from the HIT algorithm document:
616
+ Equation 9 from the HIT algorithm document:
560
617
  Standard Intensity = (Summed L1B Standard Rates) /
561
618
  (60 * Delta E * Geometry Factor * Efficiency) - b
562
619
 
@@ -582,9 +639,6 @@ def process_standard_intensity_data(
582
639
  l2_standard_intensity_dataset["dynamic_threshold_state"] = (
583
640
  l1b_standard_rates_dataset["dynamic_threshold_state"]
584
641
  )
585
- l2_standard_intensity_dataset[
586
- "dynamic_threshold_state"
587
- ].attrs = l1b_standard_rates_dataset["dynamic_threshold_state"].attrs
588
642
 
589
643
  # Load ancillary data for each dynamic threshold state into a dictionary
590
644
  ancillary_data_frames = load_ancillary_data(
@@ -592,23 +646,78 @@ def process_standard_intensity_data(
592
646
  L2_STANDARD_ANCILLARY_PATH_PREFIX,
593
647
  )
594
648
 
595
- # Process each particle type and energy range and add rates and uncertainties
596
- # to the dataset
649
+ # Process each particle type and add rates and uncertainties to the dataset
597
650
  for particle, energy_ranges in STANDARD_PARTICLE_ENERGY_RANGE_MAPPING.items():
598
- # Add systematic uncertainties to the dataset. These will not have the intensity
599
- # calculation applied to them and values will be zeros
600
- add_systematic_uncertainties(
601
- l2_standard_intensity_dataset, particle, len(energy_ranges)
602
- )
603
651
  # Add standard particle rates and statistical uncertainties to the dataset
604
- add_standard_particle_rates_to_dataset(
652
+ l2_standard_intensity_dataset = add_summed_particle_data_to_dataset(
605
653
  l2_standard_intensity_dataset,
606
654
  l1b_standard_rates_dataset,
607
655
  particle,
608
656
  energy_ranges,
609
657
  )
610
- calculate_intensities_for_all_species(
611
- l2_standard_intensity_dataset, ancillary_data_frames
658
+
659
+ l2_standard_intensity_dataset = calculate_intensities_for_all_species(
660
+ l2_standard_intensity_dataset, ancillary_data_frames, VALID_SPECIES
612
661
  )
613
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
+
614
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