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

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

Potentially problematic release.


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

Files changed (415) hide show
  1. imap_processing/__init__.py +11 -11
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/ccsds_data.py +1 -2
  4. imap_processing/ccsds/excel_to_xtce.py +66 -18
  5. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +24 -40
  6. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +934 -42
  7. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
  8. imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +0 -5
  9. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +10 -11
  10. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +17 -19
  11. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +27 -14
  12. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +106 -116
  13. imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +120 -145
  14. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +14 -0
  15. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +25 -9
  16. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +6 -4
  17. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +3 -3
  18. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +0 -12
  19. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +1 -1
  20. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +23 -20
  21. imap_processing/cdf/config/imap_mag_l1a_variable_attrs.yaml +361 -0
  22. imap_processing/cdf/config/imap_mag_l1b_variable_attrs.yaml +160 -0
  23. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +160 -0
  24. imap_processing/cdf/config/imap_spacecraft_global_cdf_attrs.yaml +18 -0
  25. imap_processing/cdf/config/imap_spacecraft_variable_attrs.yaml +40 -0
  26. imap_processing/cdf/config/imap_swapi_global_cdf_attrs.yaml +1 -5
  27. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +22 -0
  28. imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +12 -4
  29. imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +16 -2
  30. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +64 -52
  31. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +71 -47
  32. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +180 -19
  33. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
  34. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +80 -17
  35. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +32 -57
  36. imap_processing/cdf/utils.py +52 -38
  37. imap_processing/cli.py +477 -233
  38. imap_processing/codice/codice_l1a.py +466 -131
  39. imap_processing/codice/codice_l1b.py +51 -152
  40. imap_processing/codice/constants.py +1360 -569
  41. imap_processing/codice/decompress.py +2 -6
  42. imap_processing/ena_maps/ena_maps.py +1103 -146
  43. imap_processing/ena_maps/utils/coordinates.py +19 -0
  44. imap_processing/ena_maps/utils/map_utils.py +14 -17
  45. imap_processing/ena_maps/utils/spatial_utils.py +55 -52
  46. imap_processing/glows/l1a/glows_l1a.py +28 -99
  47. imap_processing/glows/l1a/glows_l1a_data.py +2 -2
  48. imap_processing/glows/l1b/glows_l1b.py +1 -4
  49. imap_processing/glows/l1b/glows_l1b_data.py +1 -3
  50. imap_processing/glows/l2/glows_l2.py +2 -5
  51. imap_processing/hi/l1a/hi_l1a.py +54 -29
  52. imap_processing/hi/l1a/histogram.py +0 -1
  53. imap_processing/hi/l1a/science_direct_event.py +6 -8
  54. imap_processing/hi/l1b/hi_l1b.py +111 -82
  55. imap_processing/hi/l1c/hi_l1c.py +416 -32
  56. imap_processing/hi/utils.py +58 -12
  57. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
  58. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt0-factors_20250219_v002.csv +205 -0
  59. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt1-factors_20250219_v002.csv +205 -0
  60. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt2-factors_20250219_v002.csv +205 -0
  61. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt3-factors_20250219_v002.csv +205 -0
  62. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-summed-dt0-factors_20250219_v002.csv +68 -0
  63. imap_processing/hit/hit_utils.py +235 -5
  64. imap_processing/hit/l0/constants.py +20 -11
  65. imap_processing/hit/l0/decom_hit.py +21 -5
  66. imap_processing/hit/l1a/hit_l1a.py +71 -75
  67. imap_processing/hit/l1b/constants.py +321 -0
  68. imap_processing/hit/l1b/hit_l1b.py +377 -67
  69. imap_processing/hit/l2/constants.py +318 -0
  70. imap_processing/hit/l2/hit_l2.py +723 -0
  71. imap_processing/hit/packet_definitions/hit_packet_definitions.xml +1323 -71
  72. imap_processing/ialirt/l0/mag_l0_ialirt_data.py +155 -0
  73. imap_processing/ialirt/l0/parse_mag.py +374 -0
  74. imap_processing/ialirt/l0/process_swapi.py +69 -0
  75. imap_processing/ialirt/l0/process_swe.py +548 -0
  76. imap_processing/ialirt/packet_definitions/ialirt.xml +216 -208
  77. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +1 -1
  78. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +1 -1
  79. imap_processing/ialirt/packet_definitions/ialirt_mag.xml +115 -0
  80. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +14 -14
  81. imap_processing/ialirt/utils/grouping.py +114 -0
  82. imap_processing/ialirt/utils/time.py +29 -0
  83. imap_processing/idex/atomic_masses.csv +22 -0
  84. imap_processing/idex/decode.py +2 -2
  85. imap_processing/idex/idex_constants.py +33 -0
  86. imap_processing/idex/idex_l0.py +22 -8
  87. imap_processing/idex/idex_l1a.py +81 -51
  88. imap_processing/idex/idex_l1b.py +13 -39
  89. imap_processing/idex/idex_l2a.py +823 -0
  90. imap_processing/idex/idex_l2b.py +120 -0
  91. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +11 -11
  92. imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
  93. imap_processing/lo/l0/lo_science.py +7 -2
  94. imap_processing/lo/l1a/lo_l1a.py +1 -5
  95. imap_processing/lo/l1b/lo_l1b.py +702 -29
  96. imap_processing/lo/l1b/tof_conversions.py +11 -0
  97. imap_processing/lo/l1c/lo_l1c.py +1 -4
  98. imap_processing/mag/constants.py +51 -0
  99. imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
  100. imap_processing/mag/l0/decom_mag.py +10 -3
  101. imap_processing/mag/l1a/mag_l1a.py +23 -19
  102. imap_processing/mag/l1a/mag_l1a_data.py +35 -10
  103. imap_processing/mag/l1b/mag_l1b.py +259 -50
  104. imap_processing/mag/l1c/interpolation_methods.py +388 -0
  105. imap_processing/mag/l1c/mag_l1c.py +621 -17
  106. imap_processing/mag/l2/mag_l2.py +140 -0
  107. imap_processing/mag/l2/mag_l2_data.py +288 -0
  108. imap_processing/quality_flags.py +1 -0
  109. imap_processing/spacecraft/packet_definitions/scid_x252.xml +538 -0
  110. imap_processing/spacecraft/quaternions.py +121 -0
  111. imap_processing/spice/geometry.py +19 -22
  112. imap_processing/spice/kernels.py +0 -276
  113. imap_processing/spice/pointing_frame.py +257 -0
  114. imap_processing/spice/repoint.py +149 -0
  115. imap_processing/spice/spin.py +38 -33
  116. imap_processing/spice/time.py +24 -0
  117. imap_processing/swapi/l1/swapi_l1.py +20 -12
  118. imap_processing/swapi/l2/swapi_l2.py +116 -5
  119. imap_processing/swapi/swapi_utils.py +32 -0
  120. imap_processing/swe/l1a/swe_l1a.py +44 -12
  121. imap_processing/swe/l1a/swe_science.py +13 -13
  122. imap_processing/swe/l1b/swe_l1b.py +898 -23
  123. imap_processing/swe/l2/swe_l2.py +75 -136
  124. imap_processing/swe/packet_definitions/swe_packet_definition.xml +1121 -1
  125. imap_processing/swe/utils/swe_constants.py +64 -0
  126. imap_processing/swe/utils/swe_utils.py +85 -28
  127. imap_processing/tests/ccsds/test_data/expected_output.xml +40 -1
  128. imap_processing/tests/ccsds/test_excel_to_xtce.py +24 -21
  129. imap_processing/tests/cdf/test_data/imap_instrument2_global_cdf_attrs.yaml +0 -2
  130. imap_processing/tests/cdf/test_utils.py +14 -16
  131. imap_processing/tests/codice/conftest.py +44 -33
  132. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  133. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  134. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-ialirt_20241110193700_v0.0.0.cdf +0 -0
  135. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-omni_20241110193700_v0.0.0.cdf +0 -0
  136. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
  137. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-priorities_20241110193700_v0.0.0.cdf +0 -0
  138. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-sectored_20241110193700_v0.0.0.cdf +0 -0
  139. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  140. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  141. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  142. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-angular_20241110193700_v0.0.0.cdf +0 -0
  143. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-priority_20241110193700_v0.0.0.cdf +0 -0
  144. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-species_20241110193700_v0.0.0.cdf +0 -0
  145. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
  146. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-angular_20241110193700_v0.0.0.cdf +0 -0
  147. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-priority_20241110193700_v0.0.0.cdf +0 -0
  148. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-species_20241110193700_v0.0.0.cdf +0 -0
  149. imap_processing/tests/codice/test_codice_l1a.py +126 -53
  150. imap_processing/tests/codice/test_codice_l1b.py +6 -7
  151. imap_processing/tests/codice/test_decompress.py +4 -4
  152. imap_processing/tests/conftest.py +239 -27
  153. imap_processing/tests/ena_maps/conftest.py +51 -0
  154. imap_processing/tests/ena_maps/test_ena_maps.py +1068 -110
  155. imap_processing/tests/ena_maps/test_map_utils.py +66 -43
  156. imap_processing/tests/ena_maps/test_spatial_utils.py +17 -21
  157. imap_processing/tests/glows/conftest.py +10 -14
  158. imap_processing/tests/glows/test_glows_decom.py +4 -4
  159. imap_processing/tests/glows/test_glows_l1a_cdf.py +6 -27
  160. imap_processing/tests/glows/test_glows_l1a_data.py +6 -8
  161. imap_processing/tests/glows/test_glows_l1b.py +11 -11
  162. imap_processing/tests/glows/test_glows_l1b_data.py +5 -5
  163. imap_processing/tests/glows/test_glows_l2.py +2 -8
  164. imap_processing/tests/hi/conftest.py +1 -1
  165. imap_processing/tests/hi/data/l0/H45_diag_fee_20250208.bin +0 -0
  166. imap_processing/tests/hi/data/l0/H45_diag_fee_20250208_verify.csv +205 -0
  167. imap_processing/tests/hi/test_hi_l1b.py +22 -27
  168. imap_processing/tests/hi/test_hi_l1c.py +249 -18
  169. imap_processing/tests/hi/test_l1a.py +35 -7
  170. imap_processing/tests/hi/test_science_direct_event.py +3 -3
  171. imap_processing/tests/hi/test_utils.py +24 -2
  172. imap_processing/tests/hit/helpers/l1_validation.py +74 -73
  173. imap_processing/tests/hit/test_data/hskp_sample.ccsds +0 -0
  174. imap_processing/tests/hit/test_data/imap_hit_l0_raw_20100105_v001.pkts +0 -0
  175. imap_processing/tests/hit/test_decom_hit.py +5 -1
  176. imap_processing/tests/hit/test_hit_l1a.py +32 -36
  177. imap_processing/tests/hit/test_hit_l1b.py +300 -81
  178. imap_processing/tests/hit/test_hit_l2.py +716 -0
  179. imap_processing/tests/hit/test_hit_utils.py +184 -7
  180. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
  181. imap_processing/tests/hit/validation_data/hskp_sample_eu_3_6_2025.csv +89 -0
  182. imap_processing/tests/hit/validation_data/hskp_sample_raw.csv +89 -88
  183. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +1 -1
  184. imap_processing/tests/ialirt/data/l0/461971383-404.bin +0 -0
  185. imap_processing/tests/ialirt/data/l0/461971384-405.bin +0 -0
  186. imap_processing/tests/ialirt/data/l0/461971385-406.bin +0 -0
  187. imap_processing/tests/ialirt/data/l0/461971386-407.bin +0 -0
  188. imap_processing/tests/ialirt/data/l0/461971387-408.bin +0 -0
  189. imap_processing/tests/ialirt/data/l0/461971388-409.bin +0 -0
  190. imap_processing/tests/ialirt/data/l0/461971389-410.bin +0 -0
  191. imap_processing/tests/ialirt/data/l0/461971390-411.bin +0 -0
  192. imap_processing/tests/ialirt/data/l0/461971391-412.bin +0 -0
  193. imap_processing/tests/ialirt/data/l0/sample_decoded_i-alirt_data.csv +383 -0
  194. imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
  195. imap_processing/tests/ialirt/unit/test_grouping.py +81 -0
  196. imap_processing/tests/ialirt/unit/test_parse_mag.py +223 -0
  197. imap_processing/tests/ialirt/unit/test_process_codicehi.py +3 -3
  198. imap_processing/tests/ialirt/unit/test_process_codicelo.py +3 -10
  199. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +4 -4
  200. imap_processing/tests/ialirt/unit/test_process_hit.py +3 -3
  201. imap_processing/tests/ialirt/unit/test_process_swapi.py +24 -16
  202. imap_processing/tests/ialirt/unit/test_process_swe.py +319 -6
  203. imap_processing/tests/ialirt/unit/test_time.py +16 -0
  204. imap_processing/tests/idex/conftest.py +127 -6
  205. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231218_v001.pkts +0 -0
  206. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20241206_v001.pkts +0 -0
  207. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20250108_v001.pkts +0 -0
  208. imap_processing/tests/idex/test_data/impact_14_tof_high_data.txt +4508 -4508
  209. imap_processing/tests/idex/test_idex_l0.py +33 -11
  210. imap_processing/tests/idex/test_idex_l1a.py +92 -21
  211. imap_processing/tests/idex/test_idex_l1b.py +106 -27
  212. imap_processing/tests/idex/test_idex_l2a.py +399 -0
  213. imap_processing/tests/idex/test_idex_l2b.py +93 -0
  214. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20241022_v002.cdf +0 -0
  215. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20241022_v002.cdf +0 -0
  216. imap_processing/tests/lo/test_lo_l1a.py +3 -3
  217. imap_processing/tests/lo/test_lo_l1b.py +515 -6
  218. imap_processing/tests/lo/test_lo_l1c.py +1 -1
  219. imap_processing/tests/lo/test_lo_science.py +7 -7
  220. imap_processing/tests/lo/test_star_sensor.py +1 -1
  221. imap_processing/tests/mag/conftest.py +120 -2
  222. imap_processing/tests/mag/test_mag_decom.py +5 -4
  223. imap_processing/tests/mag/test_mag_l1a.py +51 -7
  224. imap_processing/tests/mag/test_mag_l1b.py +40 -59
  225. imap_processing/tests/mag/test_mag_l1c.py +354 -19
  226. imap_processing/tests/mag/test_mag_l2.py +130 -0
  227. imap_processing/tests/mag/test_mag_validation.py +247 -26
  228. imap_processing/tests/mag/validation/L1b/T009/MAGScience-normal-(2,2)-8s-20250204-16h39.csv +17 -0
  229. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +16 -16
  230. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +16 -16
  231. imap_processing/tests/mag/validation/L1b/T010/MAGScience-normal-(2,2)-8s-20250206-12h05.csv +17 -0
  232. imap_processing/tests/mag/validation/L1b/T011/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
  233. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +16 -16
  234. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +16 -16
  235. imap_processing/tests/mag/validation/L1b/T012/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
  236. imap_processing/tests/mag/validation/L1b/T012/data.bin +0 -0
  237. imap_processing/tests/mag/validation/L1b/T012/field_like_all_ranges.txt +19200 -0
  238. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-cal.cdf +0 -0
  239. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-in.csv +17 -0
  240. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-magi-out.csv +17 -0
  241. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-mago-out.csv +17 -0
  242. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-in.csv +1217 -0
  243. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-out.csv +1857 -0
  244. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-in.csv +1217 -0
  245. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-out.csv +1857 -0
  246. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-in.csv +1217 -0
  247. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-out.csv +1793 -0
  248. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-in.csv +1217 -0
  249. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-out.csv +1793 -0
  250. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-burst-in.csv +2561 -0
  251. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-in.csv +961 -0
  252. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-out.csv +1539 -0
  253. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-in.csv +1921 -0
  254. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-out.csv +2499 -0
  255. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-in.csv +865 -0
  256. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-out.csv +1196 -0
  257. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-in.csv +1729 -0
  258. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-out.csv +3053 -0
  259. imap_processing/tests/mag/validation/L2/imap_mag_l1b_norm-mago_20251017_v002.cdf +0 -0
  260. imap_processing/tests/mag/validation/calibration/imap_mag_l1b-calibration_20240229_v001.cdf +0 -0
  261. imap_processing/tests/mag/validation/calibration/imap_mag_l2-calibration-matrices_20251017_v004.cdf +0 -0
  262. imap_processing/tests/mag/validation/calibration/imap_mag_l2-offsets-norm_20251017_20251017_v001.cdf +0 -0
  263. imap_processing/tests/spacecraft/data/SSR_2024_190_20_08_12_0483851794_2_DA_apid0594_1packet.pkts +0 -0
  264. imap_processing/tests/spacecraft/test_quaternions.py +71 -0
  265. imap_processing/tests/spice/test_data/fake_repoint_data.csv +5 -0
  266. imap_processing/tests/spice/test_data/fake_spin_data.csv +11 -11
  267. imap_processing/tests/spice/test_geometry.py +9 -12
  268. imap_processing/tests/spice/test_kernels.py +1 -200
  269. imap_processing/tests/spice/test_pointing_frame.py +185 -0
  270. imap_processing/tests/spice/test_repoint.py +121 -0
  271. imap_processing/tests/spice/test_spin.py +50 -9
  272. imap_processing/tests/spice/test_time.py +14 -0
  273. imap_processing/tests/swapi/lut/imap_swapi_esa-unit-conversion_20250211_v000.csv +73 -0
  274. imap_processing/tests/swapi/lut/imap_swapi_lut-notes_20250211_v000.csv +1025 -0
  275. imap_processing/tests/swapi/test_swapi_l1.py +13 -11
  276. imap_processing/tests/swapi/test_swapi_l2.py +180 -8
  277. imap_processing/tests/swe/l0_data/2024051010_SWE_HK_packet.bin +0 -0
  278. imap_processing/tests/swe/l0_data/2024051011_SWE_CEM_RAW_packet.bin +0 -0
  279. imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_APP_HK_20240510_092742.csv +49 -0
  280. imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_CEM_RAW_20240510_092742.csv +593 -0
  281. imap_processing/tests/swe/lut/checker-board-indices.csv +24 -0
  282. imap_processing/tests/swe/lut/imap_swe_esa-lut_20250301_v000.csv +385 -0
  283. imap_processing/tests/swe/lut/imap_swe_l1b-in-flight-cal_20240510_20260716_v000.csv +3 -0
  284. imap_processing/tests/swe/test_swe_l1a.py +20 -2
  285. imap_processing/tests/swe/test_swe_l1a_cem_raw.py +52 -0
  286. imap_processing/tests/swe/test_swe_l1a_hk.py +68 -0
  287. imap_processing/tests/swe/test_swe_l1a_science.py +3 -3
  288. imap_processing/tests/swe/test_swe_l1b.py +162 -24
  289. imap_processing/tests/swe/test_swe_l2.py +153 -91
  290. imap_processing/tests/test_cli.py +171 -88
  291. imap_processing/tests/test_utils.py +140 -17
  292. imap_processing/tests/ultra/data/l0/FM45_UltraFM45_Functional_2024-01-22T0105_20240122T010548.CCSDS +0 -0
  293. imap_processing/tests/ultra/data/l0/ultra45_raw_sc_ultraimgrates_20220530_00.csv +164 -0
  294. imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3243 -3243
  295. imap_processing/tests/ultra/data/mock_data.py +369 -0
  296. imap_processing/tests/ultra/unit/conftest.py +115 -89
  297. imap_processing/tests/ultra/unit/test_badtimes.py +4 -4
  298. imap_processing/tests/ultra/unit/test_cullingmask.py +8 -6
  299. imap_processing/tests/ultra/unit/test_de.py +14 -13
  300. imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
  301. imap_processing/tests/ultra/unit/test_decom_apid_881.py +54 -11
  302. imap_processing/tests/ultra/unit/test_decom_apid_883.py +12 -10
  303. imap_processing/tests/ultra/unit/test_decom_apid_896.py +202 -55
  304. imap_processing/tests/ultra/unit/test_lookup_utils.py +23 -1
  305. imap_processing/tests/ultra/unit/test_spacecraft_pset.py +77 -0
  306. imap_processing/tests/ultra/unit/test_ultra_l1a.py +98 -305
  307. imap_processing/tests/ultra/unit/test_ultra_l1b.py +60 -14
  308. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
  309. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +26 -27
  310. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +239 -70
  311. imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
  312. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +114 -83
  313. imap_processing/tests/ultra/unit/test_ultra_l2.py +230 -0
  314. imap_processing/ultra/constants.py +1 -1
  315. imap_processing/ultra/l0/decom_tools.py +27 -39
  316. imap_processing/ultra/l0/decom_ultra.py +168 -204
  317. imap_processing/ultra/l0/ultra_utils.py +152 -136
  318. imap_processing/ultra/l1a/ultra_l1a.py +55 -271
  319. imap_processing/ultra/l1b/badtimes.py +1 -4
  320. imap_processing/ultra/l1b/cullingmask.py +2 -6
  321. imap_processing/ultra/l1b/de.py +116 -57
  322. imap_processing/ultra/l1b/extendedspin.py +20 -18
  323. imap_processing/ultra/l1b/lookup_utils.py +72 -9
  324. imap_processing/ultra/l1b/ultra_l1b.py +36 -16
  325. imap_processing/ultra/l1b/ultra_l1b_culling.py +66 -30
  326. imap_processing/ultra/l1b/ultra_l1b_extended.py +297 -94
  327. imap_processing/ultra/l1c/histogram.py +2 -6
  328. imap_processing/ultra/l1c/spacecraft_pset.py +84 -0
  329. imap_processing/ultra/l1c/ultra_l1c.py +8 -9
  330. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +206 -108
  331. imap_processing/ultra/l2/ultra_l2.py +299 -0
  332. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +526 -0
  333. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +526 -0
  334. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +526 -0
  335. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +526 -0
  336. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -2
  337. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -0
  338. imap_processing/ultra/packet_definitions/README.md +38 -0
  339. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +15302 -482
  340. imap_processing/ultra/utils/ultra_l1_utils.py +31 -12
  341. imap_processing/utils.py +69 -29
  342. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +10 -6
  343. imap_processing-0.13.0.dist-info/RECORD +578 -0
  344. imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +0 -237
  345. imap_processing/hi/l1a/housekeeping.py +0 -27
  346. imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
  347. imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
  348. imap_processing/swe/l1b/swe_l1b_science.py +0 -652
  349. imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-aggregated_20240429_v001.cdf +0 -0
  350. imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-singles_20240429_v001.cdf +0 -0
  351. imap_processing/tests/codice/data/imap_codice_l1a_hi-omni_20240429_v001.cdf +0 -0
  352. imap_processing/tests/codice/data/imap_codice_l1a_hi-sectored_20240429_v001.cdf +0 -0
  353. imap_processing/tests/codice/data/imap_codice_l1a_hskp_20100101_v001.cdf +0 -0
  354. imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-aggregated_20240429_v001.cdf +0 -0
  355. imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-singles_20240429_v001.cdf +0 -0
  356. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-angular_20240429_v001.cdf +0 -0
  357. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-priority_20240429_v001.cdf +0 -0
  358. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-species_20240429_v001.cdf +0 -0
  359. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-angular_20240429_v001.cdf +0 -0
  360. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-priority_20240429_v001.cdf +0 -0
  361. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-species_20240429_v001.cdf +0 -0
  362. imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-aggregated_20240429_v001.cdf +0 -0
  363. imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-singles_20240429_v001.cdf +0 -0
  364. imap_processing/tests/codice/data/imap_codice_l1b_hi-omni_20240429_v001.cdf +0 -0
  365. imap_processing/tests/codice/data/imap_codice_l1b_hi-sectored_20240429_v001.cdf +0 -0
  366. imap_processing/tests/codice/data/imap_codice_l1b_hskp_20100101_v001.cdf +0 -0
  367. imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-aggregated_20240429_v001.cdf +0 -0
  368. imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-singles_20240429_v001.cdf +0 -0
  369. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-angular_20240429_v001.cdf +0 -0
  370. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-priority_20240429_v001.cdf +0 -0
  371. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-species_20240429_v001.cdf +0 -0
  372. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-angular_20240429_v001.cdf +0 -0
  373. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-priority_20240429_v001.cdf +0 -0
  374. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-species_20240429_v001.cdf +0 -0
  375. imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
  376. imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1251.pkts +0 -0
  377. imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1252.pkts +0 -0
  378. imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +0 -89
  379. imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +0 -29
  380. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231214_v001.pkts +0 -0
  381. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20100101_v001.cdf +0 -0
  382. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20100101_v001.cdf +0 -0
  383. imap_processing/tests/swe/test_swe_l1b_science.py +0 -84
  384. imap_processing/tests/ultra/test_data/mock_data.py +0 -161
  385. imap_processing/ultra/l1c/pset.py +0 -40
  386. imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
  387. imap_processing-0.11.0.dist-info/RECORD +0 -488
  388. /imap_processing/idex/packet_definitions/{idex_packet_definition.xml → idex_science_packet_definition.xml} +0 -0
  389. /imap_processing/tests/ialirt/{test_data → data}/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  390. /imap_processing/tests/ialirt/{test_data → data}/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  391. /imap_processing/tests/ialirt/{test_data → data}/l0/IALiRT Raw Packet Telemetry.txt +0 -0
  392. /imap_processing/tests/ialirt/{test_data → data}/l0/apid01152.tlm +0 -0
  393. /imap_processing/tests/ialirt/{test_data → data}/l0/eu_SWP_IAL_20240826_152033.csv +0 -0
  394. /imap_processing/tests/ialirt/{test_data → data}/l0/hi_fsw_view_1_ccsds.bin +0 -0
  395. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.ccsds +0 -0
  396. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.csv +0 -0
  397. /imap_processing/tests/ialirt/{test_data → data}/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +0 -0
  398. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  399. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  400. /imap_processing/{mag/l1b → tests/spacecraft}/__init__.py +0 -0
  401. /imap_processing/{swe/l1b/engineering_unit_convert_table.csv → tests/swe/lut/imap_swe_eu-conversion_20240510_v000.csv} +0 -0
  402. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_40P_Phi28p5_BeamCal_LinearScan_phi28.50_theta-0.00_20240207T102740.CCSDS +0 -0
  403. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_7P_Phi0.0_BeamCal_LinearScan_phi0.04_theta-0.01_20230821T121304.CCSDS +0 -0
  404. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.CCSDS +0 -0
  405. /imap_processing/tests/ultra/{test_data → data}/l0/Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.CCSDS +0 -0
  406. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_auxdata_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
  407. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_enaphxtofhangimg_FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.csv +0 -0
  408. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultraimgrates_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
  409. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimgevent_FM45_7P_Phi00_BeamCal_LinearScan_phi004_theta-001_20230821T121304.csv +0 -0
  410. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E1.cdf +0 -0
  411. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E12.cdf +0 -0
  412. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E24.cdf +0 -0
  413. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/LICENSE +0 -0
  414. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
  415. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,399 @@
1
+ """Tests the L2a processing for IDEX data"""
2
+
3
+ import numpy as np
4
+ import xarray as xr
5
+ from scipy.stats import exponnorm
6
+
7
+ from imap_processing.idex import idex_constants
8
+ from imap_processing.idex.idex_l2a import (
9
+ BaselineNoiseTime,
10
+ analyze_peaks,
11
+ butter_lowpass_filter,
12
+ calculate_kappa,
13
+ calculate_snr,
14
+ chi_square,
15
+ estimate_dust_mass,
16
+ fit_impact,
17
+ remove_signal_noise,
18
+ sine_fit,
19
+ time_to_mass,
20
+ )
21
+
22
+
23
+ def mock_microphonics_noise(time: np.ndarray) -> np.ndarray:
24
+ """Function to mock signal noise (linear and sine wave) due to microphonics."""
25
+ noise_frequency = idex_constants.TARGET_NOISE_FREQUENCY
26
+ phase_shift = 45
27
+ amp = 10
28
+ # Create a sine wave signal
29
+ sine_signal = sine_fit(time, amp, noise_frequency, phase_shift)
30
+ # Combine the sine wave signals with a linear signal to create noise
31
+ combined_sig = sine_signal + (time * 5)
32
+
33
+ return combined_sig
34
+
35
+
36
+ def test_l2a_logical_source(l2a_dataset: xr.Dataset):
37
+ """Tests that the ``idex_l2a`` function generates datasets
38
+ with the expected logical source.
39
+
40
+ Parameters
41
+ ----------
42
+ l2a_dataset : xr.Dataset
43
+ A ``xarray`` dataset containing the test data
44
+ """
45
+ expected_src = "imap_idex_l2a_sci-1week"
46
+ assert l2a_dataset.attrs["Logical_source"] == expected_src
47
+
48
+
49
+ def test_l2a_cdf_variables(l2a_dataset: xr.Dataset):
50
+ """Tests that the ``idex_l2a`` function generates datasets
51
+ with the expected variables.
52
+
53
+ Parameters
54
+ ----------
55
+ l2a_dataset : xr.Dataset
56
+ A ``xarray`` dataset containing the test data
57
+ """
58
+ expected_vars = [
59
+ "target_low_fit_parameters",
60
+ "target_low_fit_impact_charge",
61
+ "target_low_fit_impact_mass_estimate",
62
+ "target_low_chi_squared",
63
+ "target_low_reduced_chi_squared",
64
+ "target_low_fit_results",
65
+ "target_high_fit_parameters",
66
+ "target_high_fit_impact_charge",
67
+ "target_high_fit_impact_mass_estimate",
68
+ "target_high_chi_squared",
69
+ "target_high_reduced_chi_squared",
70
+ "target_high_fit_results",
71
+ "ion_grid_fit_parameters",
72
+ "ion_grid_fit_impact_charge",
73
+ "ion_grid_fit_impact_mass_estimate",
74
+ "ion_grid_chi_squared",
75
+ "ion_grid_reduced_chi_squared",
76
+ "ion_grid_fit_results",
77
+ "tof_peak_fit_parameters",
78
+ "tof_peak_area_under_fit",
79
+ "tof_peak_chi_square",
80
+ "tof_peak_reduced_chi_square",
81
+ "tof_peak_kappa",
82
+ "tof_snr",
83
+ "mass",
84
+ ]
85
+
86
+ cdf_vars = l2a_dataset.variables
87
+ for var in expected_vars:
88
+ assert var in cdf_vars
89
+
90
+
91
+ def test_time_to_mass_zero_lag():
92
+ """
93
+ Tests that the time_to_mass function correctly converts time-of-flight
94
+ to a mass scale using known peak positions.
95
+ """
96
+ carbon_mass = 12
97
+ masses = np.asarray([1, 4, 9])
98
+
99
+ expected_lag = 10
100
+ expected_stretch = 1500
101
+ # Create a 2d time of flight array exactly where we would expect the peaks to be
102
+ # Each mass should appear at time t = 1400 * sqrt(m) ns
103
+ tof = np.zeros((15, int(np.sqrt(masses[-1]) * expected_stretch + 1 + expected_lag)))
104
+ min_stretch = 1400
105
+ # Mass 1 expected tof
106
+ tof[:-1, min_stretch] = 1
107
+ # Mass 4 expected tof
108
+ tof[:-1, min_stretch * 2] = 1
109
+ # Mass 9 expected tof
110
+ tof[:-1, min_stretch * 3] = 1
111
+ # Change the last TOF array to be shifted and 'stretched'
112
+ # Mass 1 expected tof
113
+ tof[-1, expected_stretch + expected_lag] = 1
114
+ # Mass 4 expected tof
115
+ tof[-1, expected_stretch * 2 + expected_lag] = 1
116
+ # Mass 9 expected tof
117
+ tof[-1, expected_stretch * 3 + expected_lag] = 1
118
+
119
+ time = np.tile(np.arange(len(tof[0])), (15, 1))
120
+ stretch, shift, mass_scale = time_to_mass(tof, time, masses)
121
+
122
+ # Test with carbon mass
123
+ carbon_time = (stretch[0] * np.sqrt(carbon_mass)) / 1e-6 # Convert ms to s
124
+ mass = np.interp(carbon_time, time[0], mass_scale[0])
125
+ assert np.allclose(carbon_mass, mass, rtol=1e-2)
126
+
127
+ # Test shift is zero since peaks are aligned
128
+ assert np.all(shift[:-1] == 0)
129
+ # Test stretch factor matches expected 1400 ns in seconds
130
+ assert np.all(stretch[:-1] == 1400 * 1e-9)
131
+ # Test output shape
132
+ assert mass_scale.shape == time.shape
133
+ # Test that the last shift and stretch are the expected values
134
+ assert shift[-1] == -expected_lag * idex_constants.FM_SAMPLING_RATE
135
+ # Test stretch factor matches expected 1400 ns in seconds
136
+ assert stretch[-1] == expected_stretch * 1e-9
137
+
138
+ # Test with carbon mass
139
+ carbon_time = (stretch[-1] * np.sqrt(carbon_mass) + shift[-1]) / 1e-6
140
+ mass = np.interp(carbon_time, time[-1], mass_scale[-1])
141
+ assert np.allclose(carbon_mass, mass, rtol=1e-2)
142
+
143
+
144
+ def test_time_to_mass_zero_correlation_warning(caplog):
145
+ """
146
+ Tests that the time_to_mass function correctly logs a warning if zero correlations
147
+ are found between the TOF and expected mass times array.
148
+ """
149
+ masses = np.asarray([1, 4, 9])
150
+ # Create a time of flight array that will result in no correlation between the
151
+ # Expected tof peaks.
152
+ tof = np.zeros((10, 8000))
153
+ time = np.tile(np.arange(len(tof[0])), (10, 1))
154
+ with caplog.at_level("WARNING"):
155
+ time_to_mass(tof, time, masses)
156
+
157
+ assert any(
158
+ "There are no correlations found between the"
159
+ " TOF array and the expected mass times array" in message
160
+ for message in caplog.text.splitlines()
161
+ )
162
+
163
+
164
+ def test_calculate_kappa():
165
+ """Tests the functionality of calculate_kappa()."""
166
+ # Create a 2d list of peak indices
167
+ peaks = [[0, 1], [1, 2], [0, 1, 2]]
168
+
169
+ # Create mass_scales array
170
+ mass_scales = np.array(
171
+ [
172
+ [1.2, 2.2, 3.2], # The kappa value for peaks 0,1 should be .2
173
+ [1.4, 2.4, 3.4], # The kappa value for peaks 1,2 should be .4
174
+ [1.7, 2.7, 3.7], # The kappa value for peaks 2,3,4 should be -0.3
175
+ ]
176
+ )
177
+ kappas = calculate_kappa(mass_scales, peaks)
178
+
179
+ assert np.allclose(list(kappas), [0.2, 0.4, -0.3], rtol=1e-12)
180
+
181
+
182
+ def test_calculate_snr():
183
+ """Tests the functionality of calculate_snr()."""
184
+ step = 0.5
185
+ max_tof = 10
186
+ time = np.arange(BaselineNoiseTime.START, 5, step)
187
+
188
+ # Create a baseline noise array with an std of 1 and mean of 1
189
+ baseline_noise = np.asarray([0, 0, 1, 2, 2])
190
+ signal_length = len(time) - len(baseline_noise)
191
+ tof_signal = np.full(int(signal_length), max_tof)
192
+
193
+ tof = np.tile(np.append(baseline_noise, tof_signal), (3, 1))
194
+ time = np.tile(time, (3, 1))
195
+
196
+ snr = calculate_snr(tof, time)
197
+
198
+ # Since std=1 and mean=1, SNR should be (max_tof - mean)/std
199
+ assert np.all(snr == (max_tof - 1))
200
+
201
+
202
+ def test_calculate_snr_warning(caplog):
203
+ """Tests that calculate_snr() throws warning if no baseline noise is found."""
204
+ time = np.tile(np.arange(10), (3, 1))
205
+ tof = np.ones_like(time)
206
+
207
+ with caplog.at_level("WARNING"):
208
+ calculate_snr(tof, time)
209
+ assert any(
210
+ "Unable to find baseline noise" in message
211
+ for message in caplog.text.splitlines()
212
+ )
213
+
214
+
215
+ def test_analyze_peaks_warning(caplog):
216
+ """Tests that analyze_peaks() throws warning if the emg curve fit fails."""
217
+ # Create a 2d list of peak indices
218
+ peaks = [[2]]
219
+ time = xr.DataArray(np.arange(6))
220
+ # When there is a flat signal for TOF, we expect the fit to fail and a
221
+ # warning to be logged.
222
+ tof = np.ones_like(time)
223
+ mass_scale = np.ones_like(time)
224
+ with caplog.at_level("WARNING"):
225
+ fit_params, area_under_curve, chisqr, redchi = analyze_peaks(
226
+ tof, time, mass_scale, 0, peaks
227
+ )
228
+ assert any(
229
+ "Failed to fit EMG curve" in message for message in caplog.text.splitlines()
230
+ )
231
+
232
+ # The fit_params and area_under_curve arrays should be zero
233
+ assert np.all(fit_params == 0)
234
+ assert np.all(area_under_curve == 0)
235
+ # chi-square and reduced chi-square values should all be np.nan
236
+ np.testing.assert_array_equal(chisqr, np.nan)
237
+ np.testing.assert_array_equal(chisqr, np.nan)
238
+
239
+
240
+ def test_analyze_peaks_perfect_fits():
241
+ """Tests that analyze_peaks() returns the expected fit params and areas."""
242
+ event = 0
243
+ # Create a 2d list of peak indices
244
+ peak_1 = 7
245
+ peak_2 = 25
246
+ peak_3 = 80
247
+ # Create tof array of ones
248
+ time = xr.DataArray(np.arange(100))
249
+ tof = np.zeros(100)
250
+ mass_scale = np.arange(100) + 0.5
251
+ # Only test peaks[0] this function is not vectorized but we pass in the full 2d peak
252
+ # array.
253
+ peaks = [np.asarray([peak_1, peak_2, peak_3]), np.asarray([])]
254
+ sigma = 2.0
255
+ lam = 1.0
256
+ k = 1 / (lam * sigma)
257
+ # Create a tof array with an emg curve at each peak
258
+ for peak in peaks[event]:
259
+ # Create a perfect emg curve
260
+ mu = peak - 0.4
261
+ gauss = exponnorm.pdf(time.data, k, mu, sigma)
262
+ tof[peak - 5 : peak + 6] = gauss[peak - 5 : peak + 6]
263
+
264
+ fit_params, area_under_curve, chisqr, redchi = analyze_peaks(
265
+ tof, time, mass_scale, event, peaks
266
+ )
267
+
268
+ for peak in peaks[event]:
269
+ mu = peak - 0.4
270
+ mass = round(mass_scale[round(mu)])
271
+ # Test that the fitted parameters at the mass index match our input parameters
272
+ assert np.allclose(fit_params[mass], np.asarray([mu, sigma, lam]), rtol=1e-12)
273
+ # Test that there is a value greater than zero at this index
274
+ assert area_under_curve[mass] > 0
275
+ # Test the goodness of fit
276
+ assert chisqr < 1e-20
277
+ assert redchi < 1e-20
278
+
279
+
280
+ def test_estimate_dust_mass_no_noise_removal():
281
+ """
282
+ Test that estimate_dust_mass() is fitting the signal properly when there is no
283
+ noise removal.
284
+ """
285
+ # TODO: The IDEX team is iterating on this function and will provide more
286
+ # information soon.
287
+ start_time = -60
288
+ total_low_sampling_microseconds = 126.03 # see algorithm document.
289
+ num_samples = 512
290
+
291
+ # Create realistic low sampling time
292
+ time = xr.DataArray(
293
+ np.linspace(
294
+ start_time, total_low_sampling_microseconds - start_time, num_samples
295
+ )
296
+ )
297
+ signal = xr.DataArray(
298
+ fit_impact(
299
+ time.data,
300
+ time_of_impact=0.0,
301
+ constant_offset=1.0,
302
+ amplitude=10.0,
303
+ rise_time=0.371,
304
+ discharge_time=0.371,
305
+ )
306
+ )
307
+ param, sig_amp, chisqr, redchi, result = estimate_dust_mass(
308
+ time, signal, remove_noise=False
309
+ )
310
+ # Assert that the chi square value indicates a very good fit
311
+ assert chisqr <= 1e-12
312
+
313
+ assert np.allclose(result, signal)
314
+
315
+
316
+ def test_lowpass_filter():
317
+ """
318
+ Tests that the lowpass filter is filtering out high frequency signals.
319
+
320
+ Look at
321
+ https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.filtfilt.html#scipy.signal.filtfilt
322
+ for the source of the testing example.
323
+ """
324
+
325
+ time = np.linspace(-60, 60, 512)
326
+ # Calculate nyquist frequency to help get cutoff.
327
+ # This is the highest frequency that can be captured
328
+ time_between_samples = time[1] - time[0]
329
+ nqf = (1 / time_between_samples) / 2
330
+ # Choose cutoff of 0.125 times the Nyquist frequency
331
+ cutoff = nqf * 0.125
332
+ # Create two signals with different frequencies and combine them
333
+ low_freq = cutoff / 4 # Lower than cutoff
334
+ high_freq = nqf # The nyquist frequency is much higher than the cutoff
335
+ # Create sine signals
336
+ signal_low = np.sin(2 * np.pi * low_freq * time)
337
+ signal_high = np.sin(2 * np.pi * high_freq * time)
338
+ combined_sig = signal_low + signal_high
339
+ # The filter should filter out the high frequency signal
340
+ filtered_sig = butter_lowpass_filter(time, combined_sig, cutoff)
341
+ # Assert that the filtered signal is relatively close to the original low
342
+ # frequency signal.
343
+ np.allclose(filtered_sig, signal_low)
344
+
345
+
346
+ def test_remove_signal_noise():
347
+ """
348
+ Tests that remove_signal_noise() function is filtering out sine wave and linear
349
+ noise due to "microphonics"
350
+ """
351
+ start_time = -60
352
+ total_low_sampling_microseconds = 126.03 # see algorithm document.
353
+ num_samples = 512
354
+
355
+ # Create realistic low sampling time
356
+ time = np.linspace(
357
+ start_time, total_low_sampling_microseconds - start_time, num_samples
358
+ )
359
+
360
+ mask = time <= (start_time + total_low_sampling_microseconds) / 2
361
+ noisy_signal = mock_microphonics_noise(time)
362
+ # Filter signal
363
+ filtered_sig = remove_signal_noise(time, noisy_signal, mask)
364
+
365
+ np.allclose(filtered_sig, np.zeros_like(filtered_sig), atol=1e-2)
366
+
367
+
368
+ def test_remove_signal_noise_no_sine_wave(caplog):
369
+ """
370
+ Tests that remove_signal_noise() function filters linear noise when there is no
371
+ sine wave.
372
+ """
373
+ time = np.linspace(-60, 60, 512)
374
+ # linear signal to create noise
375
+ signal = time * 10
376
+ mask = time <= 0.5
377
+ # Filter signal
378
+ filtered_sig = remove_signal_noise(time, signal, mask)
379
+ # Test that the filtered signal is close to zero
380
+ assert np.allclose(filtered_sig, np.zeros_like(filtered_sig), rtol=1e-24)
381
+
382
+
383
+ def test_chi_square():
384
+ """
385
+ Test that chi_square() function calculates the expected values for an array of given
386
+ residuals.
387
+ """
388
+ residual = 3
389
+ nparams = 2
390
+ exp = np.array([16, 18, 16, 14, 12, 12])
391
+ obs = exp + residual
392
+
393
+ expected_chi_square = np.square(residual) * len(exp)
394
+ expected_red_chi_square = expected_chi_square / (len(exp) - nparams)
395
+
396
+ chisqr, redchi = chi_square(obs, exp, nparams)
397
+
398
+ assert chisqr == expected_chi_square
399
+ assert redchi == expected_red_chi_square
@@ -0,0 +1,93 @@
1
+ """Tests the L2b processing for IDEX data"""
2
+
3
+ import numpy as np
4
+ import pytest
5
+ import xarray as xr
6
+ from numpy.testing import assert_array_equal
7
+
8
+ from imap_processing.idex.idex_l2b import idex_l2b, round_spin_phases
9
+
10
+
11
+ @pytest.fixture
12
+ def l2b_dataset(l2a_dataset: xr.Dataset) -> xr.Dataset:
13
+ """Return a ``xarray`` dataset containing test data.
14
+
15
+ Returns
16
+ -------
17
+ dataset : xr.Dataset
18
+ A ``xarray`` dataset containing the test data
19
+ """
20
+ dataset = idex_l2b(l2a_dataset)
21
+ return dataset
22
+
23
+
24
+ def test_l2b_logical_source(l2b_dataset: xr.Dataset):
25
+ """Tests that the ``idex_l2b`` function generates datasets
26
+ with the expected logical source.
27
+
28
+ Parameters
29
+ ----------
30
+ l2b_dataset : xr.Dataset
31
+ A ``xarray`` dataset containing the test data
32
+ """
33
+ expected_src = "imap_idex_l2b_sci"
34
+ assert l2b_dataset.attrs["Logical_source"] == expected_src
35
+
36
+
37
+ def test_l2a_cdf_variables(l2b_dataset: xr.Dataset):
38
+ """Tests that the ``idex_l2a`` function generates datasets
39
+ with the expected variables.
40
+
41
+ Parameters
42
+ ----------
43
+ l2b_dataset : xr.Dataset
44
+ A ``xarray`` dataset containing the test data
45
+ """
46
+ expected_vars = [
47
+ "epoch",
48
+ "impact_day_of_year",
49
+ "spin_phase_quadrants",
50
+ "target_low_fit_impact_charge",
51
+ "target_low_fit_impact_mass_estimate",
52
+ "target_high_fit_impact_charge",
53
+ "target_high_fit_impact_mass_estimate",
54
+ "ion_grid_fit_impact_charge",
55
+ "ion_grid_fit_impact_mass_estimate",
56
+ ]
57
+
58
+ cdf_vars = l2b_dataset.variables
59
+ for var in expected_vars:
60
+ assert var in cdf_vars
61
+
62
+
63
+ def test_round_spin_phases():
64
+ """Tests that round_spin_phases() produces expected results."""
65
+ spin_phase_angles = xr.DataArray([90, 1, 10, 200, 359, 179, 100])
66
+ expected_quadrants = [90, 0, 0, 180, 0, 180, 90]
67
+
68
+ spin_quadrants = round_spin_phases(spin_phase_angles)
69
+ assert_array_equal(spin_quadrants, expected_quadrants)
70
+
71
+ # Test with a larger number of random values
72
+ spin_phase_angles = np.random.randint(0, 360, 1000)
73
+ spin_quadrants = round_spin_phases(spin_phase_angles)
74
+ unique_quadrants = np.unique(spin_quadrants)
75
+ assert set(unique_quadrants) == {0, 90, 180, 270}
76
+
77
+ # Test values that are exactly halfway between quadrants
78
+ spin_quadrants = round_spin_phases(np.array([45, 135, 225, 315]))
79
+ assert_array_equal(spin_quadrants, [90, 180, 270, 0])
80
+
81
+
82
+ def test_round_spin_phases_warning(caplog):
83
+ """Tests that round_spin_phases() logs expected out of range warning."""
84
+ # The last value in the array should trigger a warning since it is >=360.
85
+ spin_phase_angles = xr.DataArray([90, 1, 10, 200, 360])
86
+
87
+ with caplog.at_level("WARNING"):
88
+ round_spin_phases(spin_phase_angles)
89
+
90
+ assert (
91
+ f"Spin phase angles, {spin_phase_angles.data} "
92
+ f"are outside of the expected spin phase angle range, [0, 360)."
93
+ ) in caplog.text
@@ -15,7 +15,7 @@ def test_lo_l1a():
15
15
  "imap_lo_l1a_histogram",
16
16
  "imap_lo_l1a_de",
17
17
  ]
18
- output_dataset = lo_l1a(dependency, "001")
18
+ output_dataset = lo_l1a(dependency)
19
19
 
20
20
  # Assert
21
21
  for dataset, logical_source in zip(output_dataset, expected_logical_source):
@@ -56,7 +56,7 @@ def test_lo_l1a_dataset():
56
56
  hist_fields_lower = [field.lower() for field in histogram_fields]
57
57
 
58
58
  # Act
59
- output_datasets = lo_l1a(dependency, "001")
59
+ output_datasets = lo_l1a(dependency)
60
60
 
61
61
  # Assert
62
62
  np.testing.assert_array_equal(hist_fields_lower, output_datasets[1].data_vars)
@@ -108,7 +108,7 @@ def test_validate_spin_data():
108
108
  validation_data = validation_data.drop(matching_columns, axis=1)
109
109
 
110
110
  # Act
111
- output_dataset = lo_l1a(dependency, "001")
111
+ output_dataset = lo_l1a(dependency)
112
112
 
113
113
  # Assert
114
114
  for field in spin_fields: