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
@@ -3,17 +3,20 @@
3
3
  # TODO: Come back and add in FSW logic.
4
4
  import logging
5
5
  from enum import Enum
6
- from typing import ClassVar
7
6
 
8
7
  import numpy as np
8
+ import pandas
9
9
  import xarray
10
10
  from numpy import ndarray
11
11
  from numpy.typing import NDArray
12
+ from scipy.interpolate import LinearNDInterpolator, RegularGridInterpolator
12
13
 
13
14
  from imap_processing.spice.spin import get_spin_data
14
15
  from imap_processing.ultra.constants import UltraConstants
15
16
  from imap_processing.ultra.l1b.lookup_utils import (
17
+ get_angular_profiles,
16
18
  get_back_position,
19
+ get_energy_efficiencies,
17
20
  get_energy_norm,
18
21
  get_image_params,
19
22
  get_norm,
@@ -35,8 +38,8 @@ class StopType(Enum):
35
38
 
36
39
  Top = 1
37
40
  Bottom = 2
38
- PH: ClassVar[list[int]] = [1, 2]
39
- SSD: ClassVar[list[int]] = [8, 9, 10, 11, 12, 13, 14, 15]
41
+ PH = [1, 2] # noqa RUF012 mutable class attribute
42
+ SSD = [8, 9, 10, 11, 12, 13, 14, 15] # noqa RUF012 mutable class attribute
40
43
 
41
44
 
42
45
  class CoinType(Enum):
@@ -46,14 +49,16 @@ class CoinType(Enum):
46
49
  Bottom = 2
47
50
 
48
51
 
49
- def get_front_x_position(start_type: ndarray, start_position_tdc: ndarray) -> ndarray:
52
+ def get_front_x_position(
53
+ start_type: ndarray, start_position_tdc: ndarray, sensor: str
54
+ ) -> ndarray:
50
55
  """
51
56
  Calculate the front xf position.
52
57
 
53
58
  Converts Start Position Time to Digital Converter (TDC)
54
59
  values into units of hundredths of a millimeter using a scale factor and offsets.
55
60
  Further description is available on pages 30 of
56
- IMAP-Ultra Flight Software Specification document (7523-9009_Rev_-.pdf).
61
+ IMAP-Ultra Flight Software Specification document.
57
62
 
58
63
  Parameters
59
64
  ----------
@@ -61,6 +66,8 @@ def get_front_x_position(start_type: ndarray, start_position_tdc: ndarray) -> nd
61
66
  Start Type: 1=Left, 2=Right.
62
67
  start_position_tdc : ndarray
63
68
  Start Position Time to Digital Converter (TDC).
69
+ sensor : str
70
+ Sensor name.
64
71
 
65
72
  Returns
66
73
  -------
@@ -70,9 +77,9 @@ def get_front_x_position(start_type: ndarray, start_position_tdc: ndarray) -> nd
70
77
  # Left and right start types.
71
78
  indices = np.nonzero((start_type == 1) | (start_type == 2))
72
79
 
73
- xftsc = get_image_params("XFTSC")
74
- xft_lt_off = get_image_params("XFTLTOFF")
75
- xft_rt_off = get_image_params("XFTRTOFF")
80
+ xftsc = get_image_params("XFTSC", sensor)
81
+ xft_lt_off = get_image_params("XFTLTOFF", sensor)
82
+ xft_rt_off = get_image_params("XFTRTOFF", sensor)
76
83
  xft_off = np.where(start_type[indices] == 1, xft_lt_off, xft_rt_off)
77
84
 
78
85
  # Calculate xf and convert to hundredths of a millimeter
@@ -158,8 +165,7 @@ def get_ph_tof_and_back_positions(
158
165
  The Time Of Flight (tof) and the position of the particle at the
159
166
  back of the sensor are measured using the timing of the pulses.
160
167
  Further description is available on pages 32-33 of
161
- IMAP-Ultra Flight Software Specification document
162
- (7523-9009_Rev_-.pdf).
168
+ IMAP-Ultra Flight Software Specification document.
163
169
 
164
170
  Parameters
165
171
  ----------
@@ -183,7 +189,7 @@ def get_ph_tof_and_back_positions(
183
189
  Back positions in y direction (hundredths of a millimeter).
184
190
  """
185
191
  indices = np.nonzero(
186
- np.isin(de_dataset["STOP_TYPE"], [StopType.Top.value, StopType.Bottom.value])
192
+ np.isin(de_dataset["stop_type"], [StopType.Top.value, StopType.Bottom.value])
187
193
  )[0]
188
194
  de_filtered = de_dataset.isel(epoch=indices)
189
195
 
@@ -191,10 +197,10 @@ def get_ph_tof_and_back_positions(
191
197
 
192
198
  # There are mismatches between the stop TDCs, i.e., SpN, SpS, SpE, and SpW.
193
199
  # This normalizes the TDCs
194
- sp_n_norm = get_norm(de_filtered["STOP_NORTH_TDC"].data, "SpN", sensor)
195
- sp_s_norm = get_norm(de_filtered["STOP_SOUTH_TDC"].data, "SpS", sensor)
196
- sp_e_norm = get_norm(de_filtered["STOP_EAST_TDC"].data, "SpE", sensor)
197
- sp_w_norm = get_norm(de_filtered["STOP_WEST_TDC"].data, "SpW", sensor)
200
+ sp_n_norm = get_norm(de_filtered["stop_north_tdc"].data, "SpN", sensor)
201
+ sp_s_norm = get_norm(de_filtered["stop_south_tdc"].data, "SpS", sensor)
202
+ sp_e_norm = get_norm(de_filtered["stop_east_tdc"].data, "SpE", sensor)
203
+ sp_w_norm = get_norm(de_filtered["stop_west_tdc"].data, "SpW", sensor)
198
204
 
199
205
  # Convert normalized TDC values into units of hundredths of a
200
206
  # millimeter using lookup tables.
@@ -220,20 +226,20 @@ def get_ph_tof_and_back_positions(
220
226
  # Stop Type: 1=Top, 2=Bottom
221
227
  # Convert converts normalized TDC values into units of
222
228
  # hundredths of a millimeter using lookup tables.
223
- stop_type_top = de_filtered["STOP_TYPE"].data == StopType.Top.value
229
+ stop_type_top = de_filtered["stop_type"].data == StopType.Top.value
224
230
  xb[stop_type_top] = get_back_position(xb_index[stop_type_top], "XBkTp", sensor)
225
231
  yb[stop_type_top] = get_back_position(yb_index[stop_type_top], "YBkTp", sensor)
226
232
 
227
233
  # Correction for the propagation delay of the start anode and other effects.
228
- t2[stop_type_top] = get_image_params("TOFSC") * t1[
234
+ t2[stop_type_top] = get_image_params("TOFSC", sensor) * t1[
229
235
  stop_type_top
230
- ] + get_image_params("TOFTPOFF")
236
+ ] + get_image_params("TOFTPOFF", sensor)
231
237
  # Variable xf_ph divided by 10 to convert to mm.
232
238
  tof[stop_type_top] = t2[stop_type_top] + xf_ph[
233
239
  stop_type_top
234
- ] / 10 * get_image_params("XFTTOF")
240
+ ] / 10 * get_image_params("XFTTOF", sensor)
235
241
 
236
- stop_type_bottom = de_filtered["STOP_TYPE"].data == StopType.Bottom.value
242
+ stop_type_bottom = de_filtered["stop_type"].data == StopType.Bottom.value
237
243
  xb[stop_type_bottom] = get_back_position(
238
244
  xb_index[stop_type_bottom], "XBkBt", sensor
239
245
  )
@@ -242,14 +248,14 @@ def get_ph_tof_and_back_positions(
242
248
  )
243
249
 
244
250
  # Correction for the propagation delay of the start anode and other effects.
245
- t2[stop_type_bottom] = get_image_params("TOFSC") * t1[
251
+ t2[stop_type_bottom] = get_image_params("TOFSC", sensor) * t1[
246
252
  stop_type_bottom
247
- ] + get_image_params("TOFBTOFF") # 10*ns
253
+ ] + get_image_params("TOFBTOFF", sensor) # 10*ns
248
254
 
249
255
  # Variable xf_ph divided by 10 to convert to mm.
250
256
  tof[stop_type_bottom] = t2[stop_type_bottom] + xf_ph[
251
257
  stop_type_bottom
252
- ] / 10 * get_image_params("XFTTOF")
258
+ ] / 10 * get_image_params("XFTTOF", sensor)
253
259
 
254
260
  return tof, t2, xb, yb
255
261
 
@@ -284,7 +290,7 @@ def get_path_length(
284
290
 
285
291
 
286
292
  def get_ssd_back_position_and_tof_offset(
287
- de_dataset: xarray.Dataset,
293
+ de_dataset: xarray.Dataset, sensor: str
288
294
  ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
289
295
  """
290
296
  Lookup the Y SSD positions (yb), TOF Offset, and SSD number.
@@ -293,6 +299,8 @@ def get_ssd_back_position_and_tof_offset(
293
299
  ----------
294
300
  de_dataset : xarray.Dataset
295
301
  The input dataset containing STOP_TYPE and SSD_FLAG data.
302
+ sensor : str
303
+ Sensor name.
296
304
 
297
305
  Returns
298
306
  -------
@@ -307,7 +315,7 @@ def get_ssd_back_position_and_tof_offset(
307
315
  -----
308
316
  The X back position (xb) is assumed to be 0 for SSD.
309
317
  """
310
- indices = np.nonzero(np.isin(de_dataset["STOP_TYPE"], StopType.SSD.value))[0]
318
+ indices = np.nonzero(np.isin(de_dataset["stop_type"], StopType.SSD.value))[0]
311
319
  de_filtered = de_dataset.isel(epoch=indices)
312
320
 
313
321
  yb = np.zeros(len(indices), dtype=np.float64)
@@ -315,18 +323,18 @@ def get_ssd_back_position_and_tof_offset(
315
323
  tof_offset = np.zeros(len(indices), dtype=np.float64)
316
324
 
317
325
  for i in range(8):
318
- ssd_flag_mask = de_filtered[f"SSD_FLAG_{i}"].data == 1
326
+ ssd_flag_mask = de_filtered[f"ssd_flag_{i}"].data == 1
319
327
 
320
328
  # Multiply ybs times 100 to convert to hundredths of a millimeter.
321
- yb[ssd_flag_mask] = get_image_params(f"YBKSSD{i}") * 100
329
+ yb[ssd_flag_mask] = get_image_params(f"YBKSSD{i}", sensor) * 100
322
330
  ssd_number[ssd_flag_mask] = i
323
331
 
324
332
  tof_offset[
325
- (de_filtered["START_TYPE"] == StartType.Left.value) & ssd_flag_mask
326
- ] = get_image_params(f"TOFSSDLTOFF{i}")
333
+ (de_filtered["start_type"] == StartType.Left.value) & ssd_flag_mask
334
+ ] = get_image_params(f"TOFSSDLTOFF{i}", sensor)
327
335
  tof_offset[
328
- (de_filtered["START_TYPE"] == StartType.Right.value) & ssd_flag_mask
329
- ] = get_image_params(f"TOFSSDRTOFF{i}")
336
+ (de_filtered["start_type"] == StartType.Right.value) & ssd_flag_mask
337
+ ] = get_image_params(f"TOFSSDRTOFF{i}", sensor)
330
338
 
331
339
  return yb, tof_offset, ssd_number
332
340
 
@@ -357,17 +365,17 @@ def calculate_etof_xc(
357
365
  X coincidence position (millimeters).
358
366
  """
359
367
  # CoinNNorm
360
- coin_n_norm = get_norm(de_subset["COIN_NORTH_TDC"], "CoinN", sensor)
368
+ coin_n_norm = get_norm(de_subset["coin_north_tdc"], "CoinN", sensor)
361
369
  # CoinSNorm
362
- coin_s_norm = get_norm(de_subset["COIN_SOUTH_TDC"], "CoinS", sensor)
363
- xc = get_image_params(f"XCOIN{location}SC") * (
370
+ coin_s_norm = get_norm(de_subset["coin_south_tdc"], "CoinS", sensor)
371
+ xc = get_image_params(f"XCOIN{location}SC", sensor) * (
364
372
  coin_s_norm - coin_n_norm
365
- ) + get_image_params(f"XCOIN{location}OFF") # millimeter
373
+ ) + get_image_params(f"XCOIN{location}OFF", sensor) # millimeter
366
374
 
367
375
  # Time for the electrons to travel back to coincidence anode.
368
- t2 = get_image_params("ETOFSC") * (coin_n_norm + coin_s_norm) + get_image_params(
369
- f"ETOF{location}OFF"
370
- )
376
+ t2 = get_image_params("ETOFSC", sensor) * (
377
+ coin_n_norm + coin_s_norm
378
+ ) + get_image_params(f"ETOF{location}OFF", sensor)
371
379
 
372
380
  # Multiply by 10 to convert to tenths of a nanosecond.
373
381
  etof = t2 * 10 - particle_tof
@@ -389,8 +397,7 @@ def get_coincidence_positions(
389
397
  back to the coincidence anode.
390
398
 
391
399
  Further description is available on pages 34-35 of
392
- IMAP-Ultra Flight Software Specification document
393
- (7523-9009_Rev_-.pdf).
400
+ IMAP-Ultra Flight Software Specification document.
394
401
 
395
402
  Parameters
396
403
  ----------
@@ -410,16 +417,16 @@ def get_coincidence_positions(
410
417
  xc : np.ndarray
411
418
  X coincidence position (hundredths of a millimeter).
412
419
  """
413
- index_top = np.nonzero(np.isin(de_dataset["COIN_TYPE"], CoinType.Top.value))[0]
420
+ index_top = np.nonzero(np.isin(de_dataset["coin_type"], CoinType.Top.value))[0]
414
421
  de_top = de_dataset.isel(epoch=index_top)
415
422
 
416
- index_bottom = np.nonzero(np.isin(de_dataset["COIN_TYPE"], CoinType.Bottom.value))[
423
+ index_bottom = np.nonzero(np.isin(de_dataset["coin_type"], CoinType.Bottom.value))[
417
424
  0
418
425
  ]
419
426
  de_bottom = de_dataset.isel(epoch=index_bottom)
420
427
 
421
- etof = np.zeros(len(de_dataset["COIN_TYPE"]), dtype=np.float64)
422
- xc_array = np.zeros(len(de_dataset["COIN_TYPE"]), dtype=np.float64)
428
+ etof = np.zeros(len(de_dataset["coin_type"]), dtype=np.float64)
429
+ xc_array = np.zeros(len(de_dataset["coin_type"]), dtype=np.float64)
423
430
 
424
431
  # Normalized TDCs
425
432
  # For the stop anode, there are mismatches between the coincidence TDCs,
@@ -443,7 +450,7 @@ def get_de_velocity(
443
450
  back_position: tuple[NDArray, NDArray],
444
451
  d: np.ndarray,
445
452
  tof: np.ndarray,
446
- ) -> NDArray:
453
+ ) -> tuple[NDArray, NDArray, NDArray]:
447
454
  """
448
455
  Determine the direct event velocity.
449
456
 
@@ -462,6 +469,10 @@ def get_de_velocity(
462
469
  -------
463
470
  velocities : np.ndarray
464
471
  N x 3 array of velocity components (vx, vy, vz) in km/s.
472
+ v_hat : np.ndarray
473
+ Unit vector in the direction of the velocity.
474
+ r_hat : np.ndarray
475
+ Position vector.
465
476
  """
466
477
  if tof[tof < 0].any():
467
478
  logger.info("Negative tof values found.")
@@ -483,10 +494,15 @@ def get_de_velocity(
483
494
 
484
495
  velocities = np.vstack((v_x, v_y, v_z)).T
485
496
 
486
- return velocities
497
+ v_hat = velocities / np.linalg.norm(velocities, axis=1)[:, None]
498
+ r_hat = -v_hat
499
+
500
+ return velocities, v_hat, r_hat
487
501
 
488
502
 
489
- def get_ssd_tof(de_dataset: xarray.Dataset, xf: np.ndarray) -> NDArray[np.float64]:
503
+ def get_ssd_tof(
504
+ de_dataset: xarray.Dataset, xf: np.ndarray, sensor: str
505
+ ) -> NDArray[np.float64]:
490
506
  """
491
507
  Calculate back xb, yb position for the SSDs.
492
508
 
@@ -501,8 +517,7 @@ def get_ssd_tof(de_dataset: xarray.Dataset, xf: np.ndarray) -> NDArray[np.float6
501
517
  A scale factor and offsets, and a multiplier convert xf to a tof offset.
502
518
 
503
519
  Further description is available on pages 36 of
504
- IMAP-Ultra Flight Software Specification document
505
- (7523-9009_Rev_-.pdf).
520
+ IMAP-Ultra Flight Software Specification document.
506
521
 
507
522
  Parameters
508
523
  ----------
@@ -510,25 +525,27 @@ def get_ssd_tof(de_dataset: xarray.Dataset, xf: np.ndarray) -> NDArray[np.float6
510
525
  Data in xarray format.
511
526
  xf : np.array
512
527
  Front x position (hundredths of a millimeter).
528
+ sensor : str
529
+ Sensor name.
513
530
 
514
531
  Returns
515
532
  -------
516
533
  tof : np.ndarray
517
534
  Time of flight (tenths of a nanosecond).
518
535
  """
519
- _, tof_offset, ssd_number = get_ssd_back_position_and_tof_offset(de_dataset)
520
- indices = np.nonzero(np.isin(de_dataset["STOP_TYPE"], [StopType.SSD.value]))[0]
536
+ _, tof_offset, ssd_number = get_ssd_back_position_and_tof_offset(de_dataset, sensor)
537
+ indices = np.nonzero(np.isin(de_dataset["stop_type"], [StopType.SSD.value]))[0]
521
538
 
522
- de_discrete = de_dataset.isel(epoch=indices)["COIN_DISCRETE_TDC"]
539
+ de_discrete = de_dataset.isel(epoch=indices)["coin_discrete_tdc"]
523
540
 
524
- time = get_image_params("TOFSSDSC") * de_discrete.values + tof_offset
541
+ time = get_image_params("TOFSSDSC", sensor) * de_discrete.values + tof_offset
525
542
 
526
543
  # The scale factor and offsets, and a multiplier to convert xf to a tof offset.
527
544
  # Convert xf to mm by dividing by 100.
528
545
  tof = (
529
546
  time
530
- + get_image_params("TOFSSDTOTOFF")
531
- + xf[indices] / 100 * get_image_params("XFTTOF")
547
+ + get_image_params("TOFSSDTOTOFF", sensor)
548
+ + xf[indices] / 100 * get_image_params("XFTTOF", sensor)
532
549
  ) * 10
533
550
 
534
551
  # Convert TOF to tenths of a nanosecond.
@@ -555,7 +572,7 @@ def get_de_energy_kev(v: np.ndarray, species: np.ndarray) -> NDArray:
555
572
  # Compute the sum of squares.
556
573
  v2 = np.sum(vv**2, axis=1)
557
574
 
558
- index_hydrogen = np.where(species == "H")
575
+ index_hydrogen = np.where(species == 1)
559
576
  energy = np.full_like(v2, np.nan)
560
577
 
561
578
  # 1/2 mv^2 in Joules, convert to keV
@@ -567,7 +584,11 @@ def get_de_energy_kev(v: np.ndarray, species: np.ndarray) -> NDArray:
567
584
 
568
585
 
569
586
  def get_energy_pulse_height(
570
- stop_type: np.ndarray, energy: np.ndarray, xb: np.ndarray, yb: np.ndarray
587
+ stop_type: np.ndarray,
588
+ energy: np.ndarray,
589
+ xb: np.ndarray,
590
+ yb: np.ndarray,
591
+ sensor: str,
571
592
  ) -> NDArray[np.float64]:
572
593
  """
573
594
  Calculate the pulse-height energy.
@@ -576,8 +597,7 @@ def get_energy_pulse_height(
576
597
  pulse height from the stop anode.
577
598
  Lookup tables (lut) are used for corrections.
578
599
  Further description is available on pages 40-41 of
579
- IMAP-Ultra Flight Software Specification document
580
- (7523-9009_Rev_-.pdf).
600
+ IMAP-Ultra Flight Software Specification document.
581
601
 
582
602
  Parameters
583
603
  ----------
@@ -589,6 +609,8 @@ def get_energy_pulse_height(
589
609
  X back position (hundredths of a millimeter).
590
610
  yb : np.ndarray
591
611
  Y back position (hundredths of a millimeter).
612
+ sensor : str
613
+ Sensor name.
592
614
 
593
615
  Returns
594
616
  -------
@@ -612,12 +634,12 @@ def get_energy_pulse_height(
612
634
 
613
635
  # TODO: waiting on these lookup tables: SpTpPHCorr, SpBtPHCorr
614
636
  energy_ph[indices_top] = energy[indices_top] - get_image_params(
615
- "SPTPPHOFF"
637
+ "SPTPPHOFF", sensor
616
638
  ) # * SpTpPHCorr[
617
639
  # xlut[indices_top], ylut[indices_top]] / 1024
618
640
 
619
641
  energy_ph[indices_bottom] = energy[indices_bottom] - get_image_params(
620
- "SPBTPHOFF"
642
+ "SPBTPHOFF", sensor
621
643
  ) # * SpBtPHCorr[
622
644
  # xlut[indices_bottom], ylut[indices_bottom]] / 1024
623
645
 
@@ -634,8 +656,7 @@ def get_energy_ssd(de_dataset: xarray.Dataset, ssd: np.ndarray) -> NDArray[np.fl
634
656
  SSD energy and SSD energy pulse width.
635
657
  The result is then normalized per SSD via a lookup table.
636
658
  Further description is available on pages 41 of
637
- IMAP-Ultra Flight Software Specification document
638
- (7523-9009_Rev_-.pdf).
659
+ IMAP-Ultra Flight Software Specification document.
639
660
 
640
661
  Parameters
641
662
  ----------
@@ -649,14 +670,14 @@ def get_energy_ssd(de_dataset: xarray.Dataset, ssd: np.ndarray) -> NDArray[np.fl
649
670
  energy_norm : np.ndarray
650
671
  Energy measured using the SSD.
651
672
  """
652
- ssd_indices = np.where(de_dataset["STOP_TYPE"].data >= 8)[0]
653
- energy = de_dataset["ENERGY_PH"].data[ssd_indices]
673
+ ssd_indices = np.nonzero(np.isin(de_dataset["stop_type"], StopType.SSD.value))[0]
674
+ energy = de_dataset["energy_ph"].data[ssd_indices]
654
675
 
655
676
  composite_energy = np.empty(len(energy), dtype=np.float64)
656
677
 
657
678
  composite_energy[energy >= UltraConstants.COMPOSITE_ENERGY_THRESHOLD] = (
658
679
  UltraConstants.COMPOSITE_ENERGY_THRESHOLD
659
- + de_dataset["PULSE_WIDTH"].data[ssd_indices][
680
+ + de_dataset["pulse_width"].data[ssd_indices][
660
681
  energy >= UltraConstants.COMPOSITE_ENERGY_THRESHOLD
661
682
  ]
662
683
  )
@@ -679,8 +700,7 @@ def get_ctof(
679
700
  to a fixed distance dmin between the front and back detectors.
680
701
  The normalized TOF is termed the corrected TOF (ctof).
681
702
  Further description is available on pages 42-44 of
682
- IMAP-Ultra Flight Software Specification document
683
- (7523-9009_Rev_-.pdf).
703
+ IMAP-Ultra Flight Software Specification document.
684
704
 
685
705
  Parameters
686
706
  ----------
@@ -702,9 +722,11 @@ def get_ctof(
702
722
 
703
723
  # Multiply times 100 to convert to hundredths of a millimeter.
704
724
  ctof = tof * dmin_ctof * 100 / path_length
725
+ magnitude_v = np.full(len(ctof), -1.0e31, dtype=np.float32)
705
726
 
706
- # Convert from mm/0.1ns to km/s.
707
- magnitude_v = dmin_ctof / ctof * 1e4
727
+ # Convert from mm/0.1ns to km/s for valid ctof values
728
+ valid_mask = ctof >= 0
729
+ magnitude_v[valid_mask] = dmin_ctof / ctof[valid_mask] * 1e4
708
730
 
709
731
  return ctof, magnitude_v
710
732
 
@@ -720,8 +742,7 @@ def determine_species(tof: np.ndarray, path_length: np.ndarray, type: str) -> ND
720
742
  Particle species are determined from ctof using thresholds.
721
743
 
722
744
  Further description is available on pages 42-44 of
723
- IMAP-Ultra Flight Software Specification document
724
- (7523-9009_Rev_-.pdf).
745
+ IMAP-Ultra Flight Software Specification document.
725
746
 
726
747
  Parameters
727
748
  ----------
@@ -740,13 +761,13 @@ def determine_species(tof: np.ndarray, path_length: np.ndarray, type: str) -> ND
740
761
  # Event TOF normalization to Z axis
741
762
  ctof, _ = get_ctof(tof, path_length, type)
742
763
  # Initialize bin array
743
- species_bin = np.full(len(ctof), "UNKNOWN", dtype="U10")
764
+ species_bin = np.full(len(ctof), 255, dtype=np.uint8)
744
765
 
745
- # Assign "H" to bins where cTOF is within the specified range
766
+ # Assign Species 1 ("H") to bins where cTOF is within the specified range
746
767
  species_bin[
747
768
  (ctof > UltraConstants.CTOF_SPECIES_MIN)
748
769
  & (ctof < UltraConstants.CTOF_SPECIES_MAX)
749
- ] = "H"
770
+ ] = 1
750
771
 
751
772
  return species_bin
752
773
 
@@ -809,14 +830,14 @@ def get_eventtimes(
809
830
  Notes
810
831
  -----
811
832
  Equation for event time:
812
- t = t_(spin start) + t_(spin start sub)/1000 +
833
+ t = t_(spin start) + t_(spin start sub)/1e6 +
813
834
  t_spin_period_sec * phase_angle/720
814
835
  """
815
836
  spin_df = get_spin_data()
816
837
  index = np.searchsorted(spin_df["spin_number"].values, spin)
817
838
  spin_starts = (
818
- spin_df["spin_start_sec"].values[index]
819
- + spin_df["spin_start_subsec"].values[index] / 1000
839
+ spin_df["spin_start_sec_sclk"].values[index]
840
+ + spin_df["spin_start_subsec_sclk"].values[index] / 1e6
820
841
  )
821
842
 
822
843
  spin_period_sec = spin_df["spin_period_sec"].values[index]
@@ -824,3 +845,140 @@ def get_eventtimes(
824
845
  event_times = spin_starts + spin_period_sec * (phase_angle / 720)
825
846
 
826
847
  return event_times, spin_starts, spin_period_sec
848
+
849
+
850
+ def interpolate_fwhm(
851
+ lookup_table: pandas.DataFrame,
852
+ energy: NDArray,
853
+ phi_inst: NDArray,
854
+ theta_inst: NDArray,
855
+ ) -> tuple[NDArray, NDArray]:
856
+ """
857
+ Interpolate phi and theta FWHM values using lookup tables.
858
+
859
+ Parameters
860
+ ----------
861
+ lookup_table : DataFrame
862
+ Angular profile lookup table for a given side and sensor.
863
+ energy : NDArray
864
+ Energy values.
865
+ phi_inst : NDArray
866
+ Instrument-frame azimuth angles.
867
+ theta_inst : NDArray
868
+ Instrument-frame elevation angles.
869
+
870
+ Returns
871
+ -------
872
+ phi_interp : NDArray
873
+ Interpolated phi FWHM.
874
+ theta_interp : NDArray
875
+ Interpolated theta FWHM.
876
+ """
877
+ interp_phi = LinearNDInterpolator(
878
+ lookup_table[["Energy", "phi_degrees"]].values, lookup_table["phi_fwhm"].values
879
+ )
880
+
881
+ interp_theta = LinearNDInterpolator(
882
+ lookup_table[["Energy", "theta_degrees"]].values,
883
+ lookup_table["theta_fwhm"].values,
884
+ )
885
+
886
+ # Note: will return nan for those out-of-bounds inputs.
887
+ phi_interp = interp_phi((energy, phi_inst))
888
+ theta_interp = interp_theta((energy, theta_inst))
889
+
890
+ return phi_interp, theta_interp
891
+
892
+
893
+ def get_fwhm(
894
+ start_type: NDArray,
895
+ sensor: str,
896
+ energy: NDArray,
897
+ phi_inst: NDArray,
898
+ theta_inst: NDArray,
899
+ ) -> tuple[NDArray, NDArray]:
900
+ """
901
+ Interpolate phi and theta FWHM values for each event based on start type.
902
+
903
+ Parameters
904
+ ----------
905
+ start_type : NDArray
906
+ Start Type: 1=Left, 2=Right.
907
+ sensor : str
908
+ Sensor name: "ultra45" or "ultra90".
909
+ energy : NDArray
910
+ Energy values for each event.
911
+ phi_inst : NDArray
912
+ Instrument-frame azimuth angle for each event.
913
+ theta_inst : NDArray
914
+ Instrument-frame elevation angle for each event.
915
+
916
+ Returns
917
+ -------
918
+ phi_interp : NDArray
919
+ Interpolated phi FWHM values.
920
+ theta_interp : NDArray
921
+ Interpolated theta FWHM values.
922
+ """
923
+ phi_interp = np.full_like(phi_inst, np.nan, dtype=np.float64)
924
+ theta_interp = np.full_like(theta_inst, np.nan, dtype=np.float64)
925
+ lt_table = get_angular_profiles("left", sensor)
926
+ rt_table = get_angular_profiles("right", sensor)
927
+
928
+ # Left start type
929
+ idx_left = start_type == StartType.Left.value
930
+ phi_interp[idx_left], theta_interp[idx_left] = interpolate_fwhm(
931
+ lt_table, energy[idx_left], phi_inst[idx_left], theta_inst[idx_left]
932
+ )
933
+
934
+ # Right start type
935
+ idx_right = start_type == StartType.Right.value
936
+ phi_interp[idx_right], theta_interp[idx_right] = interpolate_fwhm(
937
+ rt_table, energy[idx_right], phi_inst[idx_right], theta_inst[idx_right]
938
+ )
939
+
940
+ return phi_interp, theta_interp
941
+
942
+
943
+ def get_efficiency(
944
+ energy: NDArray,
945
+ phi_inst: NDArray,
946
+ theta_inst: NDArray,
947
+ ) -> NDArray:
948
+ """
949
+ Interpolate efficiency values for each event.
950
+
951
+ Parameters
952
+ ----------
953
+ energy : NDArray
954
+ Energy values for each event.
955
+ phi_inst : NDArray
956
+ Instrument-frame azimuth angle for each event.
957
+ theta_inst : NDArray
958
+ Instrument-frame elevation angle for each event.
959
+
960
+ Returns
961
+ -------
962
+ efficiency : NDArray
963
+ Interpolated efficiency values.
964
+ """
965
+ lookup_table = get_energy_efficiencies()
966
+
967
+ theta_vals = np.sort(lookup_table["theta (deg)"].unique())
968
+ phi_vals = np.sort(lookup_table["phi (deg)"].unique())
969
+ energy_column_names = lookup_table.columns[2:].tolist()
970
+ energy_vals = [float(col.replace("keV", "")) for col in energy_column_names]
971
+ efficiency_2d = lookup_table[energy_column_names].values
972
+
973
+ efficiency_grid = efficiency_2d.reshape(
974
+ (len(theta_vals), len(phi_vals), len(energy_vals))
975
+ )
976
+
977
+ interpolator = RegularGridInterpolator(
978
+ (theta_vals, phi_vals, energy_vals),
979
+ efficiency_grid,
980
+ bounds_error=False,
981
+ fill_value=np.nan,
982
+ )
983
+
984
+ return interpolator((theta_inst, phi_inst, energy))
@@ -6,9 +6,7 @@ import xarray as xr
6
6
  from imap_processing.ultra.utils.ultra_l1_utils import create_dataset
7
7
 
8
8
 
9
- def calculate_histogram(
10
- histogram_dataset: xr.Dataset, name: str, data_version: str
11
- ) -> xr.Dataset:
9
+ def calculate_histogram(histogram_dataset: xr.Dataset, name: str) -> xr.Dataset:
12
10
  """
13
11
  Create dictionary with defined datatype for Histogram Data.
14
12
 
@@ -18,8 +16,6 @@ def calculate_histogram(
18
16
  Dataset containing histogram data.
19
17
  name : str
20
18
  Name of dataset.
21
- data_version : str
22
- Version of the data.
23
19
 
24
20
  Returns
25
21
  -------
@@ -35,6 +31,6 @@ def calculate_histogram(
35
31
  histogram_dict["epoch"] = epoch
36
32
  histogram_dict["sid"] = np.zeros(len(epoch), dtype=np.uint8)
37
33
 
38
- dataset = create_dataset(histogram_dict, name, "l1c", data_version)
34
+ dataset = create_dataset(histogram_dict, name, "l1c")
39
35
 
40
36
  return dataset
@@ -22,7 +22,6 @@ def calculate_spacecraft_pset(
22
22
  extendedspin_dataset: xr.Dataset,
23
23
  cullingmask_dataset: xr.Dataset,
24
24
  name: str,
25
- data_version: str,
26
25
  ) -> xr.Dataset:
27
26
  """
28
27
  Create dictionary with defined datatype for Pointing Set Grid Data.
@@ -37,8 +36,6 @@ def calculate_spacecraft_pset(
37
36
  Dataset containing cullingmask data.
38
37
  name : str
39
38
  Name of the dataset.
40
- data_version : str
41
- Version of the data.
42
39
 
43
40
  Returns
44
41
  -------
@@ -80,7 +77,8 @@ def calculate_spacecraft_pset(
80
77
  pset_dict["background_rates"] = background_rates
81
78
  pset_dict["exposure_factor"] = exposure_pointing
82
79
  pset_dict["healpix"] = healpix
80
+ pset_dict["energy_bin_delta"] = np.diff(intervals, axis=1).squeeze()
83
81
 
84
- dataset = create_dataset(pset_dict, name, "l1c", data_version)
82
+ dataset = create_dataset(pset_dict, name, "l1c")
85
83
 
86
84
  return dataset