imap-processing 0.11.0__py3-none-any.whl → 0.12.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 (288) hide show
  1. imap_processing/__init__.py +10 -11
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/excel_to_xtce.py +65 -16
  4. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -28
  5. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +365 -42
  6. imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +0 -5
  7. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +10 -11
  8. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +17 -19
  9. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +26 -13
  10. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +106 -116
  11. imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +120 -145
  12. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +14 -0
  13. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +6 -9
  14. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +1 -1
  15. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +0 -12
  16. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +1 -1
  17. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +9 -21
  18. imap_processing/cdf/config/imap_mag_l1a_variable_attrs.yaml +361 -0
  19. imap_processing/cdf/config/imap_mag_l1b_variable_attrs.yaml +160 -0
  20. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +160 -0
  21. imap_processing/cdf/config/imap_spacecraft_global_cdf_attrs.yaml +18 -0
  22. imap_processing/cdf/config/imap_spacecraft_variable_attrs.yaml +40 -0
  23. imap_processing/cdf/config/imap_swapi_global_cdf_attrs.yaml +1 -5
  24. imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +12 -4
  25. imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +16 -2
  26. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +48 -52
  27. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +71 -47
  28. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +2 -14
  29. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +51 -2
  30. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +29 -14
  31. imap_processing/cdf/utils.py +13 -7
  32. imap_processing/cli.py +23 -8
  33. imap_processing/codice/codice_l1a.py +207 -85
  34. imap_processing/codice/constants.py +1322 -568
  35. imap_processing/codice/decompress.py +2 -6
  36. imap_processing/ena_maps/ena_maps.py +480 -116
  37. imap_processing/ena_maps/utils/coordinates.py +19 -0
  38. imap_processing/ena_maps/utils/map_utils.py +14 -17
  39. imap_processing/ena_maps/utils/spatial_utils.py +45 -47
  40. imap_processing/hi/l1a/hi_l1a.py +24 -18
  41. imap_processing/hi/l1a/histogram.py +0 -1
  42. imap_processing/hi/l1a/science_direct_event.py +6 -8
  43. imap_processing/hi/l1b/hi_l1b.py +31 -39
  44. imap_processing/hi/l1c/hi_l1c.py +405 -17
  45. imap_processing/hi/utils.py +58 -12
  46. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt0-factors_20250219_v002.csv +205 -0
  47. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt1-factors_20250219_v002.csv +205 -0
  48. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt2-factors_20250219_v002.csv +205 -0
  49. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt3-factors_20250219_v002.csv +205 -0
  50. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-summed-dt0-factors_20250219_v002.csv +68 -0
  51. imap_processing/hit/hit_utils.py +173 -1
  52. imap_processing/hit/l0/constants.py +20 -11
  53. imap_processing/hit/l0/decom_hit.py +18 -4
  54. imap_processing/hit/l1a/hit_l1a.py +45 -54
  55. imap_processing/hit/l1b/constants.py +317 -0
  56. imap_processing/hit/l1b/hit_l1b.py +367 -18
  57. imap_processing/hit/l2/constants.py +281 -0
  58. imap_processing/hit/l2/hit_l2.py +614 -0
  59. imap_processing/hit/packet_definitions/hit_packet_definitions.xml +1323 -71
  60. imap_processing/ialirt/l0/mag_l0_ialirt_data.py +155 -0
  61. imap_processing/ialirt/l0/parse_mag.py +246 -0
  62. imap_processing/ialirt/l0/process_swe.py +252 -0
  63. imap_processing/ialirt/packet_definitions/ialirt.xml +7 -3
  64. imap_processing/ialirt/packet_definitions/ialirt_mag.xml +115 -0
  65. imap_processing/ialirt/utils/grouping.py +114 -0
  66. imap_processing/ialirt/utils/time.py +29 -0
  67. imap_processing/idex/atomic_masses.csv +22 -0
  68. imap_processing/idex/decode.py +2 -2
  69. imap_processing/idex/idex_constants.py +25 -0
  70. imap_processing/idex/idex_l1a.py +6 -7
  71. imap_processing/idex/idex_l1b.py +4 -31
  72. imap_processing/idex/idex_l2a.py +789 -0
  73. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +39 -33
  74. imap_processing/lo/l0/lo_science.py +6 -0
  75. imap_processing/lo/l1a/lo_l1a.py +0 -1
  76. imap_processing/lo/l1b/lo_l1b.py +177 -25
  77. imap_processing/mag/constants.py +8 -0
  78. imap_processing/mag/imap_mag_sdc-configuration_v001.yaml +6 -0
  79. imap_processing/mag/l0/decom_mag.py +10 -3
  80. imap_processing/mag/l1a/mag_l1a.py +22 -11
  81. imap_processing/mag/l1a/mag_l1a_data.py +28 -3
  82. imap_processing/mag/l1b/mag_l1b.py +190 -48
  83. imap_processing/mag/l1c/interpolation_methods.py +211 -0
  84. imap_processing/mag/l1c/mag_l1c.py +447 -9
  85. imap_processing/quality_flags.py +1 -0
  86. imap_processing/spacecraft/packet_definitions/scid_x252.xml +538 -0
  87. imap_processing/spacecraft/quaternions.py +123 -0
  88. imap_processing/spice/geometry.py +16 -19
  89. imap_processing/spice/repoint.py +120 -0
  90. imap_processing/swapi/l1/swapi_l1.py +4 -0
  91. imap_processing/swapi/l2/swapi_l2.py +0 -1
  92. imap_processing/swe/l1a/swe_l1a.py +47 -8
  93. imap_processing/swe/l1a/swe_science.py +5 -2
  94. imap_processing/swe/l1b/swe_l1b_science.py +103 -56
  95. imap_processing/swe/l2/swe_l2.py +60 -65
  96. imap_processing/swe/packet_definitions/swe_packet_definition.xml +1121 -1
  97. imap_processing/swe/utils/swe_constants.py +63 -0
  98. imap_processing/swe/utils/swe_utils.py +85 -28
  99. imap_processing/tests/ccsds/test_data/expected_output.xml +40 -1
  100. imap_processing/tests/ccsds/test_excel_to_xtce.py +23 -20
  101. imap_processing/tests/cdf/test_data/imap_instrument2_global_cdf_attrs.yaml +0 -2
  102. imap_processing/tests/codice/conftest.py +1 -1
  103. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  104. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  105. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-ialirt_20241110193700_v0.0.0.cdf +0 -0
  106. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-omni_20241110193700_v0.0.0.cdf +0 -0
  107. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
  108. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-priorities_20241110193700_v0.0.0.cdf +0 -0
  109. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-sectored_20241110193700_v0.0.0.cdf +0 -0
  110. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  111. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  112. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  113. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-angular_20241110193700_v0.0.0.cdf +0 -0
  114. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-priority_20241110193700_v0.0.0.cdf +0 -0
  115. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-species_20241110193700_v0.0.0.cdf +0 -0
  116. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
  117. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-angular_20241110193700_v0.0.0.cdf +0 -0
  118. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-priority_20241110193700_v0.0.0.cdf +0 -0
  119. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-species_20241110193700_v0.0.0.cdf +0 -0
  120. imap_processing/tests/codice/test_codice_l1a.py +110 -46
  121. imap_processing/tests/codice/test_decompress.py +4 -4
  122. imap_processing/tests/conftest.py +166 -10
  123. imap_processing/tests/ena_maps/conftest.py +51 -0
  124. imap_processing/tests/ena_maps/test_ena_maps.py +638 -109
  125. imap_processing/tests/ena_maps/test_map_utils.py +66 -43
  126. imap_processing/tests/ena_maps/test_spatial_utils.py +16 -20
  127. imap_processing/tests/hi/data/l0/H45_diag_fee_20250208.bin +0 -0
  128. imap_processing/tests/hi/data/l0/H45_diag_fee_20250208_verify.csv +205 -0
  129. imap_processing/tests/hi/test_hi_l1b.py +12 -15
  130. imap_processing/tests/hi/test_hi_l1c.py +234 -6
  131. imap_processing/tests/hi/test_l1a.py +30 -0
  132. imap_processing/tests/hi/test_science_direct_event.py +1 -1
  133. imap_processing/tests/hi/test_utils.py +24 -2
  134. imap_processing/tests/hit/helpers/l1_validation.py +39 -39
  135. imap_processing/tests/hit/test_data/hskp_sample.ccsds +0 -0
  136. imap_processing/tests/hit/test_data/imap_hit_l0_raw_20100105_v001.pkts +0 -0
  137. imap_processing/tests/hit/test_decom_hit.py +4 -0
  138. imap_processing/tests/hit/test_hit_l1a.py +24 -28
  139. imap_processing/tests/hit/test_hit_l1b.py +304 -40
  140. imap_processing/tests/hit/test_hit_l2.py +454 -0
  141. imap_processing/tests/hit/test_hit_utils.py +112 -2
  142. imap_processing/tests/hit/validation_data/hskp_sample_eu_3_6_2025.csv +89 -0
  143. imap_processing/tests/hit/validation_data/hskp_sample_raw.csv +89 -88
  144. imap_processing/tests/ialirt/test_data/l0/461971383-404.bin +0 -0
  145. imap_processing/tests/ialirt/test_data/l0/461971384-405.bin +0 -0
  146. imap_processing/tests/ialirt/test_data/l0/461971385-406.bin +0 -0
  147. imap_processing/tests/ialirt/test_data/l0/461971386-407.bin +0 -0
  148. imap_processing/tests/ialirt/test_data/l0/461971387-408.bin +0 -0
  149. imap_processing/tests/ialirt/test_data/l0/461971388-409.bin +0 -0
  150. imap_processing/tests/ialirt/test_data/l0/461971389-410.bin +0 -0
  151. imap_processing/tests/ialirt/test_data/l0/461971390-411.bin +0 -0
  152. imap_processing/tests/ialirt/test_data/l0/461971391-412.bin +0 -0
  153. imap_processing/tests/ialirt/test_data/l0/sample_decoded_i-alirt_data.csv +383 -0
  154. imap_processing/tests/ialirt/unit/test_grouping.py +81 -0
  155. imap_processing/tests/ialirt/unit/test_parse_mag.py +168 -0
  156. imap_processing/tests/ialirt/unit/test_process_swe.py +208 -3
  157. imap_processing/tests/ialirt/unit/test_time.py +16 -0
  158. imap_processing/tests/idex/conftest.py +62 -6
  159. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231218_v001.pkts +0 -0
  160. imap_processing/tests/idex/test_data/impact_14_tof_high_data.txt +4508 -4508
  161. imap_processing/tests/idex/test_idex_l1a.py +48 -4
  162. imap_processing/tests/idex/test_idex_l1b.py +3 -3
  163. imap_processing/tests/idex/test_idex_l2a.py +383 -0
  164. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20241022_v002.cdf +0 -0
  165. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20241022_v002.cdf +0 -0
  166. imap_processing/tests/lo/test_lo_l1b.py +148 -4
  167. imap_processing/tests/lo/test_lo_science.py +1 -0
  168. imap_processing/tests/mag/conftest.py +69 -0
  169. imap_processing/tests/mag/test_mag_decom.py +1 -1
  170. imap_processing/tests/mag/test_mag_l1a.py +38 -0
  171. imap_processing/tests/mag/test_mag_l1b.py +34 -53
  172. imap_processing/tests/mag/test_mag_l1c.py +251 -20
  173. imap_processing/tests/mag/test_mag_validation.py +109 -25
  174. imap_processing/tests/mag/validation/L1b/T009/MAGScience-normal-(2,2)-8s-20250204-16h39.csv +17 -0
  175. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +16 -16
  176. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +16 -16
  177. imap_processing/tests/mag/validation/L1b/T010/MAGScience-normal-(2,2)-8s-20250206-12h05.csv +17 -0
  178. imap_processing/tests/mag/validation/L1b/T011/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
  179. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +16 -16
  180. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +16 -16
  181. imap_processing/tests/mag/validation/L1b/T012/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
  182. imap_processing/tests/mag/validation/L1b/T012/data.bin +0 -0
  183. imap_processing/tests/mag/validation/L1b/T012/field_like_all_ranges.txt +19200 -0
  184. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-cal.cdf +0 -0
  185. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-in.csv +17 -0
  186. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-magi-out.csv +17 -0
  187. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-mago-out.csv +17 -0
  188. imap_processing/tests/mag/validation/imap_calibration_mag_20240229_v01.cdf +0 -0
  189. imap_processing/tests/spacecraft/__init__.py +0 -0
  190. imap_processing/tests/spacecraft/data/SSR_2024_190_20_08_12_0483851794_2_DA_apid0594_1packet.pkts +0 -0
  191. imap_processing/tests/spacecraft/test_quaternions.py +71 -0
  192. imap_processing/tests/spice/test_data/fake_repoint_data.csv +5 -0
  193. imap_processing/tests/spice/test_geometry.py +6 -9
  194. imap_processing/tests/spice/test_repoint.py +111 -0
  195. imap_processing/tests/swapi/test_swapi_l1.py +7 -3
  196. imap_processing/tests/swe/l0_data/2024051010_SWE_HK_packet.bin +0 -0
  197. imap_processing/tests/swe/l0_data/2024051011_SWE_CEM_RAW_packet.bin +0 -0
  198. imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_APP_HK_20240510_092742.csv +49 -0
  199. imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_CEM_RAW_20240510_092742.csv +593 -0
  200. imap_processing/tests/swe/test_swe_l1a.py +18 -0
  201. imap_processing/tests/swe/test_swe_l1a_cem_raw.py +52 -0
  202. imap_processing/tests/swe/test_swe_l1a_hk.py +68 -0
  203. imap_processing/tests/swe/test_swe_l1b_science.py +23 -4
  204. imap_processing/tests/swe/test_swe_l2.py +112 -30
  205. imap_processing/tests/test_cli.py +2 -2
  206. imap_processing/tests/test_utils.py +138 -16
  207. imap_processing/tests/ultra/data/l0/FM45_UltraFM45_Functional_2024-01-22T0105_20240122T010548.CCSDS +0 -0
  208. imap_processing/tests/ultra/data/l0/ultra45_raw_sc_ultraimgrates_20220530_00.csv +164 -0
  209. 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
  210. imap_processing/tests/ultra/data/mock_data.py +341 -0
  211. imap_processing/tests/ultra/unit/conftest.py +69 -26
  212. imap_processing/tests/ultra/unit/test_badtimes.py +2 -0
  213. imap_processing/tests/ultra/unit/test_cullingmask.py +4 -0
  214. imap_processing/tests/ultra/unit/test_de.py +12 -4
  215. imap_processing/tests/ultra/unit/test_decom_apid_881.py +44 -0
  216. imap_processing/tests/ultra/unit/test_spacecraft_pset.py +78 -0
  217. imap_processing/tests/ultra/unit/test_ultra_l1a.py +28 -12
  218. imap_processing/tests/ultra/unit/test_ultra_l1b.py +34 -6
  219. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +22 -26
  220. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +86 -51
  221. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +94 -52
  222. imap_processing/ultra/l0/decom_tools.py +6 -5
  223. imap_processing/ultra/l1a/ultra_l1a.py +28 -56
  224. imap_processing/ultra/l1b/de.py +72 -28
  225. imap_processing/ultra/l1b/extendedspin.py +12 -14
  226. imap_processing/ultra/l1b/ultra_l1b.py +34 -9
  227. imap_processing/ultra/l1b/ultra_l1b_culling.py +65 -29
  228. imap_processing/ultra/l1b/ultra_l1b_extended.py +64 -19
  229. imap_processing/ultra/l1c/spacecraft_pset.py +86 -0
  230. imap_processing/ultra/l1c/ultra_l1c.py +7 -4
  231. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +112 -61
  232. imap_processing/ultra/lookup_tables/ultra_90_dps_exposure_compressed.cdf +0 -0
  233. imap_processing/ultra/utils/ultra_l1_utils.py +20 -2
  234. imap_processing/utils.py +68 -28
  235. {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/METADATA +8 -5
  236. {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/RECORD +250 -199
  237. imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +0 -237
  238. imap_processing/hi/l1a/housekeeping.py +0 -27
  239. imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-aggregated_20240429_v001.cdf +0 -0
  240. imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-singles_20240429_v001.cdf +0 -0
  241. imap_processing/tests/codice/data/imap_codice_l1a_hi-omni_20240429_v001.cdf +0 -0
  242. imap_processing/tests/codice/data/imap_codice_l1a_hi-sectored_20240429_v001.cdf +0 -0
  243. imap_processing/tests/codice/data/imap_codice_l1a_hskp_20100101_v001.cdf +0 -0
  244. imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-aggregated_20240429_v001.cdf +0 -0
  245. imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-singles_20240429_v001.cdf +0 -0
  246. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-angular_20240429_v001.cdf +0 -0
  247. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-priority_20240429_v001.cdf +0 -0
  248. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-species_20240429_v001.cdf +0 -0
  249. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-angular_20240429_v001.cdf +0 -0
  250. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-priority_20240429_v001.cdf +0 -0
  251. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-species_20240429_v001.cdf +0 -0
  252. imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-aggregated_20240429_v001.cdf +0 -0
  253. imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-singles_20240429_v001.cdf +0 -0
  254. imap_processing/tests/codice/data/imap_codice_l1b_hi-omni_20240429_v001.cdf +0 -0
  255. imap_processing/tests/codice/data/imap_codice_l1b_hi-sectored_20240429_v001.cdf +0 -0
  256. imap_processing/tests/codice/data/imap_codice_l1b_hskp_20100101_v001.cdf +0 -0
  257. imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-aggregated_20240429_v001.cdf +0 -0
  258. imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-singles_20240429_v001.cdf +0 -0
  259. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-angular_20240429_v001.cdf +0 -0
  260. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-priority_20240429_v001.cdf +0 -0
  261. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-species_20240429_v001.cdf +0 -0
  262. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-angular_20240429_v001.cdf +0 -0
  263. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-priority_20240429_v001.cdf +0 -0
  264. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-species_20240429_v001.cdf +0 -0
  265. imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
  266. imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1251.pkts +0 -0
  267. imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1252.pkts +0 -0
  268. imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +0 -89
  269. imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +0 -29
  270. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231214_v001.pkts +0 -0
  271. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20100101_v001.cdf +0 -0
  272. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20100101_v001.cdf +0 -0
  273. imap_processing/tests/ultra/test_data/mock_data.py +0 -161
  274. imap_processing/ultra/l1c/pset.py +0 -40
  275. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_40P_Phi28p5_BeamCal_LinearScan_phi28.50_theta-0.00_20240207T102740.CCSDS +0 -0
  276. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_7P_Phi0.0_BeamCal_LinearScan_phi0.04_theta-0.01_20230821T121304.CCSDS +0 -0
  277. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.CCSDS +0 -0
  278. /imap_processing/tests/ultra/{test_data → data}/l0/Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.CCSDS +0 -0
  279. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_auxdata_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
  280. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_enaphxtofhangimg_FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.csv +0 -0
  281. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultraimgrates_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
  282. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimgevent_FM45_7P_Phi00_BeamCal_LinearScan_phi004_theta-001_20230821T121304.csv +0 -0
  283. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E1.cdf +0 -0
  284. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E12.cdf +0 -0
  285. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E24.cdf +0 -0
  286. {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/LICENSE +0 -0
  287. {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/WHEEL +0 -0
  288. {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/entry_points.txt +0 -0
@@ -22,11 +22,10 @@ def test_idex_cdf_file(decom_test_data: xr.Dataset):
22
22
  decom_test_data : xarray.Dataset
23
23
  The dataset to test with
24
24
  """
25
-
26
25
  file_name = write_cdf(decom_test_data)
27
26
 
28
27
  assert file_name.exists()
29
- assert file_name.name == "imap_idex_l1a_sci_20231214_v001.cdf"
28
+ assert file_name.name == "imap_idex_l1a_sci-1week_20231218_v001.cdf"
30
29
 
31
30
 
32
31
  def test_bad_cdf_attributes(decom_test_data: xr.Dataset):
@@ -41,7 +40,7 @@ def test_bad_cdf_attributes(decom_test_data: xr.Dataset):
41
40
  del decom_test_data["TOF_High"].attrs["CATDESC"]
42
41
 
43
42
  with pytest.raises(ISTPError):
44
- write_cdf(decom_test_data)
43
+ write_cdf(decom_test_data, istp=True, terminate_on_warning=True)
45
44
 
46
45
  # Add attributes back so future tests do not fail
47
46
  decom_test_data["TOF_High"].attrs["CATDESC"] = tof_catdesc
@@ -79,7 +78,7 @@ def test_bad_cdf_file_data(decom_test_data: xr.Dataset):
79
78
  decom_test_data["Bad_data"] = bad_data_xr
80
79
 
81
80
  with pytest.raises(ISTPError):
82
- write_cdf(decom_test_data)
81
+ write_cdf(decom_test_data, istp=True, terminate_on_warning=True)
83
82
 
84
83
  del decom_test_data["Bad_data"]
85
84
 
@@ -104,6 +103,51 @@ def test_idex_tof_high_data_from_cdf(decom_test_data: xr.Dataset):
104
103
  assert (l1_data["TOF_High"][13].data == data).all()
105
104
 
106
105
 
106
+ def test_validate_l1a_idex_data_variables(
107
+ decom_test_data: xr.Dataset, l1a_example_data: xr.Dataset
108
+ ):
109
+ """
110
+ Verify that each of the 6 waveform and telemetry arrays are equal to the
111
+ corresponding array produced by the IDEX team using the same l0 file.
112
+
113
+
114
+ Parameters
115
+ ----------
116
+ decom_test_data : xarray.Dataset
117
+ The dataset to test with
118
+ l1a_example_data: xarray.Dataset
119
+ A dataset containing the 6 waveform and telemetry arrays
120
+ """
121
+ # Lookup table to match the SDS array names to the Idex Team array names
122
+ match_variables = {
123
+ "TOF L": "TOF_Low",
124
+ "TOF H": "TOF_High",
125
+ "TOF M": "TOF_Mid",
126
+ "Target H": "Target_High",
127
+ "Target L": "Target_Low",
128
+ "Ion Grid": "Ion_Grid",
129
+ "Time (high sampling)": "time_high_sample_rate",
130
+ "Time (low sampling)": "time_low_sample_rate",
131
+ }
132
+ # The Engineering data is converting to UTC, and the SDC is converting to J2000,
133
+ # for 'epoch' and 'Timestamp' so this test is using the raw time value 'SCHOARSE' to
134
+ # validate time
135
+ arrays_to_skip = ["Timestamp", "Epoch", "event"]
136
+
137
+ # loop through all keys from the l1a example dict
138
+ for var in l1a_example_data.variables:
139
+ if var not in arrays_to_skip:
140
+ # Find the corresponding array name
141
+ cdf_var = match_variables.get(var, var.lower())
142
+
143
+ np.testing.assert_array_equal(
144
+ decom_test_data[cdf_var],
145
+ l1a_example_data[var],
146
+ f"The array '{cdf_var}' does not equal the expected example "
147
+ f"array '{var}' produced by the IDEX team",
148
+ )
149
+
150
+
107
151
  def test_compressed_packet():
108
152
  """
109
153
  Test compressed data decompression against known non-compressed data.
@@ -65,7 +65,7 @@ def test_l1b_cdf_filenames(l1b_dataset: xr.Dataset):
65
65
  l1b_dataset : xr.Dataset
66
66
  A ``xarray`` dataset containing the test data
67
67
  """
68
- expected_src = "imap_idex_l1b_sci"
68
+ expected_src = "imap_idex_l1b_sci-1week"
69
69
  assert l1b_dataset.attrs["Logical_source"] == expected_src
70
70
 
71
71
 
@@ -81,7 +81,7 @@ def test_idex_cdf_file(l1b_dataset: xr.Dataset):
81
81
  file_name = write_cdf(l1b_dataset)
82
82
 
83
83
  assert file_name.exists()
84
- assert file_name.name == "imap_idex_l1b_sci_20231214_v001.cdf"
84
+ assert file_name.name == "imap_idex_l1b_sci-1week_20231218_v001.cdf"
85
85
 
86
86
 
87
87
  def test_idex_waveform_units(l1b_dataset: xr.Dataset):
@@ -100,7 +100,7 @@ def test_idex_waveform_units(l1b_dataset: xr.Dataset):
100
100
  # Check instrument setting units
101
101
  for _, row in cdf_var_defs.iterrows():
102
102
  var_name = row["mnemonic"]
103
- assert l1b_dataset[var_name].attrs["units"] == row["unit"]
103
+ assert l1b_dataset[var_name].attrs["UNITS"] == row["unit"]
104
104
 
105
105
  # Check waveform units
106
106
  waveform_var_names = [
@@ -0,0 +1,383 @@
1
+ """Tests the L2a processing for IDEX data"""
2
+
3
+ from unittest import mock
4
+
5
+ import numpy as np
6
+ import pytest
7
+ import xarray as xr
8
+ from scipy.stats import exponnorm
9
+
10
+ from imap_processing.idex import idex_constants
11
+ from imap_processing.idex.idex_l1b import idex_l1b
12
+ from imap_processing.idex.idex_l2a import (
13
+ BaselineNoiseTime,
14
+ analyze_peaks,
15
+ butter_lowpass_filter,
16
+ calculate_kappa,
17
+ calculate_snr,
18
+ estimate_dust_mass,
19
+ fit_impact,
20
+ idex_l2a,
21
+ remove_signal_noise,
22
+ time_to_mass,
23
+ )
24
+
25
+
26
+ def mock_microphonics_noise(time: np.ndarray) -> np.ndarray:
27
+ """Function to mock signal noise (linear and sine wave) due to microphonics."""
28
+ noise_frequency = idex_constants.TARGET_NOISE_FREQUENCY
29
+ phase_shift = 45
30
+ amp = 10
31
+ # Create a sine wave signal
32
+ sine_signal = amp * np.sin(2 * np.pi * noise_frequency * time + phase_shift)
33
+ # Combine the sine wave signals with a linear signal to create noise
34
+ combined_sig = sine_signal + (time * 5)
35
+
36
+ return combined_sig
37
+
38
+
39
+ @pytest.fixture(scope="module")
40
+ def l2a_dataset(decom_test_data: xr.Dataset) -> xr.Dataset:
41
+ """Return a ``xarray`` dataset containing test data.
42
+
43
+ Returns
44
+ -------
45
+ dataset : xr.Dataset
46
+ A ``xarray`` dataset containing the test data
47
+ """
48
+ with mock.patch("imap_processing.idex.idex_l1b.get_spice_data", return_value={}):
49
+ dataset = idex_l2a(
50
+ idex_l1b(decom_test_data, data_version="001"), data_version="001"
51
+ )
52
+ return dataset
53
+
54
+
55
+ def test_l2a_cdf_filenames(l2a_dataset: xr.Dataset):
56
+ """Tests that the ``idex_l2a`` function generates datasets
57
+ with the expected logical source.
58
+
59
+ Parameters
60
+ ----------
61
+ l2a_dataset : xr.Dataset
62
+ A ``xarray`` dataset containing the test data
63
+ """
64
+ expected_src = "imap_idex_l2a_sci-1week"
65
+ assert l2a_dataset.attrs["Logical_source"] == expected_src
66
+
67
+
68
+ def test_l2a_cdf_variables(l2a_dataset: xr.Dataset):
69
+ """Tests that the ``idex_l2a`` function generates datasets
70
+ with the expected variables.
71
+
72
+ Parameters
73
+ ----------
74
+ l2a_dataset : xr.Dataset
75
+ A ``xarray`` dataset containing the test data
76
+ """
77
+ expected_vars = [
78
+ "mass",
79
+ "target_low_fit_parameters",
80
+ "target_low_fit_imapct_charge",
81
+ "target_low_fit_imapct_mass_estimate",
82
+ "target_low_chi_squared",
83
+ "target_low_reduced_chi_squared",
84
+ "target_low_fit_results",
85
+ "target_high_fit_parameters",
86
+ "target_high_fit_imapct_charge",
87
+ "target_high_fit_imapct_mass_estimate",
88
+ "target_high_chi_squared",
89
+ "target_high_reduced_chi_squared",
90
+ "target_high_fit_results",
91
+ "ion_grid_fit_parameters",
92
+ "ion_grid_fit_imapct_charge",
93
+ "ion_grid_fit_imapct_mass_estimate",
94
+ "ion_grid_chi_squared",
95
+ "ion_grid_reduced_chi_squared",
96
+ "ion_grid_fit_results",
97
+ ]
98
+
99
+ cdf_vars = l2a_dataset.variables
100
+ for var in expected_vars:
101
+ assert var in cdf_vars
102
+
103
+
104
+ def test_time_to_mass_zero_lag():
105
+ """
106
+ Tests that the time_to_mass function correctly converts time-of-flight
107
+ to a mass scale using known peak positions.
108
+ """
109
+ carbon_mass = 12
110
+ masses = np.asarray([1, 4, 9])
111
+
112
+ expected_lag = 10
113
+ expected_stretch = 1500
114
+ # Create a 2d time of flight array exactly where we would expect the peaks to be
115
+ # Each mass should appear at time t = 1400 * sqrt(m) ns
116
+ tof = np.zeros((15, int(np.sqrt(masses[-1]) * expected_stretch + 1 + expected_lag)))
117
+ min_stretch = 1400
118
+ # Mass 1 expected tof
119
+ tof[:-1, min_stretch] = 1
120
+ # Mass 4 expected tof
121
+ tof[:-1, min_stretch * 2] = 1
122
+ # Mass 9 expected tof
123
+ tof[:-1, min_stretch * 3] = 1
124
+ # Change the last TOF array to be shifted and 'stretched'
125
+ # Mass 1 expected tof
126
+ tof[-1, expected_stretch + expected_lag] = 1
127
+ # Mass 4 expected tof
128
+ tof[-1, expected_stretch * 2 + expected_lag] = 1
129
+ # Mass 9 expected tof
130
+ tof[-1, expected_stretch * 3 + expected_lag] = 1
131
+
132
+ time = np.tile(np.arange(len(tof[0])), (15, 1))
133
+ stretch, shift, mass_scale = time_to_mass(tof, time, masses)
134
+
135
+ # Test with carbon mass
136
+ carbon_time = (stretch[0] * np.sqrt(carbon_mass)) / 1e-6 # Convert ms to s
137
+ mass = np.interp(carbon_time, time[0], mass_scale[0])
138
+ assert np.allclose(carbon_mass, mass, rtol=1e-2)
139
+
140
+ # Test shift is zero since peaks are aligned
141
+ assert np.all(shift[:-1] == 0)
142
+ # Test stretch factor matches expected 1400 ns in seconds
143
+ assert np.all(stretch[:-1] == 1400 * 1e-9)
144
+ # Test output shape
145
+ assert mass_scale.shape == time.shape
146
+ # Test that the last shift and stretch are the expected values
147
+ assert shift[-1] == -expected_lag * idex_constants.FM_SAMPLING_RATE
148
+ # Test stretch factor matches expected 1400 ns in seconds
149
+ assert stretch[-1] == expected_stretch * 1e-9
150
+
151
+ # Test with carbon mass
152
+ carbon_time = (stretch[-1] * np.sqrt(carbon_mass) + shift[-1]) / 1e-6
153
+ mass = np.interp(carbon_time, time[-1], mass_scale[-1])
154
+ assert np.allclose(carbon_mass, mass, rtol=1e-2)
155
+
156
+
157
+ def test_time_to_mass_zero_correlation_warning(caplog):
158
+ """
159
+ Tests that the time_to_mass function correctly logs a warning if zero correlations
160
+ are found between the TOF and expected mass times array.
161
+ """
162
+ masses = np.asarray([1, 4, 9])
163
+ # Create a time of flight array that will result in no correlation between the
164
+ # Expected tof peaks.
165
+ tof = np.zeros((10, 8000))
166
+ time = np.tile(np.arange(len(tof[0])), (10, 1))
167
+ with caplog.at_level("WARNING"):
168
+ time_to_mass(tof, time, masses)
169
+
170
+ assert any(
171
+ "There are no correlations found between the"
172
+ " TOF array and the expected mass times array" in message
173
+ for message in caplog.text.splitlines()
174
+ )
175
+
176
+
177
+ def test_calculate_kappa():
178
+ """Tests the functionality of calculate_kappa()."""
179
+ # Create a 2d list of peak indices
180
+ peaks = [[0, 1], [1, 2], [0, 1, 2]]
181
+
182
+ # Create mass_scales array
183
+ mass_scales = np.array(
184
+ [
185
+ [1.2, 2.2, 3.2], # The kappa value for peaks 0,1 should be .2
186
+ [1.4, 2.4, 3.4], # The kappa value for peaks 1,2 should be .4
187
+ [1.7, 2.7, 3.7], # The kappa value for peaks 2,3,4 should be -0.3
188
+ ]
189
+ )
190
+ kappas = calculate_kappa(mass_scales, peaks)
191
+
192
+ assert np.allclose(list(kappas), [0.2, 0.4, -0.3], rtol=1e-12)
193
+
194
+
195
+ def test_calculate_snr():
196
+ """Tests the functionality of calculate_snr()."""
197
+ step = 0.5
198
+ max_tof = 10
199
+ time = np.arange(BaselineNoiseTime.START, 5, step)
200
+
201
+ # Create a baseline noise array with an std of 1 and mean of 1
202
+ baseline_noise = np.asarray([0, 0, 1, 2, 2])
203
+ signal_length = len(time) - len(baseline_noise)
204
+ tof_signal = np.full(int(signal_length), max_tof)
205
+
206
+ tof = np.tile(np.append(baseline_noise, tof_signal), (3, 1))
207
+ time = np.tile(time, (3, 1))
208
+
209
+ snr = calculate_snr(tof, time)
210
+
211
+ # Since std=1 and mean=1, SNR should be (max_tof - mean)/std
212
+ assert np.all(snr == (max_tof - 1))
213
+
214
+
215
+ def test_calculate_snr_warning(caplog):
216
+ """Tests that calculate_snr() throws warning if no baseline noise is found."""
217
+ time = np.tile(np.arange(10), (3, 1))
218
+ tof = np.ones_like(time)
219
+
220
+ with caplog.at_level("WARNING"):
221
+ calculate_snr(tof, time)
222
+ assert any(
223
+ "Unable to find baseline noise" in message
224
+ for message in caplog.text.splitlines()
225
+ )
226
+
227
+
228
+ def test_analyze_peaks_warning(caplog):
229
+ """Tests that analyze_peaks() throws warning if the emg curve fit fails."""
230
+ # Create a 2d list of peak indices
231
+ peaks = [[2]]
232
+ time = xr.DataArray(np.arange(6))
233
+ # When there is a flat signal for TOF, we expect the fit to fail and a
234
+ # warning to be logged.
235
+ tof = np.ones_like(time)
236
+ mass_scale = np.ones_like(time)
237
+ with caplog.at_level("WARNING"):
238
+ fit_params, area_under_curve = analyze_peaks(tof, time, mass_scale, 0, peaks)
239
+ assert any(
240
+ "Failed to fit EMG curve" in message for message in caplog.text.splitlines()
241
+ )
242
+
243
+ # The fit_params and area_under_curve arrays should be zero
244
+ assert np.all(fit_params == 0)
245
+ assert np.all(area_under_curve == 0)
246
+
247
+
248
+ def test_analyze_peaks_perfect_fits():
249
+ """Tests that analyze_peaks() returns the expected fit params and areas."""
250
+ event = 0
251
+ # Create a 2d list of peak indices
252
+ peak_1 = 7
253
+ peak_2 = 25
254
+ peak_3 = 80
255
+ # Create tof array of ones
256
+ time = xr.DataArray(np.arange(100))
257
+ tof = np.zeros(100)
258
+ mass_scale = np.arange(100) + 0.5
259
+ # Only test peaks[0] this function is not vectorized but we pass in the full 2d peak
260
+ # array.
261
+ peaks = [np.asarray([peak_1, peak_2, peak_3]), np.asarray([])]
262
+ sigma = 2.0
263
+ lam = 1.0
264
+ k = 1 / (lam * sigma)
265
+ # Create a tof array with an emg curve at each peak
266
+ for peak in peaks[event]:
267
+ # Create a perfect emg curve
268
+ mu = peak - 0.4
269
+ gauss = exponnorm.pdf(time.data, k, mu, sigma)
270
+ tof[peak - 5 : peak + 6] = gauss[peak - 5 : peak + 6]
271
+
272
+ fit_params, area_under_curve = analyze_peaks(tof, time, mass_scale, event, peaks)
273
+
274
+ for peak in peaks[event]:
275
+ mu = peak - 0.4
276
+ mass = round(mass_scale[round(mu)])
277
+ # Test that the fitted parameters at the mass index match our input parameters
278
+ assert np.allclose(fit_params[mass], np.asarray([mu, sigma, lam]), rtol=1e-12)
279
+ # Test that there is a value greater than zero at this index
280
+ assert area_under_curve[mass] > 0
281
+
282
+
283
+ def test_estimate_dust_mass_no_noise_removal():
284
+ """
285
+ Test that estimate_dust_mass() is fitting the signal properly when there is no
286
+ noise removal.
287
+ """
288
+ # TODO: The IDEX team is iterating on this function and will provide more
289
+ # information soon.
290
+ start_time = -60
291
+ total_low_sampling_microseconds = 126.03 # see algorithm document.
292
+ num_samples = 512
293
+
294
+ # Create realistic low sampling time
295
+ time = xr.DataArray(
296
+ np.linspace(
297
+ start_time, total_low_sampling_microseconds - start_time, num_samples
298
+ )
299
+ )
300
+ signal = xr.DataArray(
301
+ fit_impact(
302
+ time.data,
303
+ time_of_impact=0.0,
304
+ constant_offset=1.0,
305
+ amplitude=10.0,
306
+ rise_time=0.371,
307
+ discharge_time=0.371,
308
+ )
309
+ )
310
+ param, sig_amp, chisqr, redchi, result = estimate_dust_mass(
311
+ time, signal, remove_noise=False
312
+ )
313
+ # Assert that the chi square value indicates a very good fit
314
+ assert chisqr <= 1e-12
315
+
316
+ assert np.allclose(result, signal)
317
+
318
+
319
+ def test_lowpass_filter():
320
+ """
321
+ Tests that the lowpass filter is filtering out high frequency signals.
322
+
323
+ Look at
324
+ https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.filtfilt.html#scipy.signal.filtfilt
325
+ for the source of the testing example.
326
+ """
327
+
328
+ time = np.linspace(-60, 60, 512)
329
+ # Calculate nyquist frequency to help get cutoff.
330
+ # This is the highest frequency that can be captured
331
+ time_between_samples = time[1] - time[0]
332
+ nqf = (1 / time_between_samples) / 2
333
+ # Choose cutoff of 0.125 times the Nyquist frequency
334
+ cutoff = nqf * 0.125
335
+ # Create two signals with different frequencies and combine them
336
+ low_freq = cutoff / 4 # Lower than cutoff
337
+ high_freq = nqf # The nyquist frequency is much higher than the cutoff
338
+ # Create sine signals
339
+ signal_low = np.sin(2 * np.pi * low_freq * time)
340
+ signal_high = np.sin(2 * np.pi * high_freq * time)
341
+ combined_sig = signal_low + signal_high
342
+ # The filter should filter out the high frequency signal
343
+ filtered_sig = butter_lowpass_filter(time, combined_sig, cutoff)
344
+ # Assert that the filtered signal is relatively close to the original low
345
+ # frequency signal.
346
+ np.allclose(filtered_sig, signal_low)
347
+
348
+
349
+ def test_remove_signal_noise():
350
+ """
351
+ Tests that remove_signal_noise() function is filtering out sine wave and linear
352
+ noise due to "microphonics"
353
+ """
354
+ start_time = -60
355
+ total_low_sampling_microseconds = 126.03 # see algorithm document.
356
+ num_samples = 512
357
+
358
+ # Create realistic low sampling time
359
+ time = np.linspace(
360
+ start_time, total_low_sampling_microseconds - start_time, num_samples
361
+ )
362
+
363
+ mask = time <= (start_time + total_low_sampling_microseconds) / 2
364
+ noisy_signal = mock_microphonics_noise(time)
365
+ # Filter signal
366
+ filtered_sig = remove_signal_noise(time, noisy_signal, mask)
367
+
368
+ np.allclose(filtered_sig, np.zeros_like(filtered_sig), atol=1e-2)
369
+
370
+
371
+ def test_remove_signal_noise_no_sine_wave(caplog):
372
+ """
373
+ Tests that remove_signal_noise() function filters linear noise when there is no
374
+ sine wave.
375
+ """
376
+ time = np.linspace(-60, 60, 512)
377
+ # linear signal to create noise
378
+ signal = time * 10
379
+ mask = time <= 0.5
380
+ # Filter signal
381
+ filtered_sig = remove_signal_noise(time, signal, mask)
382
+ # Test that the filtered signal is close to zero
383
+ assert np.allclose(filtered_sig, np.zeros_like(filtered_sig), rtol=1e-24)
@@ -1,18 +1,53 @@
1
1
  from collections import namedtuple
2
2
 
3
+ import numpy as np
4
+ import pytest
5
+ import xarray as xr
6
+
3
7
  from imap_processing import imap_module_directory
4
8
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
5
9
  from imap_processing.cdf.utils import load_cdf
6
- from imap_processing.lo.l1b.lo_l1b import create_datasets, lo_l1b
10
+ from imap_processing.lo.l1b.lo_l1b import (
11
+ convert_start_end_acq_times,
12
+ create_datasets,
13
+ get_avg_spin_durations,
14
+ get_spin_angle,
15
+ initialize_l1b_de,
16
+ lo_l1b,
17
+ set_spin_bin,
18
+ )
19
+
20
+
21
+ @pytest.fixture()
22
+ def dependencies():
23
+ return {
24
+ "imap_lo_l1a_de": load_cdf(
25
+ imap_module_directory
26
+ / "tests/lo/test_cdfs/imap_lo_l1a_de_20241022_v002.cdf"
27
+ ),
28
+ "imap_lo_l1a_spin": load_cdf(
29
+ imap_module_directory
30
+ / "tests/lo/test_cdfs/imap_lo_l1a_spin_20241022_v002.cdf"
31
+ ),
32
+ }
33
+
34
+
35
+ @pytest.fixture()
36
+ def attr_mgr_l1b():
37
+ attr_mgr_l1b = ImapCdfAttributes()
38
+ attr_mgr_l1b.add_instrument_global_attrs(instrument="lo")
39
+ attr_mgr_l1b.add_instrument_variable_attrs(instrument="lo", level="l1b")
40
+ attr_mgr_l1b.add_global_attribute("Data_version", "000")
41
+ return attr_mgr_l1b
7
42
 
8
43
 
9
44
  def test_lo_l1b():
10
45
  # Arrange
11
46
  de_file = (
12
- imap_module_directory / "tests/lo/test_cdfs/imap_lo_l1a_de_20100101_v001.cdf"
47
+ imap_module_directory / "tests/lo/test_cdfs/imap_lo_l1a_de_20241022_v002.cdf"
13
48
  )
14
49
  spin_file = (
15
- imap_module_directory / "tests/lo/test_cdfs/imap_lo_l1a_spin_20100101_v001.cdf"
50
+ imap_module_directory / "tests/lo/test_cdfs/imap_lo_l1a_spin_20241022_v002.cdf"
16
51
  )
17
52
  data = {}
18
53
  for file in [de_file, spin_file]:
@@ -24,7 +59,7 @@ def test_lo_l1b():
24
59
  output_file = lo_l1b(data, "001")
25
60
 
26
61
  # Assert
27
- assert expected_logical_source == output_file.attrs["Logical_source"]
62
+ assert expected_logical_source == output_file[0].attrs["Logical_source"]
28
63
 
29
64
 
30
65
  def test_create_datasets():
@@ -72,3 +107,112 @@ def test_create_datasets():
72
107
  assert dataset.badtime.shape[0] == 3
73
108
  assert len(dataset.esa_step.shape) == 1
74
109
  assert dataset.esa_step.shape[0] == 3
110
+
111
+
112
+ def test_initialize_dataset(dependencies, attr_mgr_l1b):
113
+ # Arrange
114
+ l1a_de = dependencies["imap_lo_l1a_de"]
115
+ logical_source = "imap_lo_l1b_de"
116
+
117
+ # Act
118
+ l1b_de = initialize_l1b_de(l1a_de, attr_mgr_l1b, logical_source)
119
+
120
+ # Assert
121
+ assert l1b_de.attrs["Logical_source"] == logical_source
122
+ assert list(l1b_de.coords.keys()) == []
123
+ assert len(l1b_de.data_vars) == 4
124
+ assert len(l1b_de.coords) == 0
125
+ for l1b_name, l1a_name in {
126
+ "pos": "pos",
127
+ "mode": "mode",
128
+ "absent": "coincidence_type",
129
+ "esa_step": "esa_step",
130
+ }.items():
131
+ assert l1b_name in l1b_de.data_vars
132
+ np.testing.assert_array_equal(l1b_de[l1b_name], l1a_de[l1a_name])
133
+
134
+
135
+ def test_convert_start_end_acq_times():
136
+ # Arrange
137
+ spin = xr.Dataset(
138
+ {
139
+ "acq_start_sec": ("epoch", [1, 2, 3]),
140
+ "acq_start_subsec": ("epoch", [4, 5, 6]),
141
+ "acq_end_sec": ("epoch", [7, 8, 9]),
142
+ "acq_end_subsec": ("epoch", [10, 11, 12]),
143
+ },
144
+ coords={"epoch": [0, 1, 2]},
145
+ )
146
+
147
+ acq_start_expected = xr.DataArray(
148
+ [
149
+ spin["acq_start_sec"][0] + spin["acq_start_subsec"][0] * 1e-6,
150
+ spin["acq_start_sec"][1] + spin["acq_start_subsec"][1] * 1e-6,
151
+ spin["acq_start_sec"][2] + spin["acq_start_subsec"][2] * 1e-6,
152
+ ],
153
+ dims="epoch",
154
+ )
155
+ acq_end_expected = xr.DataArray(
156
+ [
157
+ spin["acq_end_sec"][0] + spin["acq_end_subsec"][0] * 1e-6,
158
+ spin["acq_end_sec"][1] + spin["acq_end_subsec"][1] * 1e-6,
159
+ spin["acq_end_sec"][2] + spin["acq_end_subsec"][2] * 1e-6,
160
+ ],
161
+ dims="epoch",
162
+ )
163
+
164
+ # Act
165
+ acq_start, acq_end = convert_start_end_acq_times(spin)
166
+
167
+ # Assert
168
+ np.testing.assert_array_equal(acq_start.values, acq_start_expected.values)
169
+ np.testing.assert_array_equal(acq_end.values, acq_end_expected.values)
170
+
171
+
172
+ def test_get_avg_spin_durations():
173
+ # Arrange
174
+ acq_start = xr.DataArray([0, 423, 846.2], dims="epoch")
175
+ acq_end = xr.DataArray([422.8, 846, 1269.7], dims="epoch")
176
+ expected_avg_spin_durations = np.array([422.8, 423, 423.5]) / 28
177
+
178
+ # Act
179
+ avg_spin_durations = get_avg_spin_durations(acq_start, acq_end)
180
+
181
+ # Assert
182
+ np.testing.assert_array_equal(avg_spin_durations, expected_avg_spin_durations)
183
+
184
+
185
+ def test_get_spin_angle():
186
+ # Arrange
187
+ de = xr.Dataset(
188
+ {
189
+ "de_count": ("epoch", [2, 3]),
190
+ "de_time": ("direct_event", [0000, 1000, 2000, 3000, 4000]),
191
+ },
192
+ coords={"epoch": [0, 1], "direct_event": [0, 1, 2, 3, 4]},
193
+ )
194
+ spin_angle_expected = np.array([0, 87.89, 175.78, 263.67, 351.56])
195
+
196
+ # Act
197
+ spin_angle = get_spin_angle(de)
198
+
199
+ # Assert
200
+ np.testing.assert_allclose(
201
+ spin_angle,
202
+ spin_angle_expected,
203
+ atol=1e-2,
204
+ err_msg=f"Spin angle: {spin_angle} vs {spin_angle_expected}",
205
+ )
206
+
207
+
208
+ def test_spin_bin():
209
+ # Arrange
210
+ l1b_de = xr.Dataset()
211
+ spin_angle = np.array([0, 50, 150, 250, 365])
212
+ expected_spin_bins = np.array([0, 8, 25, 41, 60])
213
+
214
+ # Act
215
+ l1b_de = set_spin_bin(l1b_de, spin_angle)
216
+
217
+ # Assert
218
+ np.testing.assert_array_equal(l1b_de["spin_bin"], expected_spin_bins)
@@ -261,6 +261,7 @@ def test_combine_segmented_packets(segmented_pkts_fake_data):
261
261
  ),
262
262
  )
263
263
  np.testing.assert_array_equal(dataset["epoch"].values, np.array([0, 10, 30]))
264
+ np.testing.assert_array_equal(dataset["met"].values, np.array([0, 10, 30]))
264
265
 
265
266
 
266
267
  def test_validate_parse_events(sample_data, attr_mgr):