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
@@ -42,7 +42,12 @@ def swe_l1a(packet_file: str, data_version: str) -> xr.Dataset:
42
42
  packet_file, xtce_document, use_derived_value=False
43
43
  )
44
44
 
45
+ if SWEAPID.SWE_SCIENCE not in datasets_by_apid:
46
+ logger.info("No science data found in packet file.")
47
+ return []
45
48
  # TODO: figure out how to handle non-science data
46
- return swe_science(
47
- l0_dataset=datasets_by_apid[SWEAPID.SWE_SCIENCE], data_version=data_version
48
- )
49
+ return [
50
+ swe_science(
51
+ l0_dataset=datasets_by_apid[SWEAPID.SWE_SCIENCE], data_version=data_version
52
+ )
53
+ ]
@@ -141,45 +141,45 @@ def swe_science(l0_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
141
141
  attrs=cdf_attrs.get_variable_attributes("epoch"),
142
142
  )
143
143
 
144
- spin_angle = xr.DataArray(
144
+ spin_sector = xr.DataArray(
145
145
  np.arange(180),
146
- name="spin_angle",
147
- dims=["spin_angle"],
148
- attrs=cdf_attrs.get_variable_attributes("spin_angle"),
146
+ name="spin_sector",
147
+ dims=["spin_sector"],
148
+ attrs=cdf_attrs.get_variable_attributes("spin_sector"),
149
149
  )
150
150
 
151
151
  # NOTE: LABL_PTR_1 should be CDF_CHAR.
152
- spin_angle_label = xr.DataArray(
153
- spin_angle.values.astype(str),
154
- name="spin_angle_label",
155
- dims=["spin_angle_label"],
156
- attrs=cdf_attrs.get_variable_attributes("spin_angle_label"),
152
+ spin_sector_label = xr.DataArray(
153
+ spin_sector.values.astype(str),
154
+ name="spin_sector_label",
155
+ dims=["spin_sector"],
156
+ attrs=cdf_attrs.get_variable_attributes("spin_sector_label"),
157
157
  )
158
158
 
159
- polar_angle = xr.DataArray(
159
+ cem_id = xr.DataArray(
160
160
  np.arange(7),
161
- name="polar_angle",
162
- dims=["polar_angle"],
163
- attrs=cdf_attrs.get_variable_attributes("polar_angle"),
161
+ name="cem_id",
162
+ dims=["cem_id"],
163
+ attrs=cdf_attrs.get_variable_attributes("cem_id"),
164
164
  )
165
165
 
166
166
  # NOTE: LABL_PTR_2 should be CDF_CHAR.
167
- polar_angle_label = xr.DataArray(
168
- polar_angle.values.astype(str),
169
- name="polar_angle_label",
170
- dims=["polar_angle_label"],
171
- attrs=cdf_attrs.get_variable_attributes("polar_angle_label"),
167
+ cem_id_label = xr.DataArray(
168
+ cem_id.values.astype(str),
169
+ name="cem_id_label",
170
+ dims=["cem_id"],
171
+ attrs=cdf_attrs.get_variable_attributes("cem_id_label"),
172
172
  )
173
173
 
174
174
  science_xarray = xr.DataArray(
175
175
  science_array,
176
- dims=["epoch", "spin_angle", "polar_angle"],
176
+ dims=["epoch", "spin_sector", "cem_id"],
177
177
  attrs=cdf_attrs.get_variable_attributes("science_data"),
178
178
  )
179
179
 
180
180
  raw_science_xarray = xr.DataArray(
181
181
  raw_science_array,
182
- dims=["epoch", "spin_angle", "polar_angle"],
182
+ dims=["epoch", "spin_sector", "cem_id"],
183
183
  attrs=cdf_attrs.get_variable_attributes("raw_counts"),
184
184
  )
185
185
 
@@ -190,10 +190,10 @@ def swe_science(l0_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
190
190
  dataset = xr.Dataset(
191
191
  coords={
192
192
  "epoch": epoch_time,
193
- "spin_angle": spin_angle,
194
- "polar_angle": polar_angle,
195
- "spin_angle_label": spin_angle_label,
196
- "polar_angle_label": polar_angle_label,
193
+ "spin_sector": spin_sector,
194
+ "cem_id": cem_id,
195
+ "spin_sector_label": spin_sector_label,
196
+ "cem_id_label": cem_id_label,
197
197
  },
198
198
  attrs=l1a_global_attrs,
199
199
  )
@@ -46,4 +46,5 @@ def swe_l1b(l1a_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
46
46
  data = swe_l1b_science(eu_data, data_version)
47
47
  if data is None:
48
48
  logger.info("No data to write to CDF")
49
- return data
49
+ return []
50
+ return [data]
@@ -8,38 +8,13 @@ import pandas as pd
8
8
  import xarray as xr
9
9
 
10
10
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
11
- from imap_processing.swe.utils.swe_utils import read_lookup_table
11
+ from imap_processing.swe.utils.swe_utils import (
12
+ ESA_VOLTAGE_ROW_INDEX_DICT,
13
+ read_lookup_table,
14
+ )
12
15
 
13
16
  logger = logging.getLogger(__name__)
14
17
 
15
- # ESA voltage and index in the final data table
16
- esa_voltage_row_index_dict = {
17
- 0.56: 0,
18
- 0.78: 1,
19
- 1.08: 2,
20
- 1.51: 3,
21
- 2.10: 4,
22
- 2.92: 5,
23
- 4.06: 6,
24
- 5.64: 7,
25
- 7.85: 8,
26
- 10.92: 9,
27
- 15.19: 10,
28
- 21.13: 11,
29
- 29.39: 12,
30
- 40.88: 13,
31
- 56.87: 14,
32
- 79.10: 15,
33
- 110.03: 16,
34
- 153.05: 17,
35
- 212.89: 18,
36
- 296.14: 19,
37
- 411.93: 20,
38
- 572.99: 21,
39
- 797.03: 22,
40
- 1108.66: 23,
41
- }
42
-
43
18
 
44
19
  def get_esa_dataframe(esa_table_number: int) -> pd.DataFrame:
45
20
  """
@@ -110,7 +85,7 @@ def deadtime_correction(counts: np.ndarray, acq_duration: int) -> npt.NDArray:
110
85
  return corrected_count.astype(np.float64)
111
86
 
112
87
 
113
- def convert_counts_to_rate(data: np.ndarray, acq_duration: int) -> npt.NDArray:
88
+ def convert_counts_to_rate(data: np.ndarray, acq_duration: np.ndarray) -> npt.NDArray:
114
89
  """
115
90
  Convert counts to rate using sampling time.
116
91
 
@@ -120,7 +95,7 @@ def convert_counts_to_rate(data: np.ndarray, acq_duration: int) -> npt.NDArray:
120
95
  ----------
121
96
  data : numpy.ndarray
122
97
  Counts data.
123
- acq_duration : int
98
+ acq_duration : numpy.ndarray
124
99
  Acquisition duration. acq_duration is in microseconds.
125
100
 
126
101
  Returns
@@ -134,51 +109,107 @@ def convert_counts_to_rate(data: np.ndarray, acq_duration: int) -> npt.NDArray:
134
109
  return count_rate.astype(np.float64)
135
110
 
136
111
 
137
- def calculate_calibration_factor(time: int) -> None:
112
+ def read_in_flight_cal_data() -> pd.DataFrame:
138
113
  """
139
- Calculate calibration factor.
114
+ Read in-flight calibration data.
140
115
 
141
- Steps to calculate calibration factor:
116
+ In-flight calibration data file will contain rows where each line
117
+ has 8 numbers, with the first being a time stamp in MET, and the next
118
+ 7 being the factors for the 7 detectors.
142
119
 
143
- 1. Convert input time to match time format in the calibration data file.
144
- 2. Find the nearest in time calibration data point.
145
- 3. Linear interpolate between those two nearest time and get factor for input time.
120
+ This file will be updated weekly with new calibration data. In other
121
+ words, one line of data will be added each week to the existing file.
122
+ File will be in CSV format. Processing won't be kicked off until there
123
+ is in-flight calibration data that covers science data.
146
124
 
147
- What this function is doing:
125
+ TODO: decide filename convention given this information. This function
126
+ is a placeholder for reading in the calibration data until we decide on
127
+ how to read calibration data through dependencies list.
148
128
 
149
- | 1. **Reading Calibration Data**: The function first reads a file containing
150
- | calibration data for electron measurements over time. This data helps
151
- | adjust or correct the measurements based on changes in the instrument's
152
- | sensitivity.
129
+ Returns
130
+ -------
131
+ in_flight_cal_df : pandas.DataFrame
132
+ DataFrame with in-flight calibration data.
133
+ """
134
+ # TODO: Read in in-flight calibration file.
153
135
 
154
- | 2. **Interpolating Calibration Factors**: Imagine you have several points on
155
- | a graph, and you want to estimate values between those points. In our case,
156
- | these points represent calibration measurements taken at different times.
157
- | The function figures out which two calibration points are closest in time
158
- | to the specific measurement time you're interested in.
136
+ # Define the column headers
137
+ columns = ["met_time", "cem1", "cem2", "cem3", "cem4", "cem5", "cem6", "cem7"]
159
138
 
160
- | 3. **Calculating Factors**: Once it finds these two nearby calibration points,
161
- | the function calculates a correction factor by drawing a straight line
162
- | between them (linear interpolation). This factor helps adjust the measurement
163
- | to make it more accurate, considering how the instrument's sensitivity changed
164
- | between those two calibration points.
139
+ # Create an empty DataFrame with the specified columns
140
+ empty_df = pd.DataFrame(columns=columns)
141
+ return empty_df
165
142
 
166
- | 4. **Returning the Correction Factor**: Finally, the function returns this
167
- | correction factor. You can then use this factor to adjust or calibrate your
168
- | measurements at the specific time you're interested in. This ensures that
169
- | your measurements are as accurate as possible, taking into account the
170
- | instrument's changing sensitivity over time.
171
143
 
172
- Parameters
173
- ----------
174
- time : int
175
- Input time.
144
+ def calculate_calibration_factor(
145
+ acquisition_times: np.ndarray, cal_times: np.ndarray, cal_data: np.ndarray
146
+ ) -> npt.NDArray:
176
147
  """
177
- # NOTE: waiting on fake calibration data to write this.
178
- pass
148
+ Calculate calibration factor using linear interpolation.
149
+
150
+ Steps to calculate calibration factor:
151
+ 1. Convert input time to match time format in the calibration data file.
152
+ Both times should be in S/C MET time.
153
+ 2. Find the nearest in time calibration data point.
154
+ 3. Linear interpolate between those two nearest time and get factor for
155
+ input time.
179
156
 
157
+ Parameters
158
+ ----------
159
+ acquisition_times : numpy.ndarray
160
+ Data points to interpolate. Shape is (24, 30).
161
+ cal_times : numpy.ndarray
162
+ X-coordinates data points. Calibration times. Shape is (n,).
163
+ cal_data : numpy.ndarray
164
+ Y-coordinates data points. Calibration data of corresponding cal_times.
165
+ Shape is (n, 7).
180
166
 
181
- def apply_in_flight_calibration(data: np.ndarray) -> None:
167
+ Returns
168
+ -------
169
+ calibration_factor : numpy.ndarray
170
+ Calibration factor for each CEM detector. Shape is (24, 30, 7)
171
+ where last 7 dimension contains calibration factor for each CEM detector.
172
+ """
173
+ # Raise error if there is no pre or post time in cal_times. SWE does not
174
+ # want to extrapolate calibration data.
175
+ if (
176
+ acquisition_times.min() < cal_times.min()
177
+ or acquisition_times.max() > cal_times.max()
178
+ ):
179
+ error_msg = (
180
+ f"Acquisition min/max times: {acquisition_times.min()} to "
181
+ f"{acquisition_times.max()}. "
182
+ f"Calibration min/max times: {cal_times.min()} to {cal_times.max()}. "
183
+ "Acquisition times should be within calibration time range."
184
+ )
185
+ raise ValueError(error_msg)
186
+
187
+ # This line of code finds the indices of acquisition_times in cal_times where
188
+ # acquisition_times should be inserted to maintain order. As a result, it finds
189
+ # its nearest pre and post time from cal_times.
190
+ input_time_indices = np.searchsorted(cal_times, acquisition_times)
191
+
192
+ # Assign to a variable for better readability
193
+ x = acquisition_times
194
+ xp = cal_times
195
+ fp = cal_data
196
+
197
+ # Given this situation which will be the case for SWE data
198
+ # where data will fall in between two calibration times and
199
+ # not be exactly equal to any calibration time,
200
+ # >>> a = [1, 2, 3]
201
+ # >>> np.searchsorted(a, [2.5])
202
+ # array([2])
203
+ # we need to use (j - 1) to get pre time indices. (j-1) is
204
+ # pre time indices and j is post time indices.
205
+ j = input_time_indices
206
+ w = (x - xp[j - 1]) / (xp[j] - xp[j - 1])
207
+ return fp[j - 1] + w[..., None] * (fp[j] - fp[j - 1])
208
+
209
+
210
+ def apply_in_flight_calibration(
211
+ corrected_counts: np.ndarray, acquisition_time: np.ndarray
212
+ ) -> npt.NDArray:
182
213
  """
183
214
  Apply in flight calibration to full cycle data.
184
215
 
@@ -188,12 +219,29 @@ def apply_in_flight_calibration(data: np.ndarray) -> None:
188
219
 
189
220
  Parameters
190
221
  ----------
191
- data : numpy.ndarray
192
- Full cycle data array.
222
+ corrected_counts : numpy.ndarray
223
+ Corrected count of full cycle data. Data shape is (24, 30, 7).
224
+ acquisition_time : numpy.ndarray
225
+ Acquisition time of full cycle data. Data shape is (24, 30).
226
+
227
+ Returns
228
+ -------
229
+ corrected_counts : numpy.ndarray
230
+ Corrected count of full cycle data after applying in-flight calibration.
231
+ Array shape is (24, 30, 7).
193
232
  """
194
- # calculate calibration factor
195
- # Apply to all data
196
- pass
233
+ # Read in in-flight calibration data
234
+ in_flight_cal_df = read_in_flight_cal_data()
235
+ # calculate calibration factor.
236
+ # return shape of calculate_calibration_factor is (24, 30, 7) where
237
+ # last 7 dimension contains calibration factor for each CEM detector.
238
+ cal_factor = calculate_calibration_factor(
239
+ acquisition_time,
240
+ in_flight_cal_df["met_time"].values,
241
+ in_flight_cal_df.iloc[:, 1:].values,
242
+ )
243
+ # Apply to full cycle data
244
+ return corrected_counts.astype(np.float64) * cal_factor
197
245
 
198
246
 
199
247
  def populate_full_cycle_data(
@@ -231,7 +279,10 @@ def populate_full_cycle_data(
231
279
  # to use in level 2 processing to calculate
232
280
  # spin phase. This is done below by using information from
233
281
  # science packet.
234
- acquisition_times = np.zeros((energy_steps, angle, cem_detectors))
282
+ acquisition_times = np.zeros((energy_steps, angle))
283
+
284
+ # Store acquisition duration for later calculation in this function
285
+ acq_duration_arr = np.zeros((energy_steps, angle))
235
286
 
236
287
  # Initialize esa_step_number and column_index.
237
288
  # esa_step_number goes from 0 to 719 range where
@@ -249,19 +300,12 @@ def populate_full_cycle_data(
249
300
  acq_duration = l1a_data["acq_duration"].data[packet_index + index]
250
301
  settle_duration = l1a_data["settle_duration"].data[packet_index + index]
251
302
  corrected_counts = deadtime_correction(decompressed_counts, acq_duration)
252
- # Convert counts to rate
253
- counts_rate = convert_counts_to_rate(corrected_counts, acq_duration)
254
303
 
255
304
  # Each quarter cycle data should have same acquisition start time coarse
256
305
  # and fine value. We will use that as base time to calculate each
257
- # acquisition time for each count data. Acquisition time of each count
258
- # data point will be calculated using this formula:
306
+ # acquisition time for each count data.
259
307
  # base_quarter_cycle_acq_time = acq_start_coarse +
260
308
  # acq_start_fine / 1000000
261
- # each_count_acq_time = base_quarter_cycle_acq_time +
262
- # (step * ( acq_duration + settle_duration) / 1000 )
263
- # where step goes from 0 to 179, acq_start_coarse is in seconds and
264
- # acq_start_fine is in microseconds and acq_duration is in milliseconds.
265
309
  base_quarter_cycle_acq_time = (
266
310
  l1a_data["acq_start_coarse"].data[packet_index + index]
267
311
  + l1a_data["acq_start_fine"].data[packet_index + index] / 1000000
@@ -273,18 +317,27 @@ def populate_full_cycle_data(
273
317
  # Get esa voltage value from esa lookup table and
274
318
  # use that to get row index in full data array
275
319
  esa_voltage_value = esa_lookup_table.loc[esa_step_number]["esa_v"]
276
- esa_voltage_row_index = esa_voltage_row_index_dict[esa_voltage_value]
320
+ esa_voltage_row_index = ESA_VOLTAGE_ROW_INDEX_DICT[esa_voltage_value]
277
321
 
278
322
  # every six steps, increment column index
279
323
  if esa_step_number % 6 == 0:
280
324
  column_index += 1
281
325
  # Put counts rate in full cycle data array
282
- full_cycle_data[esa_voltage_row_index][column_index] = counts_rate[step]
283
- # Put acquisition time in acquisition_times array
326
+ full_cycle_data[esa_voltage_row_index][column_index] = corrected_counts[
327
+ step
328
+ ]
329
+ # Acquisition time (in seconds) of each count data point will be
330
+ # using this formula:
331
+ # each_count_acq_time = base_quarter_cycle_acq_time +
332
+ # (step * ( acq_duration + settle_duration) / 1000000 )
333
+ # where step goes from 0 to 179, acq_start_coarse is in seconds and
334
+ # acq_start_fine is in microseconds and acq_duration is in microseconds.
284
335
  acquisition_times[esa_voltage_row_index][column_index] = (
285
336
  base_quarter_cycle_acq_time
286
- + (step * (acq_duration + settle_duration) / 1000)
337
+ + (step * (acq_duration + settle_duration) / 1000000)
287
338
  )
339
+ # Store acquisition duration for later calculation
340
+ acq_duration_arr[esa_voltage_row_index][column_index] = acq_duration
288
341
  esa_step_number += 1
289
342
 
290
343
  # reset column index for next quarter cycle
@@ -295,11 +348,17 @@ def populate_full_cycle_data(
295
348
  # data. But for now, we are advice to continue with current setup and can
296
349
  # add/change it when we get real data.
297
350
 
351
+ # Apply calibration based on in-flight calibration.
352
+ calibrated_counts = apply_in_flight_calibration(full_cycle_data, acquisition_times)
353
+
354
+ # Convert counts to rate
355
+ counts_rate = convert_counts_to_rate(calibrated_counts, acq_duration)
356
+
298
357
  # Store count data and acquisition times of full cycle data in xr.Dataset
299
358
  full_cycle_ds = xr.Dataset(
300
359
  {
301
- "full_cycle_data": (["energy", "angle", "cem"], full_cycle_data),
302
- "sci_step_acq_time_sec": (["energy", "angle", "cem"], acquisition_times),
360
+ "full_cycle_data": (["esa_step", "spin_sector", "cem_id"], counts_rate),
361
+ "acquisition_time": (["esa_step", "spin_sector"], acquisition_times),
303
362
  }
304
363
  )
305
364
 
@@ -464,7 +523,7 @@ def swe_l1b_science(l1a_data: xr.Dataset, data_version: str) -> xr.Dataset:
464
523
 
465
524
  # save full data array to file
466
525
  full_cycle_science_data.append(full_cycle_ds["full_cycle_data"].data)
467
- full_cycle_acq_times.append(full_cycle_ds["sci_step_acq_time_sec"].data)
526
+ full_cycle_acq_times.append(full_cycle_ds["acquisition_time"].data)
468
527
 
469
528
  # ------------------------------------------------------------------
470
529
  # Save data to dataset.
@@ -486,34 +545,34 @@ def swe_l1b_science(l1a_data: xr.Dataset, data_version: str) -> xr.Dataset:
486
545
  attrs=cdf_attrs.get_variable_attributes("epoch"),
487
546
  )
488
547
 
489
- energy = xr.DataArray(
548
+ esa_step = xr.DataArray(
490
549
  np.arange(24),
491
- name="energy",
492
- dims=["energy"],
493
- attrs=cdf_attrs.get_variable_attributes("energy"),
550
+ name="esa_step",
551
+ dims=["esa_step"],
552
+ attrs=cdf_attrs.get_variable_attributes("esa_step"),
494
553
  )
495
554
 
496
555
  # NOTE: LABL_PTR_1 should be CDF_CHAR.
497
- energy_label = xr.DataArray(
498
- energy.values.astype(str),
499
- name="energy_label",
500
- dims=["energy_label"],
501
- attrs=cdf_attrs.get_variable_attributes("energy_label"),
556
+ esa_step_label = xr.DataArray(
557
+ esa_step.values.astype(str),
558
+ name="esa_step_label",
559
+ dims=["esa_step"],
560
+ attrs=cdf_attrs.get_variable_attributes("esa_step_label"),
502
561
  )
503
562
 
504
- angle = xr.DataArray(
563
+ spin_sector = xr.DataArray(
505
564
  np.arange(30),
506
- name="angle",
507
- dims=["angle"],
508
- attrs=cdf_attrs.get_variable_attributes("angle"),
565
+ name="spin_sector",
566
+ dims=["spin_sector"],
567
+ attrs=cdf_attrs.get_variable_attributes("spin_sector"),
509
568
  )
510
569
 
511
570
  # NOTE: LABL_PTR_2 should be CDF_CHAR.
512
- angle_label = xr.DataArray(
513
- angle.values.astype(str),
514
- name="angle_label",
515
- dims=["angle_label"],
516
- attrs=cdf_attrs.get_variable_attributes("angle_label"),
571
+ spin_sector_label = xr.DataArray(
572
+ spin_sector.values.astype(str),
573
+ name="spin_sector_label",
574
+ dims=["spin_sector"],
575
+ attrs=cdf_attrs.get_variable_attributes("spin_sector_label"),
517
576
  )
518
577
 
519
578
  cycle = xr.DataArray(
@@ -523,19 +582,19 @@ def swe_l1b_science(l1a_data: xr.Dataset, data_version: str) -> xr.Dataset:
523
582
  attrs=cdf_attrs.get_variable_attributes("cycle"),
524
583
  )
525
584
 
526
- cem = xr.DataArray(
585
+ cem_id = xr.DataArray(
527
586
  np.arange(7, dtype=np.float64),
528
- name="cem",
529
- dims=["cem"],
530
- attrs=cdf_attrs.get_variable_attributes("cem"),
587
+ name="cem_id",
588
+ dims=["cem_id"],
589
+ attrs=cdf_attrs.get_variable_attributes("cem_id"),
531
590
  )
532
591
 
533
592
  # NOTE: LABL_PTR_3 should be CDF_CHAR.
534
- cem_label = xr.DataArray(
535
- cem.values.astype(str),
536
- name="cem_label",
537
- dims=["cem_label"],
538
- attrs=cdf_attrs.get_variable_attributes("cem_label"),
593
+ cem_id_label = xr.DataArray(
594
+ cem_id.values.astype(str),
595
+ name="cem_id_label",
596
+ dims=["cem_id"],
597
+ attrs=cdf_attrs.get_variable_attributes("cem_id_label"),
539
598
  )
540
599
 
541
600
  # Add science data and it's associated metadata into dataset.
@@ -556,26 +615,26 @@ def swe_l1b_science(l1a_data: xr.Dataset, data_version: str) -> xr.Dataset:
556
615
  dataset = xr.Dataset(
557
616
  coords={
558
617
  "epoch": epoch_time,
559
- "energy": energy,
560
- "angle": angle,
561
- "cem": cem,
618
+ "esa_step": esa_step,
619
+ "spin_sector": spin_sector,
620
+ "cem_id": cem_id,
562
621
  "cycle": cycle,
563
- "energy_label": energy_label,
564
- "angle_label": angle_label,
565
- "cem_label": cem_label,
622
+ "esa_step_label": esa_step_label,
623
+ "spin_sector_label": spin_sector_label,
624
+ "cem_id_label": cem_id_label,
566
625
  },
567
626
  attrs=cdf_attrs.get_global_attributes("imap_swe_l1b_sci"),
568
627
  )
569
628
 
570
629
  dataset["science_data"] = xr.DataArray(
571
630
  full_cycle_science_data,
572
- dims=["epoch", "energy", "angle", "cem"],
631
+ dims=["epoch", "esa_step", "spin_sector", "cem_id"],
573
632
  attrs=cdf_attrs.get_variable_attributes("science_data"),
574
633
  )
575
- dataset["sci_step_acq_time_sec"] = xr.DataArray(
634
+ dataset["acquisition_time"] = xr.DataArray(
576
635
  full_cycle_acq_times,
577
- dims=["epoch", "energy", "angle", "cem"],
578
- attrs=cdf_attrs.get_variable_attributes("sci_step_acq_time_sec"),
636
+ dims=["epoch", "esa_step", "spin_sector"],
637
+ attrs=cdf_attrs.get_variable_attributes("acquisition_time"),
579
638
  )
580
639
 
581
640
  # create xarray dataset for each metadata field