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,233 @@
1
+ """Functions for retrieving spin-table data."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Union
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+ from numpy import typing as npt
10
+
11
+ from imap_processing.spice.geometry import (
12
+ SpiceFrame,
13
+ get_spacecraft_to_instrument_spin_phase_offset,
14
+ )
15
+
16
+
17
+ def get_spin_data() -> pd.DataFrame:
18
+ """
19
+ Read spin file using environment variable and return spin data.
20
+
21
+ SPIN_DATA_FILEPATH environment variable would be a fixed value.
22
+ It could be s3 filepath that can be used to download the data
23
+ through API or it could be path EFS or Batch volume mount path.
24
+
25
+ Spin data should contain the following fields:
26
+ * spin_number
27
+ * spin_start_sec
28
+ * spin_start_subsec
29
+ * spin_period_sec
30
+ * spin_period_valid
31
+ * spin_phase_valid
32
+ * spin_period_source
33
+ * thruster_firing
34
+
35
+ Returns
36
+ -------
37
+ spin_data : pandas.DataFrame
38
+ Spin data.
39
+ """
40
+ spin_data_filepath = os.getenv("SPIN_DATA_FILEPATH")
41
+ if spin_data_filepath is not None:
42
+ path_to_spin_file = Path(spin_data_filepath)
43
+ else:
44
+ # Handle the case where the environment variable is not set
45
+ raise ValueError("SPIN_DATA_FILEPATH environment variable is not set.")
46
+
47
+ spin_df = pd.read_csv(path_to_spin_file, comment="#")
48
+ # Combine spin_start_sec and spin_start_subsec to get the spin start
49
+ # time in seconds. The spin start subseconds are in milliseconds.
50
+ spin_df["spin_start_time"] = (
51
+ spin_df["spin_start_sec"] + spin_df["spin_start_subsec"] / 1e3
52
+ )
53
+
54
+ return spin_df
55
+
56
+
57
+ def interpolate_spin_data(query_met_times: Union[float, npt.NDArray]) -> pd.DataFrame:
58
+ """
59
+ Interpolate spin table data to the queried MET times.
60
+
61
+ All columns in the spin table csv file are interpolated to the previous
62
+ table entry. A sc_spin_phase column is added that is the computed spacecraft
63
+ spin phase at the queried MET times. Note that spin phase is by definition,
64
+ in the interval [0, 1) where 1 is equivalent to 360 degrees.
65
+
66
+ Parameters
67
+ ----------
68
+ query_met_times : float or np.ndarray
69
+ Query times in Mission Elapsed Time (MET).
70
+
71
+ Returns
72
+ -------
73
+ spin_df : pandas.DataFrame
74
+ Spin table data with the spin-phase column added and one row
75
+ interpolated for each queried MET time. Output columns are:
76
+ * spin_number
77
+ * spin_start_sec
78
+ * spin_start_subsec
79
+ * spin_period_sec
80
+ * spin_period_valid
81
+ * spin_phase_valid
82
+ * spin_period_source
83
+ * thruster_firing
84
+ * spin_start_met
85
+ * sc_spin_phase
86
+ """
87
+ spin_df = get_spin_data()
88
+
89
+ # Ensure query_met_times is an array
90
+ query_met_times = np.asarray(query_met_times)
91
+ is_scalar = query_met_times.ndim == 0
92
+ if is_scalar:
93
+ # Force scalar to array because np.asarray() will not
94
+ # convert scalar to array
95
+ query_met_times = np.atleast_1d(query_met_times)
96
+
97
+ # Make sure input times are within the bounds of spin data
98
+ spin_df_start_time = spin_df["spin_start_time"].values[0]
99
+ spin_df_end_time = (
100
+ spin_df["spin_start_time"].values[-1] + spin_df["spin_period_sec"].values[-1]
101
+ )
102
+ input_start_time = query_met_times.min()
103
+ input_end_time = query_met_times.max()
104
+ if input_start_time < spin_df_start_time or input_end_time >= spin_df_end_time:
105
+ raise ValueError(
106
+ f"Query times, {query_met_times} are outside of the spin data range, "
107
+ f"{spin_df_start_time, spin_df_end_time}."
108
+ )
109
+
110
+ # Find all spin time that are less or equal to query_met_times.
111
+ # To do that, use side right, a[i-1] <= v < a[i], in the searchsorted.
112
+ # Eg.
113
+ # >>> df['a']
114
+ # array([0, 15, 30, 45, 60])
115
+ # >>> np.searchsorted(df['a'], [0, 13, 15, 32, 70], side='right')
116
+ # array([1, 1, 2, 3, 5])
117
+ last_spin_indices = (
118
+ np.searchsorted(spin_df["spin_start_time"], query_met_times, side="right") - 1
119
+ )
120
+ # Generate a dataframe with one row per query time
121
+ out_df = spin_df.iloc[last_spin_indices]
122
+
123
+ # Calculate spin phase
124
+ spin_phases = (query_met_times - out_df["spin_start_time"].values) / out_df[
125
+ "spin_period_sec"
126
+ ].values
127
+
128
+ # Check for invalid spin phase using below checks:
129
+ # 1. Check that the spin phase is in valid range, [0, 1).
130
+ # 2. Check invalid spin phase using spin_phase_valid,
131
+ # spin_period_valid columns.
132
+ invalid_spin_phase_range = (spin_phases < 0) | (spin_phases >= 1)
133
+
134
+ invalid_spins = (out_df["spin_phase_valid"].values == 0) | (
135
+ out_df["spin_period_valid"].values == 0
136
+ )
137
+ bad_spin_phases = invalid_spin_phase_range | invalid_spins
138
+ spin_phases[bad_spin_phases] = np.nan
139
+
140
+ # Add spin_phase column to output dataframe
141
+ out_df["sc_spin_phase"] = spin_phases
142
+
143
+ return out_df
144
+
145
+
146
+ def get_spin_angle(
147
+ spin_phases: Union[float, npt.NDArray],
148
+ degrees: bool = False,
149
+ ) -> Union[float, npt.NDArray]:
150
+ """
151
+ Convert spin_phases to radians or degrees.
152
+
153
+ Parameters
154
+ ----------
155
+ spin_phases : float or np.ndarray
156
+ Instrument or spacecraft spin phases. Spin phase is a
157
+ floating point number in the range [0, 1) corresponding to the
158
+ spin angle / 360.
159
+ degrees : bool
160
+ If degrees parameter is True, return angle in degrees otherwise return angle in
161
+ radians. Default is False.
162
+
163
+ Returns
164
+ -------
165
+ spin_phases : float or np.ndarray
166
+ Spin angle in degrees or radians for the input query times.
167
+ """
168
+ if np.any(spin_phases < 0) or np.any(spin_phases >= 1):
169
+ raise ValueError(
170
+ f"Spin phases, {spin_phases} are outside of the expected spin phase range, "
171
+ f"[0, 1) "
172
+ )
173
+ if degrees:
174
+ # Convert to degrees
175
+ return spin_phases * 360
176
+ else:
177
+ # Convert to radians
178
+ return spin_phases * 2 * np.pi
179
+
180
+
181
+ def get_spacecraft_spin_phase(
182
+ query_met_times: Union[float, npt.NDArray],
183
+ ) -> Union[float, npt.NDArray]:
184
+ """
185
+ Get the spacecraft spin phase for the input query times.
186
+
187
+ Formula to calculate spin phase:
188
+ spin_phase = (query_met_times - spin_start_time) / spin_period_sec
189
+
190
+ Parameters
191
+ ----------
192
+ query_met_times : float or np.ndarray
193
+ Query times in Mission Elapsed Time (MET).
194
+
195
+ Returns
196
+ -------
197
+ spin_phase : float or np.ndarray
198
+ Spin phase for the input query times.
199
+ """
200
+ spin_df = interpolate_spin_data(query_met_times)
201
+ if np.asarray(query_met_times).ndim == 0:
202
+ return spin_df["sc_spin_phase"].values[0]
203
+ return spin_df["sc_spin_phase"].values
204
+
205
+
206
+ def get_instrument_spin_phase(
207
+ query_met_times: Union[float, npt.NDArray], instrument: SpiceFrame
208
+ ) -> Union[float, npt.NDArray]:
209
+ """
210
+ Get the instrument spin phase for the input query times.
211
+
212
+ Formula to calculate spin phase:
213
+ instrument_spin_phase = (spacecraft_spin_phase + instrument_spin_offset) % 1
214
+
215
+ Parameters
216
+ ----------
217
+ query_met_times : float or np.ndarray
218
+ Query times in Mission Elapsed Time (MET).
219
+ instrument : SpiceFrame
220
+ Instrument frame to calculate spin phase for.
221
+
222
+ Returns
223
+ -------
224
+ spin_phase : float or np.ndarray
225
+ Instrument spin phase for the input query times. Spin phase is a
226
+ floating point number in the range [0, 1) corresponding to the
227
+ spin angle / 360.
228
+ """
229
+ spacecraft_spin_phase = get_spacecraft_spin_phase(query_met_times)
230
+ instrument_spin_phase_offset = get_spacecraft_to_instrument_spin_phase_offset(
231
+ instrument
232
+ )
233
+ return (spacecraft_spin_phase + instrument_spin_phase_offset) % 1
@@ -1,4 +1,4 @@
1
- """Time conversion functions that rely on SPICE."""
1
+ """Time conversion functions that rely on SPICEYPY."""
2
2
 
3
3
  import typing
4
4
  from collections.abc import Collection, Iterable
@@ -6,7 +6,7 @@ from typing import Union
6
6
 
7
7
  import numpy as np
8
8
  import numpy.typing as npt
9
- import spiceypy as spice
9
+ import spiceypy
10
10
 
11
11
  from imap_processing.spice import IMAP_SC_ID
12
12
  from imap_processing.spice.kernels import ensure_spice
@@ -19,8 +19,38 @@ TICK_DURATION = 2e-5 # 20 microseconds as defined in imap_sclk_0000.tsc
19
19
  # TODO: Implement a function for converting CDF epoch to UTC correctly.
20
20
  # see github ticket #1208
21
21
  # The UTC string was generated by:
22
- # >>> spiceypy.et2utc(0, "ISOC", 9)
23
- J2000_EPOCH = np.datetime64("2000-01-01T11:58:55.816072737", "ns")
22
+ # >>> spiceypy.et2utc(spiceypy.unitim(0, "TT", "ET"), "ISOC", 9)
23
+ TTJ2000_EPOCH = np.datetime64("2000-01-01T11:58:55.816", "ns")
24
+
25
+
26
+ @typing.no_type_check
27
+ def _vectorize(pyfunc: typing.Callable, **vectorize_kwargs) -> typing.Callable:
28
+ """
29
+ Convert 0-D arrays from numpy.vectorize to scalars.
30
+
31
+ For details on numpy.vectorize, see
32
+ https://numpy.org/doc/stable/reference/generated/numpy.vectorize.html
33
+
34
+ Parameters
35
+ ----------
36
+ pyfunc : callable
37
+ A python function or method.
38
+ **vectorize_kwargs :
39
+ Keyword arguments to pass to numpy.vectorize.
40
+
41
+ Returns
42
+ -------
43
+ out : callable
44
+ A vectorized function.
45
+ """
46
+ vectorized_func = np.vectorize(pyfunc, **vectorize_kwargs)
47
+
48
+ def wrapper(*args, **kwargs): # numpydoc ignore=GL08
49
+ # Calling the vectorized function with [()] will convert 0-D arrays
50
+ # to scalars
51
+ return vectorized_func(*args, **kwargs)[()]
52
+
53
+ return wrapper
24
54
 
25
55
 
26
56
  def met_to_sclkticks(met: npt.ArrayLike) -> npt.NDArray[float]:
@@ -40,11 +70,11 @@ def met_to_sclkticks(met: npt.ArrayLike) -> npt.NDArray[float]:
40
70
  return np.asarray(met, dtype=float) / TICK_DURATION
41
71
 
42
72
 
43
- def met_to_j2000ns(
73
+ def met_to_ttj2000ns(
44
74
  met: npt.ArrayLike,
45
75
  ) -> npt.NDArray[np.int64]:
46
76
  """
47
- Convert mission elapsed time (MET) to nanoseconds since J2000.
77
+ Convert mission elapsed time (MET) to terrestrial time nanoseconds since J2000.
48
78
 
49
79
  Parameters
50
80
  ----------
@@ -54,7 +84,8 @@ def met_to_j2000ns(
54
84
  Returns
55
85
  -------
56
86
  numpy.ndarray[numpy.int64]
57
- The mission elapsed time converted to nanoseconds since the J2000 epoch.
87
+ The mission elapsed time converted to nanoseconds since the J2000 epoch
88
+ in the terrestrial time (TT) timescale.
58
89
 
59
90
  Notes
60
91
  -----
@@ -67,27 +98,35 @@ def met_to_j2000ns(
67
98
  it is preferable to use the higher accuracy method.
68
99
  """
69
100
  sclk_ticks = met_to_sclkticks(met)
70
- return np.asarray(_sct2e_wrapper(sclk_ticks) * 1e9, dtype=np.int64)
101
+ return np.asarray(sct_to_ttj2000s(sclk_ticks) * 1e9, dtype=np.int64)
71
102
 
72
103
 
73
- def j2000ns_to_j2000s(j2000ns: npt.ArrayLike) -> npt.NDArray[float]:
104
+ @typing.no_type_check
105
+ @ensure_spice
106
+ def ttj2000ns_to_et(tt_ns: npt.ArrayLike) -> npt.NDArray[float]:
74
107
  """
75
- Convert the J2000 epoch nanoseconds to J2000 epoch seconds.
108
+ Convert TT J2000 epoch nanoseconds to TDB J2000 epoch seconds.
76
109
 
77
- The common CDF coordinate `epoch` stores J2000 nanoseconds. SPICE requires
78
- J2000 seconds be used. This is a common function to do that conversion.
110
+ The common CDF coordinate `epoch` stores terrestrial time (TT) J2000
111
+ nanoseconds. SPICE requires Barycentric Dynamical Time (TDB, aka ET) J2000
112
+ floating point seconds for most geometry related functions. This is a common
113
+ function to do that conversion.
79
114
 
80
115
  Parameters
81
116
  ----------
82
- j2000ns : float, numpy.ndarray
83
- Number of nanoseconds since the J2000 epoch.
117
+ tt_ns : float, numpy.ndarray
118
+ Number of nanoseconds since the J2000 epoch in the TT timescale.
84
119
 
85
120
  Returns
86
121
  -------
87
122
  numpy.ndarray[float]
88
- Number of seconds since the J2000 epoch.
123
+ Number of seconds since the J2000 epoch in the TDB timescale.
89
124
  """
90
- return np.asarray(j2000ns, dtype=np.float64) / 1e9
125
+ tt_seconds = np.asarray(tt_ns, dtype=np.float64) / 1e9
126
+ vectorized_unitim = _vectorize(
127
+ spiceypy.unitim, otypes=[float], excluded=["insys", "outsys"]
128
+ )
129
+ return vectorized_unitim(tt_seconds, "TT", "ET")
91
130
 
92
131
 
93
132
  @typing.no_type_check
@@ -112,8 +151,8 @@ def met_to_utc(met: npt.ArrayLike, precision: int = 9) -> npt.NDArray[str]:
112
151
  fractional seconds precision as specified by the precision keyword.
113
152
  """
114
153
  sclk_ticks = met_to_sclkticks(met)
115
- et = _sct2e_wrapper(sclk_ticks)
116
- return spice.et2utc(et, "ISOC", prec=precision)
154
+ et = sct_to_et(sclk_ticks)
155
+ return spiceypy.et2utc(et, "ISOC", prec=precision)
117
156
 
118
157
 
119
158
  def met_to_datetime64(
@@ -132,14 +171,12 @@ def met_to_datetime64(
132
171
  numpy.ndarray[str]
133
172
  The mission elapsed time converted to UTC string.
134
173
  """
135
- if isinstance(met, typing.Iterable):
136
- return np.asarray([np.datetime64(utc) for utc in met_to_utc(met)])
137
- return np.datetime64(met_to_utc(met))
174
+ return np.array(met_to_utc(met), dtype=np.datetime64)[()]
138
175
 
139
176
 
140
177
  @typing.no_type_check
141
178
  @ensure_spice
142
- def _sct2e_wrapper(
179
+ def sct_to_et(
143
180
  sclk_ticks: Union[float, Collection[float]],
144
181
  ) -> Union[float, np.ndarray]:
145
182
  """
@@ -159,10 +196,40 @@ def _sct2e_wrapper(
159
196
  ephemeris_time: np.ndarray
160
197
  Ephemeris time, seconds past J2000.
161
198
  """
162
- if isinstance(sclk_ticks, Collection):
163
- return np.array([spice.sct2e(IMAP_SC_ID, s) for s in sclk_ticks])
164
- else:
165
- return spice.sct2e(IMAP_SC_ID, sclk_ticks)
199
+ vectorized_sct2e = _vectorize(spiceypy.sct2e, otypes=[float], excluded=[0])
200
+ return vectorized_sct2e(IMAP_SC_ID, sclk_ticks)
201
+
202
+
203
+ @typing.no_type_check
204
+ @ensure_spice
205
+ def sct_to_ttj2000s(
206
+ sclk_ticks: Union[float, Iterable[float]],
207
+ ) -> Union[float, np.ndarray]:
208
+ """
209
+ Convert encoded spacecraft clock "ticks" to terrestrial time (TT).
210
+
211
+ Decorated wrapper for chained spiceypy functions `unitim(sct2e(), "ET", "TT")`
212
+ that vectorizes the functions in addition to wrapping with the @ensure_spice
213
+ automatic kernel furnishing functionality.
214
+ https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/sct2e_c.html
215
+ https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/unitim_c.html
216
+
217
+ Parameters
218
+ ----------
219
+ sclk_ticks : Union[float, Iterable[float]]
220
+ Input sclk ticks value(s) to be converted to ephemeris time.
221
+
222
+ Returns
223
+ -------
224
+ terrestrial_time: np.ndarray[float]
225
+ Terrestrial time, seconds past J2000.
226
+ """
227
+
228
+ def conversion(sclk_ticks): # numpydoc ignore=GL08
229
+ return spiceypy.unitim(spiceypy.sct2e(IMAP_SC_ID, sclk_ticks), "ET", "TT")
230
+
231
+ vectorized_func = _vectorize(conversion, otypes=[float])
232
+ return vectorized_func(sclk_ticks)
166
233
 
167
234
 
168
235
  @typing.no_type_check
@@ -187,10 +254,8 @@ def str_to_et(
187
254
  ephemeris_time: np.ndarray
188
255
  Ephemeris time, seconds past J2000.
189
256
  """
190
- if isinstance(time_str, str):
191
- return spice.str2et(time_str)
192
- else:
193
- return np.array([spice.str2et(t) for t in time_str])
257
+ vectorized_str2et = _vectorize(spiceypy.str2et, otypes=[float])
258
+ return vectorized_str2et(time_str)
194
259
 
195
260
 
196
261
  @typing.no_type_check
@@ -231,4 +296,4 @@ def et_to_utc(
231
296
  utc_time : str or np.ndarray
232
297
  UTC time(s).
233
298
  """
234
- return spice.et2utc(et, format_str, precision, utclen)
299
+ return spiceypy.et2utc(et, format_str, precision, utclen)
@@ -9,6 +9,7 @@ import xarray as xr
9
9
 
10
10
  from imap_processing import imap_module_directory
11
11
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
12
+ from imap_processing.cdf.utils import load_cdf
12
13
  from imap_processing.quality_flags import SWAPIFlags
13
14
  from imap_processing.swapi.swapi_utils import SWAPIAPID, SWAPIMODE
14
15
  from imap_processing.utils import packet_file_to_datasets
@@ -485,12 +486,19 @@ def process_swapi_science(
485
486
  # ===================================================================
486
487
  # Quality flags
487
488
  # ===================================================================
488
- quality_flags_data = np.zeros((total_full_sweeps, 72), np.uint16)
489
+ quality_flags_data = np.zeros((total_full_sweeps, 72), dtype=np.uint16)
489
490
 
490
491
  # Add science data quality flags
491
- quality_flags_data[pcem_compression_flags == 1] |= SWAPIFlags.SWP_PCEM_COMP.value
492
- quality_flags_data[scem_compression_flags == 1] |= SWAPIFlags.SWP_SCEM_COMP.value
493
- quality_flags_data[coin_compression_flags == 1] |= SWAPIFlags.SWP_COIN_COMP.value
492
+ # Have to match datatype to bitwise OR
493
+ quality_flags_data[pcem_compression_flags == 1] |= np.uint16(
494
+ SWAPIFlags.SWP_PCEM_COMP
495
+ )
496
+ quality_flags_data[scem_compression_flags == 1] |= np.uint16(
497
+ SWAPIFlags.SWP_SCEM_COMP
498
+ )
499
+ quality_flags_data[coin_compression_flags == 1] |= np.uint16(
500
+ SWAPIFlags.SWP_COIN_COMP
501
+ )
494
502
 
495
503
  # Add housekeeping-derived quality flags
496
504
  # --------------------------------------
@@ -536,10 +544,10 @@ def process_swapi_science(
536
544
  # Use getattr to dynamically access the flag in SWAPIFlags class
537
545
  flag_to_set = getattr(SWAPIFlags, flag_name)
538
546
  # set the quality flag for each data
539
- quality_flags_data[current_flag == 1] |= flag_to_set.value
547
+ quality_flags_data[current_flag == 1] |= np.uint16(flag_to_set)
540
548
 
541
549
  swp_flags = xr.DataArray(
542
- quality_flags_data,
550
+ quality_flags_data.astype(np.uint16),
543
551
  dims=["epoch", "energy"],
544
552
  attrs=cdf_manager.get_variable_attributes("flags_default"),
545
553
  )
@@ -555,7 +563,7 @@ def process_swapi_science(
555
563
  epoch_values,
556
564
  name="epoch",
557
565
  dims=["epoch"],
558
- attrs=cdf_manager.get_variable_attributes("epoch"),
566
+ attrs=cdf_manager.get_variable_attributes("epoch", check_schema=False),
559
567
  )
560
568
 
561
569
  # There are 72 energy steps
@@ -563,14 +571,14 @@ def process_swapi_science(
563
571
  np.arange(72),
564
572
  name="energy",
565
573
  dims=["energy"],
566
- attrs=cdf_manager.get_variable_attributes("energy"),
574
+ attrs=cdf_manager.get_variable_attributes("energy", check_schema=False),
567
575
  )
568
576
  # LABL_PTR_1 should be CDF_CHAR.
569
577
  energy_label = xr.DataArray(
570
578
  energy.values.astype(str),
571
579
  name="energy_label",
572
580
  dims=["energy_label"],
573
- attrs=cdf_manager.get_variable_attributes("energy_label"),
581
+ attrs=cdf_manager.get_variable_attributes("energy_label", check_schema=False),
574
582
  )
575
583
 
576
584
  # Add other global attributes
@@ -691,14 +699,14 @@ def process_swapi_science(
691
699
  return dataset
692
700
 
693
701
 
694
- def swapi_l1(file_path: str, data_version: str) -> xr.Dataset:
702
+ def swapi_l1(dependencies: list, data_version: str) -> xr.Dataset:
695
703
  """
696
704
  Will process SWAPI level 0 data to level 1.
697
705
 
698
706
  Parameters
699
707
  ----------
700
- file_path : str
701
- Path to SWAPI L0 file.
708
+ dependencies : list
709
+ Input dependencies needed for L1 processing.
702
710
  data_version : str
703
711
  Version of the data product being created.
704
712
 
@@ -710,27 +718,48 @@ def swapi_l1(file_path: str, data_version: str) -> xr.Dataset:
710
718
  xtce_definition = (
711
719
  f"{imap_module_directory}/swapi/packet_definitions/swapi_packet_definition.xml"
712
720
  )
713
- datasets = packet_file_to_datasets(
714
- file_path, xtce_definition, use_derived_value=False
715
- )
721
+ l0_unpacked_dict = {}
722
+ l1_hk_ds = None
723
+ for file_path in dependencies:
724
+ if file_path.suffix == ".pkts":
725
+ l0_unpacked_dict = packet_file_to_datasets(
726
+ file_path, xtce_definition, use_derived_value=False
727
+ )
728
+ if file_path.suffix == ".cdf":
729
+ l1_hk_ds = load_cdf(file_path)
730
+
716
731
  processed_data = []
717
732
 
718
- for apid, ds_data in datasets.items():
719
- # Right now, we only process SWP_HK and SWP_SCI
720
- # other packets are not process in this processing pipeline
721
- # If appId is science, then the file should contain all data of science appId
733
+ # Right now, we only process SWP_HK and SWP_SCI.
734
+ # Other apId are not processed in this processing pipeline.
735
+
736
+ # Len of dependencies is 2 and l0_unpacked_dict[SWAPIAPID.SWP_HK] is not None
737
+ if (
738
+ len(dependencies) == 2
739
+ and l0_unpacked_dict.get(SWAPIAPID.SWP_SCI, None) is not None
740
+ ):
741
+ # process science data
742
+ sci_dataset = process_swapi_science(
743
+ l0_unpacked_dict[SWAPIAPID.SWP_SCI], l1_hk_ds, data_version
744
+ )
745
+ processed_data.append(sci_dataset)
746
+
747
+ elif len(dependencies) == 1 and l0_unpacked_dict[SWAPIAPID.SWP_HK]:
748
+ hk_ds = l0_unpacked_dict[SWAPIAPID.SWP_HK]
749
+ # Add HK datalevel attrs
750
+ imap_attrs = ImapCdfAttributes()
751
+ imap_attrs.add_instrument_global_attrs("swapi")
752
+ imap_attrs.add_global_attribute("Data_version", data_version)
753
+ imap_attrs.add_instrument_variable_attrs(instrument="swapi", level=None)
754
+ hk_ds.attrs.update(imap_attrs.get_global_attributes("imap_swapi_l1_hk"))
755
+ hk_common_attrs = imap_attrs.get_variable_attributes("hk_attrs")
756
+ hk_ds["epoch"].attrs.update(
757
+ imap_attrs.get_variable_attributes("epoch", check_schema=False)
758
+ )
722
759
 
723
- if apid == SWAPIAPID.SWP_SCI.value:
724
- sci_dataset = process_swapi_science(
725
- ds_data, datasets[SWAPIAPID.SWP_HK], data_version
726
- )
727
- processed_data.append(sci_dataset)
728
- if apid == SWAPIAPID.SWP_HK.value:
729
- # Add HK datalevel attrs
730
- hk_attrs = ImapCdfAttributes()
731
- hk_attrs.add_instrument_global_attrs("swapi")
732
- hk_attrs.add_global_attribute("Data_version", data_version)
733
- ds_data.attrs.update(hk_attrs.get_global_attributes("imap_swapi_l1_hk"))
734
- processed_data.append(ds_data)
760
+ # Add attrs to HK data variables
761
+ for var_name in hk_ds.data_vars:
762
+ hk_ds[var_name].attrs.update(hk_common_attrs)
763
+ processed_data.append(hk_ds)
735
764
 
736
765
  return processed_data