imap-processing 0.9.0__py3-none-any.whl → 0.11.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 (243) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +749 -442
  3. imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +7 -0
  4. imap_processing/cdf/config/imap_glows_l1a_variable_attrs.yaml +8 -2
  5. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +0 -1
  6. imap_processing/cdf/config/imap_glows_l2_variable_attrs.yaml +358 -0
  7. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +59 -25
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +22 -0
  9. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +32 -8
  10. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +94 -5
  11. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +65 -37
  12. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +16 -1
  13. imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +7 -0
  14. imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +14 -14
  15. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +25 -24
  16. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +238 -0
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +100 -92
  18. imap_processing/cdf/utils.py +2 -2
  19. imap_processing/cli.py +45 -9
  20. imap_processing/codice/codice_l1a.py +104 -58
  21. imap_processing/codice/constants.py +111 -155
  22. imap_processing/codice/data/esa_sweep_values.csv +256 -256
  23. imap_processing/codice/data/lo_stepping_values.csv +128 -128
  24. imap_processing/ena_maps/ena_maps.py +519 -0
  25. imap_processing/ena_maps/utils/map_utils.py +145 -0
  26. imap_processing/ena_maps/utils/spatial_utils.py +226 -0
  27. imap_processing/glows/__init__.py +3 -0
  28. imap_processing/glows/ancillary/imap_glows_pipeline_settings_v001.json +52 -0
  29. imap_processing/glows/l1a/glows_l1a.py +72 -14
  30. imap_processing/glows/l1b/glows_l1b.py +2 -1
  31. imap_processing/glows/l1b/glows_l1b_data.py +25 -1
  32. imap_processing/glows/l2/glows_l2.py +324 -0
  33. imap_processing/glows/l2/glows_l2_data.py +156 -51
  34. imap_processing/hi/l1a/science_direct_event.py +57 -51
  35. imap_processing/hi/l1b/hi_l1b.py +43 -28
  36. imap_processing/hi/l1c/hi_l1c.py +225 -42
  37. imap_processing/hi/utils.py +20 -3
  38. imap_processing/hit/l0/constants.py +2 -2
  39. imap_processing/hit/l0/decom_hit.py +1 -1
  40. imap_processing/hit/l1a/hit_l1a.py +94 -13
  41. imap_processing/hit/l1b/hit_l1b.py +158 -9
  42. imap_processing/ialirt/l0/process_codicehi.py +156 -0
  43. imap_processing/ialirt/l0/process_codicelo.py +5 -2
  44. imap_processing/ialirt/packet_definitions/ialirt.xml +28 -20
  45. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +241 -0
  46. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +170 -0
  47. imap_processing/ialirt/packet_definitions/ialirt_swe.xml +258 -0
  48. imap_processing/ialirt/process_ephemeris.py +72 -40
  49. imap_processing/idex/decode.py +241 -0
  50. imap_processing/idex/idex_l1a.py +143 -81
  51. imap_processing/idex/idex_l1b.py +244 -10
  52. imap_processing/lo/l0/lo_science.py +61 -0
  53. imap_processing/lo/l1a/lo_l1a.py +98 -10
  54. imap_processing/lo/l1b/lo_l1b.py +2 -2
  55. imap_processing/lo/l1c/lo_l1c.py +2 -2
  56. imap_processing/lo/packet_definitions/lo_xtce.xml +1082 -9178
  57. imap_processing/mag/l0/decom_mag.py +2 -2
  58. imap_processing/mag/l1a/mag_l1a.py +7 -7
  59. imap_processing/mag/l1a/mag_l1a_data.py +62 -30
  60. imap_processing/mag/l1b/mag_l1b.py +11 -6
  61. imap_processing/quality_flags.py +18 -3
  62. imap_processing/spice/geometry.py +149 -177
  63. imap_processing/spice/kernels.py +26 -26
  64. imap_processing/spice/spin.py +233 -0
  65. imap_processing/spice/time.py +96 -31
  66. imap_processing/swapi/l1/swapi_l1.py +60 -31
  67. imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +363 -384
  68. imap_processing/swe/l1a/swe_l1a.py +8 -3
  69. imap_processing/swe/l1a/swe_science.py +24 -24
  70. imap_processing/swe/l1b/swe_l1b.py +2 -1
  71. imap_processing/swe/l1b/swe_l1b_science.py +181 -122
  72. imap_processing/swe/l2/swe_l2.py +337 -70
  73. imap_processing/swe/utils/swe_utils.py +28 -0
  74. imap_processing/tests/cdf/test_utils.py +2 -2
  75. imap_processing/tests/codice/conftest.py +20 -17
  76. imap_processing/tests/codice/data/validation/imap_codice_l1a_hskp_20241110193622_v0.0.0.cdf +0 -0
  77. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  78. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  79. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-angular_20241110193700_v0.0.0.cdf +0 -0
  80. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-priority_20241110193700_v0.0.0.cdf +0 -0
  81. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-species_20241110193700_v0.0.0.cdf +0 -0
  82. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-angular_20241110193700_v0.0.0.cdf +0 -0
  83. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-priority_20241110193700_v0.0.0.cdf +0 -0
  84. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-species_20241110193700_v0.0.0.cdf +0 -0
  85. imap_processing/tests/codice/test_codice_l0.py +55 -121
  86. imap_processing/tests/codice/test_codice_l1a.py +147 -59
  87. imap_processing/tests/conftest.py +81 -22
  88. imap_processing/tests/ena_maps/test_ena_maps.py +309 -0
  89. imap_processing/tests/ena_maps/test_map_utils.py +286 -0
  90. imap_processing/tests/ena_maps/test_spatial_utils.py +161 -0
  91. imap_processing/tests/glows/conftest.py +7 -1
  92. imap_processing/tests/glows/test_glows_l1a_cdf.py +3 -7
  93. imap_processing/tests/glows/test_glows_l1a_data.py +34 -6
  94. imap_processing/tests/glows/test_glows_l1b_data.py +29 -17
  95. imap_processing/tests/glows/test_glows_l2.py +101 -0
  96. imap_processing/tests/hi/conftest.py +3 -3
  97. imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
  98. imap_processing/tests/hi/data/l1/imap_his_pset-calibration-prod-config_20240101_v001.csv +31 -0
  99. imap_processing/tests/hi/test_hi_l1b.py +14 -9
  100. imap_processing/tests/hi/test_hi_l1c.py +136 -36
  101. imap_processing/tests/hi/test_l1a.py +0 -2
  102. imap_processing/tests/hi/test_science_direct_event.py +18 -14
  103. imap_processing/tests/hi/test_utils.py +16 -11
  104. imap_processing/tests/hit/helpers/__init__.py +0 -0
  105. imap_processing/tests/hit/helpers/l1_validation.py +405 -0
  106. imap_processing/tests/hit/test_data/sci_sample.ccsds +0 -0
  107. imap_processing/tests/hit/test_decom_hit.py +8 -10
  108. imap_processing/tests/hit/test_hit_l1a.py +117 -180
  109. imap_processing/tests/hit/test_hit_l1b.py +149 -55
  110. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -0
  111. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +62 -0
  112. imap_processing/tests/ialirt/test_data/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  113. imap_processing/tests/ialirt/test_data/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  114. imap_processing/tests/ialirt/test_data/l0/eu_SWP_IAL_20240826_152033.csv +644 -0
  115. imap_processing/tests/ialirt/test_data/l0/hi_fsw_view_1_ccsds.bin +0 -0
  116. imap_processing/tests/ialirt/test_data/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +914 -0
  117. imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  118. imap_processing/tests/ialirt/unit/test_process_codicehi.py +106 -0
  119. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +33 -5
  120. imap_processing/tests/ialirt/unit/test_process_swapi.py +85 -0
  121. imap_processing/tests/ialirt/unit/test_process_swe.py +106 -0
  122. imap_processing/tests/idex/conftest.py +29 -1
  123. imap_processing/tests/idex/test_data/compressed_2023_102_14_24_55.pkts +0 -0
  124. imap_processing/tests/idex/test_data/non_compressed_2023_102_14_22_26.pkts +0 -0
  125. imap_processing/tests/idex/test_idex_l0.py +6 -3
  126. imap_processing/tests/idex/test_idex_l1a.py +151 -1
  127. imap_processing/tests/idex/test_idex_l1b.py +124 -2
  128. imap_processing/tests/lo/test_lo_l1a.py +62 -2
  129. imap_processing/tests/lo/test_lo_science.py +85 -0
  130. imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SPIN_EU.csv +2 -0
  131. imap_processing/tests/mag/conftest.py +16 -0
  132. imap_processing/tests/mag/test_mag_decom.py +6 -4
  133. imap_processing/tests/mag/test_mag_l1a.py +36 -7
  134. imap_processing/tests/mag/test_mag_l1b.py +55 -4
  135. imap_processing/tests/mag/test_mag_validation.py +148 -0
  136. imap_processing/tests/mag/validation/L1a/T001/all_p_ones.txt +19200 -0
  137. imap_processing/tests/mag/validation/L1a/T001/mag-l0-l1a-t001-in.bin +0 -0
  138. imap_processing/tests/mag/validation/L1a/T001/mag-l0-l1a-t001-out.csv +17 -0
  139. imap_processing/tests/mag/validation/L1a/T002/all_n_ones.txt +19200 -0
  140. imap_processing/tests/mag/validation/L1a/T002/mag-l0-l1a-t002-in.bin +0 -0
  141. imap_processing/tests/mag/validation/L1a/T002/mag-l0-l1a-t002-out.csv +17 -0
  142. imap_processing/tests/mag/validation/L1a/T003/field_like.txt +19200 -0
  143. imap_processing/tests/mag/validation/L1a/T003/mag-l0-l1a-t003-in.bin +0 -0
  144. imap_processing/tests/mag/validation/L1a/T003/mag-l0-l1a-t003-out.csv +17 -0
  145. imap_processing/tests/mag/validation/L1a/T004/field_like.txt +19200 -0
  146. imap_processing/tests/mag/validation/L1a/T004/mag-l0-l1a-t004-in.bin +0 -0
  147. imap_processing/tests/mag/validation/L1a/T004/mag-l0-l1a-t004-out.csv +17 -0
  148. imap_processing/tests/mag/validation/L1a/T005/field_like_range_change.txt +19200 -0
  149. imap_processing/tests/mag/validation/L1a/T005/mag-l0-l1a-t005-in.bin +0 -0
  150. imap_processing/tests/mag/validation/L1a/T005/mag-l0-l1a-t005-out.csv +17 -0
  151. imap_processing/tests/mag/validation/L1a/T006/hdr_field.txt +19200 -0
  152. imap_processing/tests/mag/validation/L1a/T006/mag-l0-l1a-t006-in.bin +0 -0
  153. imap_processing/tests/mag/validation/L1a/T006/mag-l0-l1a-t006-out.csv +17 -0
  154. imap_processing/tests/mag/validation/L1a/T007/hdr_field_and_range_change.txt +19200 -0
  155. imap_processing/tests/mag/validation/L1a/T007/mag-l0-l1a-t007-in.bin +0 -0
  156. imap_processing/tests/mag/validation/L1a/T007/mag-l0-l1a-t007-out.csv +17 -0
  157. imap_processing/tests/mag/validation/L1a/T008/field_like_range_change.txt +19200 -0
  158. imap_processing/tests/mag/validation/L1a/T008/mag-l0-l1a-t008-in.bin +0 -0
  159. imap_processing/tests/mag/validation/L1a/T008/mag-l0-l1a-t008-out.csv +17 -0
  160. imap_processing/tests/mag/validation/L1b/T009/data.bin +0 -0
  161. imap_processing/tests/mag/validation/L1b/T009/field_like_all_ranges.txt +19200 -0
  162. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-in.csv +17 -0
  163. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +17 -0
  164. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +17 -0
  165. imap_processing/tests/mag/validation/L1b/T010/data.bin +0 -0
  166. imap_processing/tests/mag/validation/L1b/T010/field_like_all_ranges.txt +19200 -0
  167. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-in.csv +17 -0
  168. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-magi-out.csv +17 -0
  169. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-mago-out.csv +17 -0
  170. imap_processing/tests/mag/validation/L1b/T011/data.bin +0 -0
  171. imap_processing/tests/mag/validation/L1b/T011/field_like_all_ranges.txt +19200 -0
  172. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-in.csv +17 -0
  173. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +17 -0
  174. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +17 -0
  175. imap_processing/tests/spice/test_geometry.py +128 -133
  176. imap_processing/tests/spice/test_kernels.py +37 -37
  177. imap_processing/tests/spice/test_spin.py +184 -0
  178. imap_processing/tests/spice/test_time.py +43 -20
  179. imap_processing/tests/swapi/test_swapi_l1.py +11 -10
  180. imap_processing/tests/swapi/test_swapi_l2.py +13 -3
  181. imap_processing/tests/swe/test_swe_l1a.py +1 -1
  182. imap_processing/tests/swe/test_swe_l1b.py +20 -3
  183. imap_processing/tests/swe/test_swe_l1b_science.py +54 -35
  184. imap_processing/tests/swe/test_swe_l2.py +148 -5
  185. imap_processing/tests/test_cli.py +39 -7
  186. imap_processing/tests/test_quality_flags.py +19 -19
  187. imap_processing/tests/test_utils.py +3 -2
  188. imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
  189. imap_processing/tests/ultra/test_data/mock_data.py +161 -0
  190. imap_processing/tests/ultra/unit/conftest.py +73 -0
  191. imap_processing/tests/ultra/unit/test_badtimes.py +58 -0
  192. imap_processing/tests/ultra/unit/test_cullingmask.py +87 -0
  193. imap_processing/tests/ultra/unit/test_de.py +61 -60
  194. imap_processing/tests/ultra/unit/test_ultra_l1a.py +3 -3
  195. imap_processing/tests/ultra/unit/test_ultra_l1b.py +51 -77
  196. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +5 -5
  197. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +114 -0
  198. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +86 -26
  199. imap_processing/tests/ultra/unit/test_ultra_l1c.py +1 -1
  200. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +3 -3
  201. imap_processing/ultra/constants.py +11 -1
  202. imap_processing/ultra/l1a/ultra_l1a.py +2 -2
  203. imap_processing/ultra/l1b/badtimes.py +22 -5
  204. imap_processing/ultra/l1b/cullingmask.py +31 -5
  205. imap_processing/ultra/l1b/de.py +32 -37
  206. imap_processing/ultra/l1b/extendedspin.py +44 -20
  207. imap_processing/ultra/l1b/ultra_l1b.py +21 -22
  208. imap_processing/ultra/l1b/ultra_l1b_culling.py +190 -0
  209. imap_processing/ultra/l1b/ultra_l1b_extended.py +81 -30
  210. imap_processing/ultra/l1c/histogram.py +6 -2
  211. imap_processing/ultra/l1c/pset.py +6 -2
  212. imap_processing/ultra/l1c/ultra_l1c.py +2 -3
  213. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +4 -3
  214. imap_processing/ultra/utils/ultra_l1_utils.py +70 -14
  215. imap_processing/utils.py +2 -2
  216. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/METADATA +7 -2
  217. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/RECORD +235 -152
  218. imap_processing/tests/codice/data/eu_unit_lookup_table.csv +0 -101
  219. imap_processing/tests/codice/data/idle_export_eu.COD_NHK_20230822_122700 2.csv +0 -100
  220. imap_processing/tests/codice/data/idle_export_raw.COD_NHK_20230822_122700.csv +0 -100
  221. imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
  222. imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
  223. imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
  224. imap_processing/tests/ultra/unit/test_spatial_utils.py +0 -125
  225. imap_processing/ultra/utils/spatial_utils.py +0 -221
  226. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_APP_NHK.bin +0 -0
  227. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_APP_NHK.csv +0 -0
  228. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_SCI_CNT.bin +0 -0
  229. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_SCI_DE.bin +0 -0
  230. /imap_processing/tests/hi/{test_data → data}/l0/H90_NHK_20241104.bin +0 -0
  231. /imap_processing/tests/hi/{test_data → data}/l0/H90_sci_cnt_20241104.bin +0 -0
  232. /imap_processing/tests/hi/{test_data → data}/l0/H90_sci_de_20241104.bin +0 -0
  233. /imap_processing/tests/hi/{test_data → data}/l0/README.txt +0 -0
  234. /imap_processing/tests/idex/{imap_idex_l0_raw_20231214_v001.pkts → test_data/imap_idex_l0_raw_20231214_v001.pkts} +0 -0
  235. /imap_processing/tests/idex/{impact_14_tof_high_data.txt → test_data/impact_14_tof_high_data.txt} +0 -0
  236. /imap_processing/tests/mag/{imap_mag_l1a_norm-magi_20251017_v001.cdf → validation/imap_mag_l1a_norm-magi_20251017_v001.cdf} +0 -0
  237. /imap_processing/tests/mag/{mag_l0_test_data.pkts → validation/mag_l0_test_data.pkts} +0 -0
  238. /imap_processing/tests/mag/{mag_l0_test_output.csv → validation/mag_l0_test_output.csv} +0 -0
  239. /imap_processing/tests/mag/{mag_l1_test_data.pkts → validation/mag_l1_test_data.pkts} +0 -0
  240. /imap_processing/tests/mag/{mag_l1a_test_output.csv → validation/mag_l1a_test_output.csv} +0 -0
  241. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/LICENSE +0 -0
  242. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/WHEEL +0 -0
  243. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/entry_points.txt +0 -0
@@ -10,11 +10,19 @@ import xarray as xr
10
10
  from imap_processing import imap_module_directory
11
11
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
12
12
  from imap_processing.cdf.utils import write_cdf
13
- from imap_processing.idex.idex_l1b import idex_l1b, unpack_instrument_settings
13
+ from imap_processing.idex.idex_l1b import (
14
+ get_spice_data,
15
+ get_trigger_mode_and_level,
16
+ idex_l1b,
17
+ unpack_instrument_settings,
18
+ )
19
+ from imap_processing.tests.idex import conftest
20
+ from imap_processing.tests.idex.conftest import get_spice_data_side_effect_func
14
21
 
15
22
 
16
23
  @pytest.fixture(scope="module")
17
- def l1b_dataset(decom_test_data: xr.Dataset) -> xr.Dataset:
24
+ @mock.patch("imap_processing.idex.idex_l1b.get_spice_data")
25
+ def l1b_dataset(mock_get_spice_data, decom_test_data: xr.Dataset) -> xr.Dataset:
18
26
  """Return a ``xarray`` dataset containing test data.
19
27
 
20
28
  Returns
@@ -22,10 +30,32 @@ def l1b_dataset(decom_test_data: xr.Dataset) -> xr.Dataset:
22
30
  dataset : xr.Dataset
23
31
  A ``xarray`` dataset containing the test data
24
32
  """
33
+
34
+ mock_get_spice_data.side_effect = get_spice_data_side_effect_func
35
+
25
36
  dataset = idex_l1b(decom_test_data, data_version="001")
26
37
  return dataset
27
38
 
28
39
 
40
+ @pytest.fixture()
41
+ def mock_spice_functions():
42
+ """Mock spice functions to avoid loading kernels."""
43
+ with (
44
+ mock.patch("imap_processing.idex.idex_l1b.imap_state") as mock_state,
45
+ mock.patch(
46
+ "imap_processing.idex.idex_l1b.instrument_pointing"
47
+ ) as mock_pointing,
48
+ mock.patch("imap_processing.idex.idex_l1b.solar_longitude") as mock_lon,
49
+ ):
50
+ mock_state.side_effect = lambda t, observer: np.ones((len(t), 6))
51
+ mock_pointing.side_effect = lambda t, instrument, to_frame, cartesian: np.ones(
52
+ (len(t), 3)
53
+ )
54
+ mock_lon.side_effect = lambda t, degrees: np.ones(len(t))
55
+
56
+ yield mock_state, mock_pointing, mock_lon
57
+
58
+
29
59
  def test_l1b_cdf_filenames(l1b_dataset: xr.Dataset):
30
60
  """Tests that the ``idex_l1b`` function generates datasets
31
61
  with the expected logical source.
@@ -124,3 +154,95 @@ def test_unpack_instrument_settings():
124
154
 
125
155
  assert np.all(unpacked_dict["test_var0"] == 2)
126
156
  assert np.all(unpacked_dict["test_var1"] == 4)
157
+
158
+
159
+ def test_get_trigger_settings_success(decom_test_data):
160
+ """
161
+ Check that the output to 'get_trigger_mode_and_level' matches expected arrays.
162
+
163
+ Parameters
164
+ ----------
165
+ decom_test_data : xarray.Dataset
166
+ L1a dataset
167
+ """
168
+ # Change the trigger mode and level for the first event to check that output is
169
+ # correct when the modes and levels vary from event to event
170
+ decom_test_data["idx__txhdrmgtrigmode"][0] = 1
171
+ decom_test_data["idx__txhdrhgtrigmode"][0] = 0
172
+
173
+ n_epochs = len(decom_test_data["epoch"])
174
+ trigger_settings = get_trigger_mode_and_level(decom_test_data)
175
+
176
+ expected_modes = np.full(n_epochs, "HGThreshold")
177
+ expected_modes[0] = "MGThreshold"
178
+ expected_levels = np.full(n_epochs, 0.16762)
179
+ expected_levels[0] = 1023.0
180
+
181
+ assert (trigger_settings["triggermode"].data == expected_modes).all(), (
182
+ f"The dict entry 'triggermode' values did not match the expected values: "
183
+ f"{expected_modes}. Found: {trigger_settings['triggermode'].data}"
184
+ )
185
+
186
+ assert (trigger_settings["triggerlevel"].data == expected_levels).all(), (
187
+ f"The dict entry 'triggerlevel' values did not match the expected values: "
188
+ f"{expected_levels}. Found: {trigger_settings['triggerlevels'].data}"
189
+ )
190
+
191
+
192
+ def test_get_trigger_settings_failure(decom_test_data):
193
+ """
194
+ Check that an error is thrown when there are more than one valid trigger for an
195
+ event
196
+
197
+ Parameters
198
+ ----------
199
+ decom_test_data : xarray.Dataset
200
+ L1a dataset
201
+ """
202
+ decom_test_data["idx__txhdrhgtrigmode"][0] = 1
203
+ decom_test_data["idx__txhdrmgtrigmode"][0] = 2
204
+
205
+ error_ms = (
206
+ "Only one channel can trigger a dust event. Please make sure there is "
207
+ "only one valid trigger value per event. This caused Merge Error: "
208
+ "conflicting values for variable 'trigger_mode' on objects to be "
209
+ "combined. You can skip this check by specifying compat='override'."
210
+ )
211
+
212
+ with pytest.raises(ValueError, match=error_ms):
213
+ get_trigger_mode_and_level(decom_test_data)
214
+
215
+
216
+ @pytest.mark.usefixtures("use_fake_spin_data_for_time")
217
+ def test_get_spice_data(
218
+ mock_spice_functions,
219
+ use_fake_spin_data_for_time,
220
+ decom_test_data,
221
+ furnish_kernels,
222
+ ):
223
+ """
224
+ Test the get_spice_data() function.
225
+
226
+ Parameters
227
+ ----------
228
+ decom_test_data : xarray.Dataset
229
+ L1a dataset
230
+ """
231
+ kernels = ["naif0012.tls"]
232
+ times = decom_test_data["shcoarse"].data
233
+ use_fake_spin_data_for_time(np.min(times), np.max(times))
234
+
235
+ # Mock attribute manager variable attrs
236
+ idex_attrs = ImapCdfAttributes()
237
+
238
+ with (
239
+ furnish_kernels(kernels),
240
+ mock.patch.object(idex_attrs, "get_variable_attributes") as mock_attrs,
241
+ ):
242
+ mock_attrs.return_value = {"CATDESC": "Test var"}
243
+
244
+ spice_data = get_spice_data(decom_test_data, idex_attrs)
245
+
246
+ for array in conftest.SPICE_ARRAYS:
247
+ assert array in spice_data
248
+ assert len(spice_data[array]) == len(decom_test_data["epoch"])
@@ -1,4 +1,5 @@
1
1
  import numpy as np
2
+ import pandas as pd
2
3
 
3
4
  from imap_processing import imap_module_directory
4
5
  from imap_processing.lo.l1a.lo_l1a import lo_l1a
@@ -9,7 +10,11 @@ def test_lo_l1a():
9
10
  dependency = (
10
11
  imap_module_directory / "tests/lo/test_pkts/imap_lo_l0_raw_20240803_v002.pkts"
11
12
  )
12
- expected_logical_source = ["imap_lo_l1a_histogram", "imap_lo_l1a_de"]
13
+ expected_logical_source = [
14
+ "imap_lo_l1a_spin",
15
+ "imap_lo_l1a_histogram",
16
+ "imap_lo_l1a_de",
17
+ ]
13
18
  output_dataset = lo_l1a(dependency, "001")
14
19
 
15
20
  # Assert
@@ -54,4 +59,59 @@ def test_lo_l1a_dataset():
54
59
  output_datasets = lo_l1a(dependency, "001")
55
60
 
56
61
  # Assert
57
- np.testing.assert_array_equal(hist_fields_lower, output_datasets[0].data_vars)
62
+ np.testing.assert_array_equal(hist_fields_lower, output_datasets[1].data_vars)
63
+
64
+
65
+ def test_validate_spin_data():
66
+ # Arrange
67
+ dependency = (
68
+ imap_module_directory / "tests/lo/test_pkts/imap_lo_l0_raw_20240803_v002.pkts"
69
+ )
70
+ validation_path = (
71
+ imap_module_directory / "tests/lo/validation_data/"
72
+ "Instrument_FM1_T104_R129_20240803_ILO_SPIN_EU.csv"
73
+ )
74
+ validation_data = pd.read_csv(validation_path)
75
+
76
+ spin_fields = [
77
+ "shcoarse",
78
+ "num_completed",
79
+ "acq_start_sec",
80
+ "acq_start_subsec",
81
+ "acq_end_sec",
82
+ "acq_end_subsec",
83
+ "start_sec_spin",
84
+ "start_subsec_spin",
85
+ "esa_neg_dac_spin",
86
+ "esa_pos_dac_spin",
87
+ "valid_period_spin",
88
+ "valid_phase_spin",
89
+ "period_source_spin",
90
+ ]
91
+ # The validation data contains a duplicate set of columns for the same values.
92
+ # They are formatted as column_prefix_<spin> and column_prefix[<spin>]
93
+ # adding a condition to remove the one with [ before combining those columns into
94
+ # a list
95
+ bad_fields = [col for col in validation_data.columns if "[" in col]
96
+ validation_data = validation_data.drop(bad_fields, axis=1)
97
+
98
+ # The validation contains columns for each of the 28 spins in the packet, so these
99
+ # need to be combined into a single list for the comparison
100
+ for field in spin_fields:
101
+ matching_columns = [
102
+ col for col in validation_data.columns if col.startswith(field.upper())
103
+ ]
104
+ if len(matching_columns) > 1:
105
+ validation_data[field.upper()] = validation_data[
106
+ matching_columns
107
+ ].values.tolist()
108
+ validation_data = validation_data.drop(matching_columns, axis=1)
109
+
110
+ # Act
111
+ output_dataset = lo_l1a(dependency, "001")
112
+
113
+ # Assert
114
+ for field in spin_fields:
115
+ np.testing.assert_array_equal(
116
+ output_dataset[0][field], validation_data[field.upper()].values.tolist()
117
+ )
@@ -8,6 +8,7 @@ from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
8
8
  from imap_processing.lo.l0.lo_apid import LoAPID
9
9
  from imap_processing.lo.l0.lo_science import (
10
10
  combine_segmented_packets,
11
+ organize_spin_data,
11
12
  parse_de_bin,
12
13
  parse_events,
13
14
  parse_fixed_fields,
@@ -115,6 +116,38 @@ def segmented_pkts_fake_data():
115
116
  return dataset
116
117
 
117
118
 
119
+ @pytest.fixture()
120
+ def fake_spin_data():
121
+ dataset = xr.Dataset(
122
+ data_vars=dict(
123
+ num_completed=(["epoch"], np.array([0, 0])),
124
+ acq_start_sec=(["epoch"], np.array([1000000, 2000000])),
125
+ acq_start_subsec=(["epoch"], np.array([1000000, 2000000])),
126
+ acq_end_sec=(["epoch"], np.array([2000000, 3000000])),
127
+ acq_end_subsec=(["epoch"], np.array([2000000, 3000000])),
128
+ )
129
+ )
130
+ spin_fields = [
131
+ "start_sec_spin",
132
+ "start_subsec_spin",
133
+ "esa_neg_dac_spin",
134
+ "esa_pos_dac_spin",
135
+ "valid_period_spin",
136
+ "valid_phase_spin",
137
+ "period_source_spin",
138
+ ]
139
+
140
+ for spin_field in spin_fields:
141
+ packet_fields = [f"{spin_field}_{i}" for i in range(1, 29)]
142
+ fake_array_val = 0
143
+ for packet_field in packet_fields:
144
+ dataset[packet_field] = xr.DataArray(
145
+ np.array([fake_array_val, fake_array_val + 28]), dims="epoch"
146
+ )
147
+ fake_array_val += 1
148
+ return dataset
149
+
150
+
118
151
  @pytest.fixture()
119
152
  def attr_mgr():
120
153
  attr_mgr = ImapCdfAttributes()
@@ -266,3 +299,55 @@ def test_validate_parse_events(sample_data, attr_mgr):
266
299
 
267
300
  assert dataset["de_count"].values == 1998
268
301
  assert dataset["passes"].values == 8
302
+
303
+
304
+ def test_organize_spin_data(fake_spin_data, attr_mgr):
305
+ # Arrange
306
+ data_by_epoch_spin = np.arange(0, 56).reshape(2, 28)
307
+ expected_dataset = xr.Dataset(
308
+ data_vars=dict(
309
+ num_completed=(["epoch"], np.array([0, 0])),
310
+ acq_start_sec=(["epoch"], np.array([1000000, 2000000])),
311
+ acq_start_subsec=(["epoch"], np.array([1000000, 2000000])),
312
+ acq_end_sec=(["epoch"], np.array([2000000, 3000000])),
313
+ acq_end_subsec=(["epoch"], np.array([2000000, 3000000])),
314
+ start_sec_spin=(
315
+ ["epoch", "spin"],
316
+ np.array(data_by_epoch_spin),
317
+ ),
318
+ start_subsec_spin=(
319
+ ["epoch", "spin"],
320
+ np.array(data_by_epoch_spin),
321
+ ),
322
+ esa_neg_dac_spin=(
323
+ ["epoch", "spin"],
324
+ np.array(data_by_epoch_spin),
325
+ ),
326
+ esa_pos_dac_spin=(
327
+ ["epoch", "spin"],
328
+ np.array(data_by_epoch_spin),
329
+ ),
330
+ valid_period_spin=(
331
+ ["epoch", "spin"],
332
+ np.array(data_by_epoch_spin),
333
+ ),
334
+ valid_phase_spin=(
335
+ ["epoch", "spin"],
336
+ np.array(data_by_epoch_spin),
337
+ ),
338
+ period_source_spin=(
339
+ ["epoch", "spin"],
340
+ np.array(data_by_epoch_spin),
341
+ ),
342
+ ),
343
+ coords=dict(
344
+ # acq_start + 1e6 * acq_start_subsec converted to J2000 epoch
345
+ epoch=(["epoch"], np.array([316576067184000000, 317576068184000000]))
346
+ ),
347
+ )
348
+
349
+ # Act
350
+ organized_data = organize_spin_data(fake_spin_data, attr_mgr)
351
+
352
+ # Assert
353
+ xr.testing.assert_equal(organized_data, expected_dataset)
@@ -0,0 +1,2 @@
1
+ SHCOARSE,NUM_COMPLETED,ACQ_START_SEC,ACQ_START_SUBSEC,ACQ_END_SEC,ACQ_END_SUBSEC,START_SEC_SPIN[0],START_SEC_SPIN[1],START_SEC_SPIN[2],START_SEC_SPIN[3],START_SEC_SPIN[4],START_SEC_SPIN[5],START_SEC_SPIN[6],START_SEC_SPIN[7],START_SEC_SPIN[8],START_SEC_SPIN[9],START_SEC_SPIN[10],START_SEC_SPIN[11],START_SEC_SPIN[12],START_SEC_SPIN[13],START_SEC_SPIN[14],START_SEC_SPIN[15],START_SEC_SPIN[16],START_SEC_SPIN[17],START_SEC_SPIN[18],START_SEC_SPIN[19],START_SEC_SPIN[20],START_SEC_SPIN[21],START_SEC_SPIN[22],START_SEC_SPIN[23],START_SEC_SPIN[24],START_SEC_SPIN[25],START_SEC_SPIN[26],START_SEC_SPIN[27],START_SEC_SPIN_1,START_SUBSEC_SPIN[0],START_SUBSEC_SPIN[1],START_SUBSEC_SPIN[2],START_SUBSEC_SPIN[3],START_SUBSEC_SPIN[4],START_SUBSEC_SPIN[5],START_SUBSEC_SPIN[6],START_SUBSEC_SPIN[7],START_SUBSEC_SPIN[8],START_SUBSEC_SPIN[9],START_SUBSEC_SPIN[10],START_SUBSEC_SPIN[11],START_SUBSEC_SPIN[12],START_SUBSEC_SPIN[13],START_SUBSEC_SPIN[14],START_SUBSEC_SPIN[15],START_SUBSEC_SPIN[16],START_SUBSEC_SPIN[17],START_SUBSEC_SPIN[18],START_SUBSEC_SPIN[19],START_SUBSEC_SPIN[20],START_SUBSEC_SPIN[21],START_SUBSEC_SPIN[22],START_SUBSEC_SPIN[23],START_SUBSEC_SPIN[24],START_SUBSEC_SPIN[25],START_SUBSEC_SPIN[26],START_SUBSEC_SPIN[27],START_SUBSEC_SPIN_1,ESA_NEG_DAC_SPIN[0],ESA_NEG_DAC_SPIN[1],ESA_NEG_DAC_SPIN[2],ESA_NEG_DAC_SPIN[3],ESA_NEG_DAC_SPIN[4],ESA_NEG_DAC_SPIN[5],ESA_NEG_DAC_SPIN[6],ESA_NEG_DAC_SPIN[7],ESA_NEG_DAC_SPIN[8],ESA_NEG_DAC_SPIN[9],ESA_NEG_DAC_SPIN[10],ESA_NEG_DAC_SPIN[11],ESA_NEG_DAC_SPIN[12],ESA_NEG_DAC_SPIN[13],ESA_NEG_DAC_SPIN[14],ESA_NEG_DAC_SPIN[15],ESA_NEG_DAC_SPIN[16],ESA_NEG_DAC_SPIN[17],ESA_NEG_DAC_SPIN[18],ESA_NEG_DAC_SPIN[19],ESA_NEG_DAC_SPIN[20],ESA_NEG_DAC_SPIN[21],ESA_NEG_DAC_SPIN[22],ESA_NEG_DAC_SPIN[23],ESA_NEG_DAC_SPIN[24],ESA_NEG_DAC_SPIN[25],ESA_NEG_DAC_SPIN[26],ESA_NEG_DAC_SPIN[27],ESA_NEG_DAC_SPIN_1,ESA_POS_DAC_SPIN[0],ESA_POS_DAC_SPIN[1],ESA_POS_DAC_SPIN[2],ESA_POS_DAC_SPIN[3],ESA_POS_DAC_SPIN[4],ESA_POS_DAC_SPIN[5],ESA_POS_DAC_SPIN[6],ESA_POS_DAC_SPIN[7],ESA_POS_DAC_SPIN[8],ESA_POS_DAC_SPIN[9],ESA_POS_DAC_SPIN[10],ESA_POS_DAC_SPIN[11],ESA_POS_DAC_SPIN[12],ESA_POS_DAC_SPIN[13],ESA_POS_DAC_SPIN[14],ESA_POS_DAC_SPIN[15],ESA_POS_DAC_SPIN[16],ESA_POS_DAC_SPIN[17],ESA_POS_DAC_SPIN[18],ESA_POS_DAC_SPIN[19],ESA_POS_DAC_SPIN[20],ESA_POS_DAC_SPIN[21],ESA_POS_DAC_SPIN[22],ESA_POS_DAC_SPIN[23],ESA_POS_DAC_SPIN[24],ESA_POS_DAC_SPIN[25],ESA_POS_DAC_SPIN[26],ESA_POS_DAC_SPIN[27],ESA_POS_DAC_SPIN_1,VALID_PERIOD_SPIN[0],VALID_PERIOD_SPIN[1],VALID_PERIOD_SPIN[2],VALID_PERIOD_SPIN[3],VALID_PERIOD_SPIN[4],VALID_PERIOD_SPIN[5],VALID_PERIOD_SPIN[6],VALID_PERIOD_SPIN[7],VALID_PERIOD_SPIN[8],VALID_PERIOD_SPIN[9],VALID_PERIOD_SPIN[10],VALID_PERIOD_SPIN[11],VALID_PERIOD_SPIN[12],VALID_PERIOD_SPIN[13],VALID_PERIOD_SPIN[14],VALID_PERIOD_SPIN[15],VALID_PERIOD_SPIN[16],VALID_PERIOD_SPIN[17],VALID_PERIOD_SPIN[18],VALID_PERIOD_SPIN[19],VALID_PERIOD_SPIN[20],VALID_PERIOD_SPIN[21],VALID_PERIOD_SPIN[22],VALID_PERIOD_SPIN[23],VALID_PERIOD_SPIN[24],VALID_PERIOD_SPIN[25],VALID_PERIOD_SPIN[26],VALID_PERIOD_SPIN[27],VALID_PERIOD_SPIN_1,VALID_PHASE_SPIN[0],VALID_PHASE_SPIN[1],VALID_PHASE_SPIN[2],VALID_PHASE_SPIN[3],VALID_PHASE_SPIN[4],VALID_PHASE_SPIN[5],VALID_PHASE_SPIN[6],VALID_PHASE_SPIN[7],VALID_PHASE_SPIN[8],VALID_PHASE_SPIN[9],VALID_PHASE_SPIN[10],VALID_PHASE_SPIN[11],VALID_PHASE_SPIN[12],VALID_PHASE_SPIN[13],VALID_PHASE_SPIN[14],VALID_PHASE_SPIN[15],VALID_PHASE_SPIN[16],VALID_PHASE_SPIN[17],VALID_PHASE_SPIN[18],VALID_PHASE_SPIN[19],VALID_PHASE_SPIN[20],VALID_PHASE_SPIN[21],VALID_PHASE_SPIN[22],VALID_PHASE_SPIN[23],VALID_PHASE_SPIN[24],VALID_PHASE_SPIN[25],VALID_PHASE_SPIN[26],VALID_PHASE_SPIN[27],VALID_PHASE_SPIN_1,PERIOD_SOURCE_SPIN[0],PERIOD_SOURCE_SPIN[1],PERIOD_SOURCE_SPIN[2],PERIOD_SOURCE_SPIN[3],PERIOD_SOURCE_SPIN[4],PERIOD_SOURCE_SPIN[5],PERIOD_SOURCE_SPIN[6],PERIOD_SOURCE_SPIN[7],PERIOD_SOURCE_SPIN[8],PERIOD_SOURCE_SPIN[9],PERIOD_SOURCE_SPIN[10],PERIOD_SOURCE_SPIN[11],PERIOD_SOURCE_SPIN[12],PERIOD_SOURCE_SPIN[13],PERIOD_SOURCE_SPIN[14],PERIOD_SOURCE_SPIN[15],PERIOD_SOURCE_SPIN[16],PERIOD_SOURCE_SPIN[17],PERIOD_SOURCE_SPIN[18],PERIOD_SOURCE_SPIN[19],PERIOD_SOURCE_SPIN[20],PERIOD_SOURCE_SPIN[21],PERIOD_SOURCE_SPIN[22],PERIOD_SOURCE_SPIN[23],PERIOD_SOURCE_SPIN[24],PERIOD_SOURCE_SPIN[25],PERIOD_SOURCE_SPIN[26],PERIOD_SOURCE_SPIN[27],PERIOD_SOURCE_SPIN_1,START_SEC_SPIN_2,START_SUBSEC_SPIN_2,ESA_NEG_DAC_SPIN_2,ESA_POS_DAC_SPIN_2,VALID_PERIOD_SPIN_2,VALID_PHASE_SPIN_2,PERIOD_SOURCE_SPIN_2,START_SEC_SPIN_3,START_SUBSEC_SPIN_3,ESA_NEG_DAC_SPIN_3,ESA_POS_DAC_SPIN_3,VALID_PERIOD_SPIN_3,VALID_PHASE_SPIN_3,PERIOD_SOURCE_SPIN_3,START_SEC_SPIN_4,START_SUBSEC_SPIN_4,ESA_NEG_DAC_SPIN_4,ESA_POS_DAC_SPIN_4,VALID_PERIOD_SPIN_4,VALID_PHASE_SPIN_4,PERIOD_SOURCE_SPIN_4,START_SEC_SPIN_5,START_SUBSEC_SPIN_5,ESA_NEG_DAC_SPIN_5,ESA_POS_DAC_SPIN_5,VALID_PERIOD_SPIN_5,VALID_PHASE_SPIN_5,PERIOD_SOURCE_SPIN_5,START_SEC_SPIN_6,START_SUBSEC_SPIN_6,ESA_NEG_DAC_SPIN_6,ESA_POS_DAC_SPIN_6,VALID_PERIOD_SPIN_6,VALID_PHASE_SPIN_6,PERIOD_SOURCE_SPIN_6,START_SEC_SPIN_7,START_SUBSEC_SPIN_7,ESA_NEG_DAC_SPIN_7,ESA_POS_DAC_SPIN_7,VALID_PERIOD_SPIN_7,VALID_PHASE_SPIN_7,PERIOD_SOURCE_SPIN_7,START_SEC_SPIN_8,START_SUBSEC_SPIN_8,ESA_NEG_DAC_SPIN_8,ESA_POS_DAC_SPIN_8,VALID_PERIOD_SPIN_8,VALID_PHASE_SPIN_8,PERIOD_SOURCE_SPIN_8,START_SEC_SPIN_9,START_SUBSEC_SPIN_9,ESA_NEG_DAC_SPIN_9,ESA_POS_DAC_SPIN_9,VALID_PERIOD_SPIN_9,VALID_PHASE_SPIN_9,PERIOD_SOURCE_SPIN_9,START_SEC_SPIN_10,START_SUBSEC_SPIN_10,ESA_NEG_DAC_SPIN_10,ESA_POS_DAC_SPIN_10,VALID_PERIOD_SPIN_10,VALID_PHASE_SPIN_10,PERIOD_SOURCE_SPIN_10,START_SEC_SPIN_11,START_SUBSEC_SPIN_11,ESA_NEG_DAC_SPIN_11,ESA_POS_DAC_SPIN_11,VALID_PERIOD_SPIN_11,VALID_PHASE_SPIN_11,PERIOD_SOURCE_SPIN_11,START_SEC_SPIN_12,START_SUBSEC_SPIN_12,ESA_NEG_DAC_SPIN_12,ESA_POS_DAC_SPIN_12,VALID_PERIOD_SPIN_12,VALID_PHASE_SPIN_12,PERIOD_SOURCE_SPIN_12,START_SEC_SPIN_13,START_SUBSEC_SPIN_13,ESA_NEG_DAC_SPIN_13,ESA_POS_DAC_SPIN_13,VALID_PERIOD_SPIN_13,VALID_PHASE_SPIN_13,PERIOD_SOURCE_SPIN_13,START_SEC_SPIN_14,START_SUBSEC_SPIN_14,ESA_NEG_DAC_SPIN_14,ESA_POS_DAC_SPIN_14,VALID_PERIOD_SPIN_14,VALID_PHASE_SPIN_14,PERIOD_SOURCE_SPIN_14,START_SEC_SPIN_15,START_SUBSEC_SPIN_15,ESA_NEG_DAC_SPIN_15,ESA_POS_DAC_SPIN_15,VALID_PERIOD_SPIN_15,VALID_PHASE_SPIN_15,PERIOD_SOURCE_SPIN_15,START_SEC_SPIN_16,START_SUBSEC_SPIN_16,ESA_NEG_DAC_SPIN_16,ESA_POS_DAC_SPIN_16,VALID_PERIOD_SPIN_16,VALID_PHASE_SPIN_16,PERIOD_SOURCE_SPIN_16,START_SEC_SPIN_17,START_SUBSEC_SPIN_17,ESA_NEG_DAC_SPIN_17,ESA_POS_DAC_SPIN_17,VALID_PERIOD_SPIN_17,VALID_PHASE_SPIN_17,PERIOD_SOURCE_SPIN_17,START_SEC_SPIN_18,START_SUBSEC_SPIN_18,ESA_NEG_DAC_SPIN_18,ESA_POS_DAC_SPIN_18,VALID_PERIOD_SPIN_18,VALID_PHASE_SPIN_18,PERIOD_SOURCE_SPIN_18,START_SEC_SPIN_19,START_SUBSEC_SPIN_19,ESA_NEG_DAC_SPIN_19,ESA_POS_DAC_SPIN_19,VALID_PERIOD_SPIN_19,VALID_PHASE_SPIN_19,PERIOD_SOURCE_SPIN_19,START_SEC_SPIN_20,START_SUBSEC_SPIN_20,ESA_NEG_DAC_SPIN_20,ESA_POS_DAC_SPIN_20,VALID_PERIOD_SPIN_20,VALID_PHASE_SPIN_20,PERIOD_SOURCE_SPIN_20,START_SEC_SPIN_21,START_SUBSEC_SPIN_21,ESA_NEG_DAC_SPIN_21,ESA_POS_DAC_SPIN_21,VALID_PERIOD_SPIN_21,VALID_PHASE_SPIN_21,PERIOD_SOURCE_SPIN_21,START_SEC_SPIN_22,START_SUBSEC_SPIN_22,ESA_NEG_DAC_SPIN_22,ESA_POS_DAC_SPIN_22,VALID_PERIOD_SPIN_22,VALID_PHASE_SPIN_22,PERIOD_SOURCE_SPIN_22,START_SEC_SPIN_23,START_SUBSEC_SPIN_23,ESA_NEG_DAC_SPIN_23,ESA_POS_DAC_SPIN_23,VALID_PERIOD_SPIN_23,VALID_PHASE_SPIN_23,PERIOD_SOURCE_SPIN_23,START_SEC_SPIN_24,START_SUBSEC_SPIN_24,ESA_NEG_DAC_SPIN_24,ESA_POS_DAC_SPIN_24,VALID_PERIOD_SPIN_24,VALID_PHASE_SPIN_24,PERIOD_SOURCE_SPIN_24,START_SEC_SPIN_25,START_SUBSEC_SPIN_25,ESA_NEG_DAC_SPIN_25,ESA_POS_DAC_SPIN_25,VALID_PERIOD_SPIN_25,VALID_PHASE_SPIN_25,PERIOD_SOURCE_SPIN_25,START_SEC_SPIN_26,START_SUBSEC_SPIN_26,ESA_NEG_DAC_SPIN_26,ESA_POS_DAC_SPIN_26,VALID_PERIOD_SPIN_26,VALID_PHASE_SPIN_26,PERIOD_SOURCE_SPIN_26,START_SEC_SPIN_27,START_SUBSEC_SPIN_27,ESA_NEG_DAC_SPIN_27,ESA_POS_DAC_SPIN_27,VALID_PERIOD_SPIN_27,VALID_PHASE_SPIN_27,PERIOD_SOURCE_SPIN_27,START_SEC_SPIN_28,START_SUBSEC_SPIN_28,ESA_NEG_DAC_SPIN_28,ESA_POS_DAC_SPIN_28,VALID_PERIOD_SPIN_28,VALID_PHASE_SPIN_28,PERIOD_SOURCE_SPIN_28,CHKSUM
2
+ 460400745,0,460400294,7833,460400745,2193,460400295,460400310,460400325,460400340,460400355,460400370,460400385,460400400,460400415,460400430,460400445,460400460,460400490,460400520,460400535,460400550,460400565,460400580,460400595,460400610,460400625,460400640,460400655,460400670,460400685,460400700,460400715,460400730,460400295,54,4485,4494,4469,4480,4459,4534,4444,4520,4497,4509,4486,46,53,4483,4446,4525,4432,4569,4484,4493,4474,4480,4460,4531,4442,4520,4495,54,0,73,73,142,142,272,272,535,535,1032,1032,2113,4095,4095,4095,4095,4095,142,142,272,272,535,535,1032,1032,2113,2113,4095,0,0,73,73,142,142,272,272,535,535,1032,1032,2113,4095,4095,4095,4095,4095,142,142,272,272,535,535,1032,1032,2113,2113,4095,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,460400310,4485,73,73,1,1,0,460400325,4494,73,73,1,1,0,460400340,4469,142,142,1,1,0,460400355,4480,142,142,1,1,0,460400370,4459,272,272,1,1,0,460400385,4534,272,272,1,1,0,460400400,4444,535,535,1,1,0,460400415,4520,535,535,1,1,0,460400430,4497,1032,1032,1,1,0,460400445,4509,1032,1032,1,1,0,460400460,4486,2113,2113,1,1,0,460400490,46,4095,4095,1,1,0,460400520,53,4095,4095,1,1,0,460400535,4483,4095,4095,1,1,0,460400550,4446,4095,4095,1,1,0,460400565,4525,4095,4095,1,1,0,460400580,4432,142,142,1,1,0,460400595,4569,142,142,1,1,0,460400610,4484,272,272,1,1,0,460400625,4493,272,272,1,1,0,460400640,4474,535,535,1,1,0,460400655,4480,535,535,1,1,0,460400670,4460,1032,1032,1,1,0,460400685,4531,1032,1032,1,1,0,460400700,4442,2113,2113,1,1,0,460400715,4520,2113,2113,1,1,0,460400730,4495,4095,4095,1,1,0,2422
@@ -0,0 +1,16 @@
1
+ """Shared modules for MAG tests"""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from imap_processing.mag.l1a.mag_l1a import mag_l1a
8
+
9
+
10
+ @pytest.fixture()
11
+ def validation_l1a():
12
+ current_directory = Path(__file__).parent
13
+ test_file = current_directory / "validation" / "mag_l1_test_data.pkts"
14
+ # Test file contains only normal packets
15
+ l1a = mag_l1a(test_file, "v000")
16
+ return l1a
@@ -20,12 +20,14 @@ def cdf_attrs():
20
20
 
21
21
  def test_mag_decom():
22
22
  current_directory = Path(__file__).parent
23
- burst_test_file = current_directory / "mag_l0_test_data.pkts"
23
+ burst_test_file = current_directory / "validation" / "mag_l0_test_data.pkts"
24
24
  packets = decom_packets(str(burst_test_file))
25
25
 
26
26
  l0 = packets["burst"] + packets["norm"]
27
27
 
28
- expected_output = pd.read_csv(current_directory / "mag_l0_test_output.csv")
28
+ expected_output = pd.read_csv(
29
+ current_directory / "validation" / "mag_l0_test_output.csv"
30
+ )
29
31
  for index, test in enumerate(l0):
30
32
  assert test.ccsds_header.PKT_APID == expected_output["PHAPID"][index]
31
33
  assert test.ccsds_header.SRC_SEQ_CTR == expected_output["PHSEQCNT"][index]
@@ -59,7 +61,7 @@ def test_mag_decom():
59
61
 
60
62
  def test_mag_raw_xarray(cdf_attrs):
61
63
  current_directory = Path(__file__).parent
62
- burst_test_file = current_directory / "mag_l0_test_data.pkts"
64
+ burst_test_file = current_directory / "validation" / "mag_l0_test_data.pkts"
63
65
  packets = decom_packets(str(burst_test_file))
64
66
  l0_norm = packets["norm"]
65
67
  l0_burst = packets["burst"]
@@ -94,7 +96,7 @@ def test_mag_raw_xarray(cdf_attrs):
94
96
 
95
97
  def test_mag_raw_cdf_generation(cdf_attrs):
96
98
  current_directory = Path(__file__).parent
97
- test_file = current_directory / "mag_l0_test_data.pkts"
99
+ test_file = current_directory / "validation" / "mag_l0_test_data.pkts"
98
100
  packets = decom_packets(str(test_file))
99
101
  l0_norm = packets["norm"]
100
102
  l0_burst = packets["burst"]
@@ -12,7 +12,7 @@ from imap_processing.mag.l1a.mag_l1a_data import (
12
12
  MagL1aPacketProperties,
13
13
  TimeTuple,
14
14
  )
15
- from imap_processing.spice.time import met_to_j2000ns
15
+ from imap_processing.spice.time import met_to_ttj2000ns
16
16
 
17
17
 
18
18
  @pytest.fixture()
@@ -303,7 +303,7 @@ def test_different_vector_rates(
303
303
  uncompressed_vector_bytearray, expected_vectors, raw_compressed_vectors
304
304
  ):
305
305
  current_directory = Path(__file__).parent
306
- test_file = current_directory / "mag_l1_test_data.pkts"
306
+ test_file = current_directory / "validation" / "mag_l1_test_data.pkts"
307
307
  # Test file contains only normal packets
308
308
  l0 = decom_packets(test_file)["norm"][0]
309
309
 
@@ -319,7 +319,6 @@ def test_different_vector_rates(
319
319
  )
320
320
  l1 = process_packets([l0])
321
321
  expected_day = np.datetime64("2023-11-30")
322
-
323
322
  assert len(l1["magi"][expected_day].vectors) == 16
324
323
  assert len(l1["mago"][expected_day].vectors) == 32
325
324
 
@@ -402,7 +401,7 @@ def test_padding_uncompressed(expected_vectors):
402
401
 
403
402
  def test_compare_validation_data():
404
403
  current_directory = Path(__file__).parent
405
- test_file = current_directory / "mag_l1_test_data.pkts"
404
+ test_file = current_directory / "validation" / "mag_l1_test_data.pkts"
406
405
  # Test file contains only normal packets
407
406
  l0 = decom_packets(test_file)
408
407
  l1 = process_packets(l0["norm"])
@@ -414,7 +413,9 @@ def test_compare_validation_data():
414
413
  assert len(l1_mago.vectors) == 96
415
414
  assert len(l1_magi.vectors) == 96
416
415
 
417
- validation_data = pd.read_csv(current_directory / "mag_l1a_test_output.csv")
416
+ validation_data = pd.read_csv(
417
+ current_directory / "validation" / "mag_l1a_test_output.csv"
418
+ )
418
419
 
419
420
  # Validation data does not have differing timestamps
420
421
  for index in validation_data.index:
@@ -482,6 +483,7 @@ def test_compressed_vector_data(expected_vectors, raw_compressed_vectors):
482
483
  expected_range_secondary = [3, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 3, 1]
483
484
  # 16 bit width with range section
484
485
  headers = "01000010"
486
+ end_padding = "0000"
485
487
  input_data = np.array(
486
488
  [
487
489
  int(i)
@@ -491,6 +493,7 @@ def test_compressed_vector_data(expected_vectors, raw_compressed_vectors):
491
493
  + padding
492
494
  + range_primary
493
495
  + range_secondary
496
+ + end_padding
494
497
  ],
495
498
  dtype=np.uint8,
496
499
  )
@@ -509,7 +512,33 @@ def test_compressed_vector_data(expected_vectors, raw_compressed_vectors):
509
512
 
510
513
  assert primary_with_range.shape[0] == 16
511
514
  assert secondary_with_range.shape[0] == 16
515
+ assert np.array_equal(primary_with_range, primary_expected)
516
+ assert np.array_equal(secondary_with_range, secondary_expected)
512
517
 
518
+ # testing the case where a spare byte is included at the end.
519
+ end_padding = "000000000000"
520
+
521
+ input_data = np.array(
522
+ [
523
+ int(i)
524
+ for i in headers
525
+ + primary_compressed
526
+ + secondary_compressed
527
+ + padding
528
+ + range_primary
529
+ + range_secondary
530
+ + end_padding
531
+ ],
532
+ dtype=np.uint8,
533
+ )
534
+
535
+ input_data = np.packbits(input_data)
536
+ (primary_with_range, secondary_with_range) = MagL1a.process_compressed_vectors(
537
+ input_data, 16, 16
538
+ )
539
+
540
+ assert primary_with_range.shape[0] == 16
541
+ assert secondary_with_range.shape[0] == 16
513
542
  assert np.array_equal(primary_with_range, primary_expected)
514
543
  assert np.array_equal(secondary_with_range, secondary_expected)
515
544
 
@@ -775,7 +804,7 @@ def test_calculate_vector_time():
775
804
 
776
805
  test_data = MagL1a.calculate_vector_time(test_vectors, test_vecsec, start_time)
777
806
 
778
- converted_start_time_ns = met_to_j2000ns(start_time.to_seconds())
807
+ converted_start_time_ns = met_to_ttj2000ns(start_time.to_seconds())
779
808
 
780
809
  skips_ns = np.timedelta64(int(1 / test_vecsec * 1e9), "ns")
781
810
  expected_data = np.array(
@@ -852,7 +881,7 @@ def test_mag_l1a_data():
852
881
 
853
882
  def test_mag_l1a():
854
883
  current_directory = Path(__file__).parent
855
- test_file = current_directory / "mag_l1_test_data.pkts"
884
+ test_file = current_directory / "validation" / "mag_l1_test_data.pkts"
856
885
 
857
886
  output_data = mag_l1a(test_file, "v001")
858
887
 
@@ -9,6 +9,7 @@ from imap_processing.mag.l1b.mag_l1b import (
9
9
  calibrate_vector,
10
10
  mag_l1b,
11
11
  mag_l1b_processing,
12
+ rescale_vector,
12
13
  )
13
14
 
14
15
 
@@ -38,8 +39,10 @@ def mag_l1a_dataset():
38
39
  compression_flags = xr.DataArray(
39
40
  np.zeros((20, 2), dtype=np.int8), dims=["epoch", "compression"]
40
41
  )
42
+ compression_flags[1, :] = np.array([1, 18], dtype=np.int8)
41
43
 
42
44
  vectors[0, :] = np.array([1, 1, 1, 0])
45
+ vectors[1, :] = np.array([7982, 48671, -68090, 0])
43
46
 
44
47
  output_dataset = xr.Dataset(
45
48
  coords={"epoch": epoch, "direction": direction, "compression": compression},
@@ -60,7 +63,12 @@ def test_mag_processing(mag_l1a_dataset):
60
63
  np.testing.assert_allclose(
61
64
  mag_l1b["vectors"][0].values, [2.2972, 2.2415, 2.2381, 0], atol=1e-4
62
65
  )
63
- np.testing.assert_allclose(mag_l1b["vectors"][1].values, [0, 0, 0, 0])
66
+ np.testing.assert_allclose(
67
+ mag_l1b["vectors"][1].values,
68
+ [4584.1029091, 27238.73161294, -38405.22240195, 0.0],
69
+ )
70
+
71
+ # np.testing.assert_allclose(mag_l1b["vectors"][1].values, [0, 0, 0, 0])
64
72
 
65
73
  assert mag_l1b["vectors"].values.shape == mag_l1a_dataset["vectors"].values.shape
66
74
 
@@ -71,7 +79,6 @@ def test_mag_processing(mag_l1a_dataset):
71
79
  np.testing.assert_allclose(
72
80
  mag_l1b["vectors"][0].values, [2.27538, 2.23416, 2.23682, 0], atol=1e-5
73
81
  )
74
- np.testing.assert_allclose(mag_l1b["vectors"][1].values, [0, 0, 0, 0])
75
82
 
76
83
  assert mag_l1b["vectors"].values.shape == mag_l1a_dataset["vectors"].values.shape
77
84
 
@@ -92,7 +99,9 @@ def test_mag_attributes(mag_l1a_dataset):
92
99
 
93
100
  def test_cdf_output():
94
101
  l1a_cdf = load_cdf(
95
- Path(__file__).parent / "imap_mag_l1a_norm-magi_20251017_v001.cdf"
102
+ Path(__file__).parent
103
+ / "validation"
104
+ / "imap_mag_l1a_norm-magi_20251017_v001.cdf"
96
105
  )
97
106
  l1b_dataset = mag_l1b(l1a_cdf, "v001")
98
107
 
@@ -138,6 +147,19 @@ def test_mag_compression_scale(mag_l1a_dataset):
138
147
  assert np.allclose(output["vectors"].data[3][:3], scaled_vectors)
139
148
 
140
149
 
150
+ def test_rescale_vector():
151
+ # From algo document examples
152
+ vector = np.array([10, -2000, 0])
153
+ expected_vector = np.array([2.5, -500, 0])
154
+ output = rescale_vector(vector, [1, 18])
155
+ assert np.allclose(output, expected_vector)
156
+
157
+ vector = np.array([32766, -2, 1])
158
+ expected_vector = np.array([65532, -4, 2])
159
+ output = rescale_vector(vector, [1, 15])
160
+ assert np.allclose(output, expected_vector)
161
+
162
+
141
163
  def test_calibrate_vector():
142
164
  # from MFOTOURFO
143
165
  cal_array = np.array(
@@ -161,8 +183,37 @@ def test_calibrate_vector():
161
183
  )
162
184
 
163
185
  calibration_matrix = xr.DataArray(cal_array)
186
+ # All cal vector comparisons were calculated by hand and confirmed by MAG team.
164
187
 
165
188
  cal_vector = calibrate_vector(np.array([1.0, 1.0, 1.0, 0]), calibration_matrix)
189
+
166
190
  expected_vector = np.array([2.2972, 2.2415, 2.2381, 0])
167
191
 
168
- assert np.allclose(cal_vector, expected_vector, atol=1e-4)
192
+ assert np.allclose(cal_vector, expected_vector, atol=1e-9)
193
+
194
+ cal_vector = calibrate_vector(np.array([1.1, -2.0, 3.0, 1]), calibration_matrix)
195
+ expected_vector = np.array([(0.081202, -0.144636, 0.217628, 1)])
196
+ assert np.allclose(cal_vector, expected_vector, atol=1e-9)
197
+
198
+ cal_vector = calibrate_vector(
199
+ rescale_vector(np.array([7982, 48671, -68090, 0]), (1, 18)), calibration_matrix
200
+ )
201
+ expected_vector = [4584.1029091, 27238.73161294, -38405.22240195, 0.0]
202
+
203
+ assert np.allclose(cal_vector, expected_vector, atol=1e-9)
204
+
205
+
206
+ def test_l1a_to_l1b(validation_l1a):
207
+ # Convert l1a input validation packet file to l1b
208
+ with pytest.raises(ValueError, match="Raw L1A"):
209
+ mag_l1b(validation_l1a[0], "v000")
210
+
211
+ l1b = [mag_l1b(i, "v000") for i in validation_l1a[1:]]
212
+
213
+ assert len(l1b) == len(validation_l1a) - 1
214
+
215
+ assert l1b[0].attrs["Logical_source"] == "imap_mag_l1b_norm-mago"
216
+ assert l1b[1].attrs["Logical_source"] == "imap_mag_l1b_norm-magi"
217
+
218
+ assert len(l1b[0]["vectors"].data) > 0
219
+ assert len(l1b[1]["vectors"].data) > 0