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
@@ -0,0 +1,184 @@
1
+ from contextlib import nullcontext as does_not_raise
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import pytest
6
+
7
+ from imap_processing.spice.geometry import SpiceFrame
8
+ from imap_processing.spice.spin import (
9
+ get_instrument_spin_phase,
10
+ get_spacecraft_spin_phase,
11
+ get_spin_angle,
12
+ get_spin_data,
13
+ interpolate_spin_data,
14
+ )
15
+
16
+
17
+ @pytest.fixture()
18
+ def fake_spin_data(monkeypatch, spice_test_data_path):
19
+ """Generate fake spin dataframe for testing"""
20
+ fake_spin_path = spice_test_data_path / "fake_spin_data.csv"
21
+ monkeypatch.setenv("SPIN_DATA_FILEPATH", str(fake_spin_path))
22
+ return fake_spin_path
23
+
24
+
25
+ @pytest.mark.parametrize(
26
+ "query_met_times, expected",
27
+ [
28
+ (
29
+ 15,
30
+ [[1, 15, 0, 15.0, 1, 1, 0, 0, 15.0, 0.0]],
31
+ ), # Scalar test at spin start time
32
+ (
33
+ np.array([15.1, 30.2]),
34
+ [
35
+ [1, 15, 0, 15.0, 1, 1, 0, 0, 15.0, 0.1 / 15],
36
+ [2, 30, 0, 15.0, 1, 1, 0, 0, 30.0, 0.2 / 15],
37
+ ],
38
+ ), # Array test
39
+ ],
40
+ )
41
+ def test_interpolate_spin_data(query_met_times, expected, fake_spin_data):
42
+ """Test interpolate_spin_data() with generated spin data."""
43
+ # Call the function
44
+ spin_df = interpolate_spin_data(query_met_times=query_met_times)
45
+
46
+ # Test the value
47
+ for i_row, row in enumerate(expected):
48
+ np.testing.assert_array_almost_equal(spin_df.iloc[i_row].tolist(), row)
49
+
50
+
51
+ @pytest.mark.parametrize(
52
+ "query_met_times, expected",
53
+ [
54
+ (15, 0.0), # Scalar test
55
+ (np.array([15.1, 30.1]), np.array([0.1 / 15, 0.1 / 15])), # Array test
56
+ (np.array([50]), np.array([5 / 15])), # Single element array test
57
+ # The first spin has thruster firing set, but should return valid value
58
+ (5.0, 5 / 15),
59
+ # Test invalid spin period flag causes nan
60
+ (106.0, np.nan),
61
+ # Test invalid spin phase flag causes nans
62
+ (np.array([121, 122, 123]), np.full(3, np.nan)),
63
+ # Test that invalid spin period causes nans
64
+ (np.array([110, 111]), np.full(2, np.nan)),
65
+ # Test for time in missing spin
66
+ (65, np.nan),
67
+ (np.array([65.1, 66]), np.full(2, np.nan)),
68
+ # Combined test
69
+ (
70
+ np.array([7.5, 30, 61, 75, 106, 121, 136]),
71
+ np.array([0.5, 0, np.nan, 0, np.nan, np.nan, 1 / 15]),
72
+ ),
73
+ # Test that this spin phase range [0, 1) is valid which
74
+ # is same as [0, 360) degree angle. At 15 seconds the spacecraft
75
+ # has completed a full spin
76
+ (np.array([0, 15]), np.zeros(2)),
77
+ ],
78
+ )
79
+ def test_get_spacecraft_spin_phase(query_met_times, expected, fake_spin_data):
80
+ """Test get_spacecraft_spin_phase() with generated spin data."""
81
+ # Call the function
82
+ spin_phases = get_spacecraft_spin_phase(query_met_times=query_met_times)
83
+
84
+ # Test the returned type
85
+ if isinstance(expected, float):
86
+ assert isinstance(spin_phases, float), "Spin phase must be a float."
87
+ elif expected is None:
88
+ assert len(spin_phases) == 0, "Spin phase must be empty."
89
+ else:
90
+ assert spin_phases.shape == expected.shape
91
+ # Test the value
92
+ np.testing.assert_array_almost_equal(spin_phases, expected)
93
+
94
+
95
+ @pytest.mark.parametrize(
96
+ "spin_phases, degrees, expected, context",
97
+ [
98
+ (np.arange(0, 1, 0.1), True, np.arange(0, 1, 0.1) * 360, does_not_raise()),
99
+ (
100
+ np.arange(0, 1, 0.1),
101
+ False,
102
+ np.arange(0, 1, 0.1) * 2 * np.pi,
103
+ does_not_raise(),
104
+ ),
105
+ (
106
+ np.array([0, 1]),
107
+ True,
108
+ None,
109
+ pytest.raises(ValueError, match="Spin phases *"),
110
+ ),
111
+ (
112
+ np.array([-1, 0]),
113
+ False,
114
+ None,
115
+ pytest.raises(ValueError, match="Spin phases *"),
116
+ ),
117
+ ],
118
+ )
119
+ def test_get_spin_angle(spin_phases, degrees, expected, context):
120
+ """Test get_spin_angle() with fake spin phases."""
121
+ with context:
122
+ spin_angles = get_spin_angle(spin_phases, degrees=degrees)
123
+ np.testing.assert_array_equal(spin_angles, expected)
124
+
125
+
126
+ @pytest.mark.parametrize("query_met_times", [-1, 165])
127
+ def test_get_spacecraft_spin_phase_value_error(query_met_times, fake_spin_data):
128
+ """Test get_spacecraft_spin_phase() for raising ValueError."""
129
+ with pytest.raises(ValueError, match="Query times"):
130
+ _ = get_spacecraft_spin_phase(query_met_times)
131
+
132
+
133
+ @pytest.mark.usefixtures("use_fake_spin_data_for_time")
134
+ def test_get_spin_data(use_fake_spin_data_for_time):
135
+ """Test get_spin_data() with generated spin data."""
136
+ use_fake_spin_data_for_time(453051323.0 - 56120)
137
+ spin_data = get_spin_data()
138
+
139
+ (
140
+ np.testing.assert_array_equal(spin_data["spin_number"], np.arange(5761)),
141
+ "One day should have 5,761 records of 15 seconds when including end_met.",
142
+ )
143
+ assert isinstance(spin_data, pd.DataFrame), "Return type must be pandas.DataFrame."
144
+
145
+ assert set(spin_data.columns) == {
146
+ "spin_number",
147
+ "spin_start_sec",
148
+ "spin_start_subsec",
149
+ "spin_period_sec",
150
+ "spin_period_valid",
151
+ "spin_phase_valid",
152
+ "spin_period_source",
153
+ "thruster_firing",
154
+ "spin_start_time",
155
+ }, "Spin data must have the specified fields."
156
+
157
+
158
+ @pytest.mark.parametrize(
159
+ "instrument",
160
+ [
161
+ SpiceFrame.IMAP_LO,
162
+ SpiceFrame.IMAP_HI_45,
163
+ SpiceFrame.IMAP_HI_90,
164
+ SpiceFrame.IMAP_ULTRA_45,
165
+ SpiceFrame.IMAP_ULTRA_90,
166
+ SpiceFrame.IMAP_SWAPI,
167
+ SpiceFrame.IMAP_IDEX,
168
+ SpiceFrame.IMAP_CODICE,
169
+ SpiceFrame.IMAP_HIT,
170
+ SpiceFrame.IMAP_SWE,
171
+ SpiceFrame.IMAP_GLOWS,
172
+ SpiceFrame.IMAP_MAG,
173
+ ],
174
+ )
175
+ def test_get_instrument_spin_phase(instrument, fake_spin_data):
176
+ """Test coverage for get_instrument_spin_phase()"""
177
+ met_times = np.array([7.5, 30, 61, 75, 106, 121, 136])
178
+ expected_nan_mask = np.array([False, False, True, False, True, True, False])
179
+ inst_phase = get_instrument_spin_phase(met_times, instrument)
180
+ assert inst_phase.shape == met_times.shape
181
+ np.testing.assert_array_equal(np.isnan(inst_phase), expected_nan_mask)
182
+ assert np.logical_and(
183
+ 0 <= inst_phase[~expected_nan_mask], inst_phase[~expected_nan_mask] < 1
184
+ ).all()
@@ -7,14 +7,15 @@ import spiceypy
7
7
  from imap_processing.spice import IMAP_SC_ID
8
8
  from imap_processing.spice.time import (
9
9
  TICK_DURATION,
10
- _sct2e_wrapper,
11
10
  et_to_utc,
12
- j2000ns_to_j2000s,
13
11
  met_to_datetime64,
14
- met_to_j2000ns,
15
12
  met_to_sclkticks,
13
+ met_to_ttj2000ns,
16
14
  met_to_utc,
15
+ sct_to_et,
16
+ sct_to_ttj2000s,
17
17
  str_to_et,
18
+ ttj2000ns_to_et,
18
19
  )
19
20
 
20
21
 
@@ -27,10 +28,11 @@ def test_met_to_sclkticks(met):
27
28
  np.testing.assert_array_equal(ticks, expected)
28
29
 
29
30
 
30
- def test_met_to_j2000ns(furnish_time_kernels):
31
- """Test coverage for met_to_j2000ns function."""
31
+ def test_met_to_ttj2000ns(furnish_time_kernels):
32
+ """Test coverage for met_to_ttj2000ns function."""
32
33
  utc = "2026-01-01T00:00:00.125"
33
34
  et = spiceypy.str2et(utc)
35
+ spicey_tt = spiceypy.unitim(et, "ET", "TT")
34
36
  sclk_str = spiceypy.sce2s(IMAP_SC_ID, et)
35
37
  seconds, ticks = sclk_str.split("/")[1].split(":")
36
38
  # There is some floating point error calculating tick duration from 1 clock
@@ -39,26 +41,30 @@ def test_met_to_j2000ns(furnish_time_kernels):
39
41
  spiceypy.sct2e(IMAP_SC_ID, 1e12) - spiceypy.sct2e(IMAP_SC_ID, 0)
40
42
  ) / 1e12
41
43
  met = float(seconds) + float(ticks) * spice_tick_duration
42
- j2000ns = met_to_j2000ns(met)
43
- assert j2000ns.dtype == np.int64
44
- np.testing.assert_array_equal(j2000ns, np.array(et * 1e9))
44
+ tt = met_to_ttj2000ns(met)
45
+ assert tt.dtype == np.int64
46
+ np.testing.assert_array_equal(tt, np.array(spicey_tt * 1e9))
45
47
 
46
48
 
47
- def test_j2000ns_to_j2000s(furnish_time_kernels):
48
- """Test coverage for j2000ns_to_j2000s function."""
49
+ def test_ttj2000ns_to_et(furnish_time_kernels):
50
+ """Test coverage for ttj2000ns_to_et function."""
49
51
  # Use spice to come up with reasonable J2000 values
50
52
  utc = "2025-09-23T00:00:00.000"
51
53
  # Test single value input
52
54
  et = spiceypy.str2et(utc)
53
- epoch = int(et * 1e9)
54
- j2000s = j2000ns_to_j2000s(epoch)
55
+ epoch = int(spiceypy.unitim(et, "ET", "TT") * 1e9)
56
+ j2000s = ttj2000ns_to_et(epoch)
55
57
  assert j2000s == et
58
+ # Test for bug when spiceypy tries to iterate over 0-d array returned by
59
+ # np.vectorize for the scalar case
60
+ assert not spiceypy.support_types.is_iterable(et)
56
61
  # Test array input
57
- epoch = (np.arange(et, et + 10000, 100) * 1e9).astype(np.int64)
58
- j2000s = j2000ns_to_j2000s(epoch)
59
- np.testing.assert_array_equal(
60
- j2000s, np.arange(et, et + 10000, 100, dtype=np.float64)
62
+ ets = np.arange(et, et + 10000, 100)
63
+ epoch = np.array([spiceypy.unitim(et, "ET", "TT") * 1e9 for et in ets]).astype(
64
+ np.int64
61
65
  )
66
+ j2000s = ttj2000ns_to_et(epoch)
67
+ np.testing.assert_array_equal(j2000s, ets)
62
68
 
63
69
 
64
70
  @pytest.mark.parametrize(
@@ -106,26 +112,42 @@ def test_met_to_datetime64(furnish_time_kernels, utc):
106
112
  et_arr = spiceypy.str2et(utc)
107
113
  sclk_ticks = np.array([spiceypy.sce2c(IMAP_SC_ID, et) for et in et_arr])
108
114
  else:
109
- expected_dt64 = np.asarray(np.datetime64(utc))
115
+ expected_dt64 = np.datetime64(utc)
110
116
  et = spiceypy.str2et(utc)
111
117
  sclk_ticks = spiceypy.sce2c(IMAP_SC_ID, et)
112
118
  met = sclk_ticks * TICK_DURATION
113
119
  dt64 = met_to_datetime64(met)
120
+
121
+ if isinstance(utc, list):
122
+ assert isinstance(dt64, np.ndarray)
123
+ assert dt64.dtype.name == "datetime64[ns]"
124
+ else:
125
+ assert isinstance(dt64, np.datetime64)
114
126
  np.testing.assert_array_equal(
115
127
  dt64.astype("datetime64[us]"), expected_dt64.astype("datetime64[us]")
116
128
  )
117
129
 
118
130
 
119
131
  @pytest.mark.parametrize("sclk_ticks", [0.0, np.arange(10)])
120
- def test_sct2e_wrapper(sclk_ticks):
121
- """Test for `_sct2e_wrapper` function."""
122
- et = _sct2e_wrapper(sclk_ticks)
132
+ def test_sct_to_et(sclk_ticks):
133
+ """Test for `sct_to_et` function."""
134
+ et = sct_to_et(sclk_ticks)
123
135
  if isinstance(sclk_ticks, float):
124
136
  assert isinstance(et, float)
125
137
  else:
126
138
  assert len(et) == len(sclk_ticks)
127
139
 
128
140
 
141
+ @pytest.mark.parametrize("sclk_ticks", [0.0, np.arange(10)])
142
+ def test_sct_to_ttj2000s(sclk_ticks):
143
+ """Test for `sct_to_ttj2000s` function."""
144
+ tt = sct_to_ttj2000s(sclk_ticks)
145
+ if isinstance(sclk_ticks, float):
146
+ assert isinstance(tt, float)
147
+ else:
148
+ assert len(tt) == len(sclk_ticks)
149
+
150
+
129
151
  def test_str_to_et(furnish_time_kernels):
130
152
  """Test coverage for string to et conversion function."""
131
153
  utc = "2017-07-14T19:46:00"
@@ -133,6 +155,7 @@ def test_str_to_et(furnish_time_kernels):
133
155
  expected_et = 553333629.1837274
134
156
  actual_et = str_to_et(utc)
135
157
  assert expected_et == actual_et
158
+ assert isinstance(actual_et, float)
136
159
 
137
160
  # Test list input
138
161
  list_of_utc = [
@@ -4,7 +4,7 @@ import xarray as xr
4
4
 
5
5
  from imap_processing import imap_module_directory
6
6
  from imap_processing.cdf.utils import write_cdf
7
- from imap_processing.spice.time import met_to_j2000ns
7
+ from imap_processing.spice.time import met_to_ttj2000ns
8
8
  from imap_processing.swapi.l1.swapi_l1 import (
9
9
  SWAPIAPID,
10
10
  decompress_count,
@@ -91,7 +91,7 @@ def test_find_sweep_starts():
91
91
  sequence_number = time % 12
92
92
  ds = xr.Dataset(
93
93
  {"seq_number": sequence_number, "shcoarse": np.arange(1, 27, 1)},
94
- coords={"epoch": met_to_j2000ns(time)},
94
+ coords={"epoch": met_to_ttj2000ns(time)},
95
95
  )
96
96
 
97
97
  start_indices = find_sweep_starts(ds)
@@ -114,7 +114,7 @@ def test_get_full_indices():
114
114
  sequence_number = time % 12
115
115
  ds = xr.Dataset(
116
116
  {"seq_number": sequence_number, "shcoarse": np.arange(1, 27, 1)},
117
- coords={"epoch": met_to_j2000ns(time)},
117
+ coords={"epoch": met_to_ttj2000ns(time)},
118
118
  )
119
119
 
120
120
  sweep_indices = get_indices_of_full_sweep(ds)
@@ -176,7 +176,14 @@ def test_process_swapi_science(decom_test_data):
176
176
  def test_swapi_l1_cdf(swapi_l0_test_data_path):
177
177
  """Test housekeeping processing and CDF file creation"""
178
178
  test_packet_file = swapi_l0_test_data_path / "imap_swapi_l0_raw_20240924_v001.pkts"
179
- processed_data = swapi_l1(test_packet_file, data_version="v001")
179
+ processed_data = swapi_l1([test_packet_file], data_version="v001")
180
+ # hk cdf file
181
+ hk_cdf_filename = "imap_swapi_l1_hk_20240924_v001.cdf"
182
+ # TODO: how to add ignore ISTP checks for HK data to cli.py
183
+ hk_cdf_path = write_cdf(processed_data[0])
184
+ assert hk_cdf_path.name == hk_cdf_filename
185
+
186
+ processed_data = swapi_l1([test_packet_file, hk_cdf_path], data_version="v001")
180
187
 
181
188
  assert processed_data[0].attrs["Apid"] == f"{SWAPIAPID.SWP_SCI}"
182
189
 
@@ -184,9 +191,3 @@ def test_swapi_l1_cdf(swapi_l0_test_data_path):
184
191
  cdf_filename = "imap_swapi_l1_sci_20240924_v001.cdf"
185
192
  cdf_path = write_cdf(processed_data[0])
186
193
  assert cdf_path.name == cdf_filename
187
-
188
- # hk cdf file
189
- cdf_filename = "imap_swapi_l1_hk_20240924_v001.cdf"
190
- # Ignore ISTP checks for HK data
191
- cdf_path = write_cdf(processed_data[1], istp=False)
192
- assert cdf_path.name == cdf_filename
@@ -7,10 +7,20 @@ from imap_processing.swapi.l2.swapi_l2 import TIME_PER_BIN, swapi_l2
7
7
 
8
8
  def test_swapi_l2_cdf(swapi_l0_test_data_path):
9
9
  """Test housekeeping processing and CDF file creation"""
10
- l0_data_path = swapi_l0_test_data_path / "imap_swapi_l0_raw_20240924_v001.pkts"
11
- processed_data = swapi_l1(l0_data_path, data_version="v001")
12
- l1_dataset = processed_data[0]
10
+ test_packet_file = swapi_l0_test_data_path / "imap_swapi_l0_raw_20240924_v001.pkts"
11
+ # Create HK CDF File
12
+ processed_hk_data = swapi_l1([test_packet_file], data_version="v001")
13
+ hk_cdf_filename = "imap_swapi_l1_hk_20240924_v001.cdf"
14
+ hk_cdf_path = write_cdf(processed_hk_data[0])
15
+ assert hk_cdf_path.name == hk_cdf_filename
13
16
 
17
+ # Create L1 CDF File
18
+ processed_sci_data = swapi_l1([test_packet_file, hk_cdf_path], data_version="v001")
19
+ cdf_filename = "imap_swapi_l1_sci_20240924_v001.cdf"
20
+ cdf_path = write_cdf(processed_sci_data[0])
21
+ assert cdf_path.name == cdf_filename
22
+
23
+ l1_dataset = processed_sci_data[0]
14
24
  l2_dataset = swapi_l2(l1_dataset, data_version="v001")
15
25
  l2_cdf = write_cdf(l2_dataset)
16
26
  assert l2_cdf.name == "imap_swapi_l2_sci_20240924_v001.cdf"
@@ -7,6 +7,6 @@ def test_cdf_creation():
7
7
  test_data_path = "tests/swe/l0_data/2024051010_SWE_SCIENCE_packet.bin"
8
8
  processed_data = swe_l1a(imap_module_directory / test_data_path, "001")
9
9
 
10
- cem_raw_cdf_filepath = write_cdf(processed_data)
10
+ cem_raw_cdf_filepath = write_cdf(processed_data[0])
11
11
 
12
12
  assert cem_raw_cdf_filepath.name == "imap_swe_l1a_sci_20240510_v001.cdf"
@@ -1,3 +1,5 @@
1
+ from unittest.mock import patch
2
+
1
3
  import numpy as np
2
4
  import pandas as pd
3
5
 
@@ -45,14 +47,29 @@ def test_swe_l1b(decom_test_data_derived):
45
47
  )
46
48
 
47
49
 
48
- def test_cdf_creation(l1b_validation_df):
50
+ @patch(
51
+ "imap_processing.swe.l1b.swe_l1b_science.read_in_flight_cal_data",
52
+ return_value=pd.DataFrame(
53
+ {
54
+ "met_time": [452051300, 454051900],
55
+ "cem1": [1, 1],
56
+ "cem2": [1, 1],
57
+ "cem3": [1, 1],
58
+ "cem4": [1, 1],
59
+ "cem5": [1, 1],
60
+ "cem6": [1, 1],
61
+ "cem7": [1, 1],
62
+ }
63
+ ),
64
+ )
65
+ def test_cdf_creation(mock_read_in_flight_cal_data, l1b_validation_df):
49
66
  """Test that CDF file is created and has the correct name."""
50
67
  test_data_path = "tests/swe/l0_data/2024051010_SWE_SCIENCE_packet.bin"
51
68
  l1a_datasets = swe_l1a(imap_module_directory / test_data_path, "002")
52
69
 
53
- l1b_dataset = swe_l1b(l1a_datasets, "002")
70
+ l1b_dataset = swe_l1b(l1a_datasets[0], "002")
54
71
 
55
- sci_l1b_filepath = write_cdf(l1b_dataset)
72
+ sci_l1b_filepath = write_cdf(l1b_dataset[0])
56
73
 
57
74
  assert sci_l1b_filepath.name == "imap_swe_l1b_sci_20240510_v002.cdf"
58
75
  # load the CDF file and compare the values
@@ -1,47 +1,21 @@
1
+ from unittest.mock import patch
2
+
1
3
  import numpy as np
4
+ import pandas as pd
2
5
  import pytest
3
6
 
4
- from imap_processing import imap_module_directory
5
7
  from imap_processing.swe.l1a.swe_science import swe_science
6
8
  from imap_processing.swe.l1b.swe_l1b_science import (
9
+ apply_in_flight_calibration,
7
10
  get_indices_of_full_cycles,
8
11
  )
9
- from imap_processing.swe.utils.swe_utils import SWEAPID
10
- from imap_processing.utils import packet_file_to_datasets
11
12
 
12
13
 
13
14
  @pytest.fixture(scope="session")
14
- def l1a_test_data():
15
- """Read test data from file"""
16
- # NOTE: data was provided in this sequence in both bin and validation data
17
- # from instrument team.
18
- # Packet 1 has spin 4's data
19
- # Packet 2 has spin 1's data
20
- # Packet 3 has spin 2's data
21
- # Packet 4 has spin 3's data
22
- # moved packet 1 to bottom to show data in order.
23
- packet_files = [
24
- imap_module_directory
25
- / "tests/swe/l0_data/20230927173253_SWE_SCIENCE_packet.bin",
26
- imap_module_directory
27
- / "tests/swe/l0_data/20230927173308_SWE_SCIENCE_packet.bin",
28
- imap_module_directory
29
- / "tests/swe/l0_data/20230927173323_SWE_SCIENCE_packet.bin",
30
- imap_module_directory
31
- / "tests/swe/l0_data/20230927173238_SWE_SCIENCE_packet.bin",
32
- ]
33
- dataset = packet_file_to_datasets(packet_files[0], use_derived_value=False)[
34
- SWEAPID.SWE_SCIENCE
35
- ]
36
- for packet_file in packet_files[1:]:
37
- dataset.merge(
38
- packet_file_to_datasets(packet_file, use_derived_value=False)[
39
- SWEAPID.SWE_SCIENCE
40
- ]
41
- )
42
- # Get unpacked science data
43
- unpacked_data = swe_science(dataset, "001")
44
- return unpacked_data
15
+ def l1a_test_data(decom_test_data):
16
+ """Read test data from file and process to l1a"""
17
+ processed_data = swe_science(decom_test_data, "001")
18
+ return processed_data
45
19
 
46
20
 
47
21
  def test_get_full_cycle_data_indices():
@@ -62,4 +36,49 @@ def test_get_full_cycle_data_indices():
62
36
  np.testing.assert_array_equal(filtered_q, np.array([]))
63
37
 
64
38
 
65
- # TODO: Add more tests
39
+ @patch(
40
+ "imap_processing.swe.l1b.swe_l1b_science.read_in_flight_cal_data",
41
+ return_value=pd.DataFrame(
42
+ {
43
+ "met_time": [453051300, 453051900],
44
+ "cem1": [1, 2],
45
+ "cem2": [1, 2],
46
+ "cem3": [1, 2],
47
+ "cem4": [1, 2],
48
+ "cem5": [1, 2],
49
+ "cem6": [1, 2],
50
+ "cem7": [1, 2],
51
+ }
52
+ ),
53
+ )
54
+ def test_in_flight_calibration_factor(mock_read_in_flight_cal_data, l1a_test_data):
55
+ """Test that the L1B processing is working as expected."""
56
+ # create sample data
57
+
58
+ input_time = 453051355.0
59
+ input_count = 19967
60
+ one_full_cycle_data = np.full((24, 30, 7), input_count)
61
+ acquisition_time = np.full((24, 30), input_time)
62
+
63
+ # Test that calibration factor is within correct range given test data
64
+ expected_cal_factor = 1 + ((2 - 1) / (453051900 - 453051300)) * (
65
+ input_time - 453051300
66
+ )
67
+
68
+ calibrated_count = apply_in_flight_calibration(
69
+ one_full_cycle_data,
70
+ acquisition_time,
71
+ )
72
+
73
+ np.testing.assert_allclose(
74
+ calibrated_count,
75
+ np.full((24, 30, 7), input_count * expected_cal_factor),
76
+ rtol=1e-9,
77
+ )
78
+
79
+ # Check for value outside of calibration time range
80
+ input_time = 1.0
81
+ acquisition_time = np.full((24, 30), input_time)
82
+
83
+ with pytest.raises(ValueError, match="Acquisition min/max times: "):
84
+ apply_in_flight_calibration(one_full_cycle_data, acquisition_time)