imap-processing 0.12.0__py3-none-any.whl → 0.13.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 (272) hide show
  1. imap_processing/__init__.py +1 -0
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/ccsds_data.py +1 -2
  4. imap_processing/ccsds/excel_to_xtce.py +1 -2
  5. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +18 -12
  6. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +569 -0
  7. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +5 -5
  9. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +20 -1
  10. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +6 -4
  11. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +3 -3
  12. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +15 -0
  13. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +22 -0
  14. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +16 -0
  15. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +178 -5
  16. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -19
  18. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +8 -48
  19. imap_processing/cdf/utils.py +41 -33
  20. imap_processing/cli.py +463 -234
  21. imap_processing/codice/codice_l1a.py +260 -47
  22. imap_processing/codice/codice_l1b.py +51 -152
  23. imap_processing/codice/constants.py +38 -1
  24. imap_processing/ena_maps/ena_maps.py +658 -65
  25. imap_processing/ena_maps/utils/coordinates.py +1 -1
  26. imap_processing/ena_maps/utils/spatial_utils.py +10 -5
  27. imap_processing/glows/l1a/glows_l1a.py +28 -99
  28. imap_processing/glows/l1a/glows_l1a_data.py +2 -2
  29. imap_processing/glows/l1b/glows_l1b.py +1 -4
  30. imap_processing/glows/l1b/glows_l1b_data.py +1 -3
  31. imap_processing/glows/l2/glows_l2.py +2 -5
  32. imap_processing/hi/l1a/hi_l1a.py +31 -12
  33. imap_processing/hi/l1b/hi_l1b.py +80 -43
  34. imap_processing/hi/l1c/hi_l1c.py +12 -16
  35. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
  36. imap_processing/hit/hit_utils.py +93 -35
  37. imap_processing/hit/l0/decom_hit.py +3 -1
  38. imap_processing/hit/l1a/hit_l1a.py +30 -25
  39. imap_processing/hit/l1b/constants.py +6 -2
  40. imap_processing/hit/l1b/hit_l1b.py +279 -318
  41. imap_processing/hit/l2/constants.py +37 -0
  42. imap_processing/hit/l2/hit_l2.py +373 -264
  43. imap_processing/ialirt/l0/parse_mag.py +138 -10
  44. imap_processing/ialirt/l0/process_swapi.py +69 -0
  45. imap_processing/ialirt/l0/process_swe.py +318 -22
  46. imap_processing/ialirt/packet_definitions/ialirt.xml +216 -212
  47. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +1 -1
  48. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +1 -1
  49. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +14 -14
  50. imap_processing/ialirt/utils/grouping.py +1 -1
  51. imap_processing/idex/idex_constants.py +9 -1
  52. imap_processing/idex/idex_l0.py +22 -8
  53. imap_processing/idex/idex_l1a.py +75 -44
  54. imap_processing/idex/idex_l1b.py +9 -8
  55. imap_processing/idex/idex_l2a.py +79 -45
  56. imap_processing/idex/idex_l2b.py +120 -0
  57. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -39
  58. imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
  59. imap_processing/lo/l0/lo_science.py +1 -2
  60. imap_processing/lo/l1a/lo_l1a.py +1 -4
  61. imap_processing/lo/l1b/lo_l1b.py +527 -6
  62. imap_processing/lo/l1b/tof_conversions.py +11 -0
  63. imap_processing/lo/l1c/lo_l1c.py +1 -4
  64. imap_processing/mag/constants.py +43 -0
  65. imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
  66. imap_processing/mag/l1a/mag_l1a.py +2 -9
  67. imap_processing/mag/l1a/mag_l1a_data.py +10 -10
  68. imap_processing/mag/l1b/mag_l1b.py +84 -17
  69. imap_processing/mag/l1c/interpolation_methods.py +180 -3
  70. imap_processing/mag/l1c/mag_l1c.py +236 -70
  71. imap_processing/mag/l2/mag_l2.py +140 -0
  72. imap_processing/mag/l2/mag_l2_data.py +288 -0
  73. imap_processing/spacecraft/quaternions.py +1 -3
  74. imap_processing/spice/geometry.py +3 -3
  75. imap_processing/spice/kernels.py +0 -276
  76. imap_processing/spice/pointing_frame.py +257 -0
  77. imap_processing/spice/repoint.py +48 -19
  78. imap_processing/spice/spin.py +38 -33
  79. imap_processing/spice/time.py +24 -0
  80. imap_processing/swapi/l1/swapi_l1.py +16 -12
  81. imap_processing/swapi/l2/swapi_l2.py +116 -4
  82. imap_processing/swapi/swapi_utils.py +32 -0
  83. imap_processing/swe/l1a/swe_l1a.py +2 -9
  84. imap_processing/swe/l1a/swe_science.py +8 -11
  85. imap_processing/swe/l1b/swe_l1b.py +898 -23
  86. imap_processing/swe/l2/swe_l2.py +21 -77
  87. imap_processing/swe/utils/swe_constants.py +1 -0
  88. imap_processing/tests/ccsds/test_excel_to_xtce.py +1 -1
  89. imap_processing/tests/cdf/test_utils.py +14 -16
  90. imap_processing/tests/codice/conftest.py +44 -33
  91. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
  92. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
  93. imap_processing/tests/codice/test_codice_l1a.py +20 -11
  94. imap_processing/tests/codice/test_codice_l1b.py +6 -7
  95. imap_processing/tests/conftest.py +78 -22
  96. imap_processing/tests/ena_maps/test_ena_maps.py +462 -33
  97. imap_processing/tests/ena_maps/test_spatial_utils.py +1 -1
  98. imap_processing/tests/glows/conftest.py +10 -14
  99. imap_processing/tests/glows/test_glows_decom.py +4 -4
  100. imap_processing/tests/glows/test_glows_l1a_cdf.py +6 -27
  101. imap_processing/tests/glows/test_glows_l1a_data.py +6 -8
  102. imap_processing/tests/glows/test_glows_l1b.py +11 -11
  103. imap_processing/tests/glows/test_glows_l1b_data.py +5 -5
  104. imap_processing/tests/glows/test_glows_l2.py +2 -8
  105. imap_processing/tests/hi/conftest.py +1 -1
  106. imap_processing/tests/hi/test_hi_l1b.py +10 -12
  107. imap_processing/tests/hi/test_hi_l1c.py +27 -24
  108. imap_processing/tests/hi/test_l1a.py +7 -9
  109. imap_processing/tests/hi/test_science_direct_event.py +2 -2
  110. imap_processing/tests/hit/helpers/l1_validation.py +44 -43
  111. imap_processing/tests/hit/test_decom_hit.py +1 -1
  112. imap_processing/tests/hit/test_hit_l1a.py +9 -9
  113. imap_processing/tests/hit/test_hit_l1b.py +172 -217
  114. imap_processing/tests/hit/test_hit_l2.py +380 -118
  115. imap_processing/tests/hit/test_hit_utils.py +122 -55
  116. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
  117. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +1 -1
  118. imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
  119. imap_processing/tests/ialirt/unit/test_grouping.py +2 -2
  120. imap_processing/tests/ialirt/unit/test_parse_mag.py +71 -16
  121. imap_processing/tests/ialirt/unit/test_process_codicehi.py +3 -3
  122. imap_processing/tests/ialirt/unit/test_process_codicelo.py +3 -10
  123. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +4 -4
  124. imap_processing/tests/ialirt/unit/test_process_hit.py +3 -3
  125. imap_processing/tests/ialirt/unit/test_process_swapi.py +24 -16
  126. imap_processing/tests/ialirt/unit/test_process_swe.py +115 -7
  127. imap_processing/tests/idex/conftest.py +72 -7
  128. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20241206_v001.pkts +0 -0
  129. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20250108_v001.pkts +0 -0
  130. imap_processing/tests/idex/test_idex_l0.py +33 -11
  131. imap_processing/tests/idex/test_idex_l1a.py +50 -23
  132. imap_processing/tests/idex/test_idex_l1b.py +104 -25
  133. imap_processing/tests/idex/test_idex_l2a.py +48 -32
  134. imap_processing/tests/idex/test_idex_l2b.py +93 -0
  135. imap_processing/tests/lo/test_lo_l1a.py +3 -3
  136. imap_processing/tests/lo/test_lo_l1b.py +371 -6
  137. imap_processing/tests/lo/test_lo_l1c.py +1 -1
  138. imap_processing/tests/lo/test_lo_science.py +6 -7
  139. imap_processing/tests/lo/test_star_sensor.py +1 -1
  140. imap_processing/tests/mag/conftest.py +58 -9
  141. imap_processing/tests/mag/test_mag_decom.py +4 -3
  142. imap_processing/tests/mag/test_mag_l1a.py +13 -7
  143. imap_processing/tests/mag/test_mag_l1b.py +9 -9
  144. imap_processing/tests/mag/test_mag_l1c.py +151 -47
  145. imap_processing/tests/mag/test_mag_l2.py +130 -0
  146. imap_processing/tests/mag/test_mag_validation.py +144 -7
  147. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-in.csv +1217 -0
  148. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-out.csv +1857 -0
  149. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-in.csv +1217 -0
  150. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-out.csv +1857 -0
  151. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-in.csv +1217 -0
  152. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-out.csv +1793 -0
  153. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-in.csv +1217 -0
  154. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-out.csv +1793 -0
  155. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-burst-in.csv +2561 -0
  156. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-in.csv +961 -0
  157. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-out.csv +1539 -0
  158. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-in.csv +1921 -0
  159. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-out.csv +2499 -0
  160. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-in.csv +865 -0
  161. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-out.csv +1196 -0
  162. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-in.csv +1729 -0
  163. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-out.csv +3053 -0
  164. imap_processing/tests/mag/validation/L2/imap_mag_l1b_norm-mago_20251017_v002.cdf +0 -0
  165. imap_processing/tests/mag/validation/calibration/imap_mag_l2-calibration-matrices_20251017_v004.cdf +0 -0
  166. imap_processing/tests/mag/validation/calibration/imap_mag_l2-offsets-norm_20251017_20251017_v001.cdf +0 -0
  167. imap_processing/tests/spacecraft/test_quaternions.py +1 -1
  168. imap_processing/tests/spice/test_data/fake_repoint_data.csv +4 -4
  169. imap_processing/tests/spice/test_data/fake_spin_data.csv +11 -11
  170. imap_processing/tests/spice/test_geometry.py +3 -3
  171. imap_processing/tests/spice/test_kernels.py +1 -200
  172. imap_processing/tests/spice/test_pointing_frame.py +185 -0
  173. imap_processing/tests/spice/test_repoint.py +20 -10
  174. imap_processing/tests/spice/test_spin.py +50 -9
  175. imap_processing/tests/spice/test_time.py +14 -0
  176. imap_processing/tests/swapi/lut/imap_swapi_esa-unit-conversion_20250211_v000.csv +73 -0
  177. imap_processing/tests/swapi/lut/imap_swapi_lut-notes_20250211_v000.csv +1025 -0
  178. imap_processing/tests/swapi/test_swapi_l1.py +7 -9
  179. imap_processing/tests/swapi/test_swapi_l2.py +180 -8
  180. imap_processing/tests/swe/lut/checker-board-indices.csv +24 -0
  181. imap_processing/tests/swe/lut/imap_swe_esa-lut_20250301_v000.csv +385 -0
  182. imap_processing/tests/swe/lut/imap_swe_l1b-in-flight-cal_20240510_20260716_v000.csv +3 -0
  183. imap_processing/tests/swe/test_swe_l1a.py +6 -6
  184. imap_processing/tests/swe/test_swe_l1a_science.py +3 -3
  185. imap_processing/tests/swe/test_swe_l1b.py +162 -24
  186. imap_processing/tests/swe/test_swe_l2.py +82 -102
  187. imap_processing/tests/test_cli.py +171 -88
  188. imap_processing/tests/test_utils.py +2 -1
  189. imap_processing/tests/ultra/data/mock_data.py +49 -21
  190. imap_processing/tests/ultra/unit/conftest.py +53 -70
  191. imap_processing/tests/ultra/unit/test_badtimes.py +2 -4
  192. imap_processing/tests/ultra/unit/test_cullingmask.py +4 -6
  193. imap_processing/tests/ultra/unit/test_de.py +3 -10
  194. imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
  195. imap_processing/tests/ultra/unit/test_decom_apid_881.py +15 -16
  196. imap_processing/tests/ultra/unit/test_decom_apid_883.py +12 -10
  197. imap_processing/tests/ultra/unit/test_decom_apid_896.py +202 -55
  198. imap_processing/tests/ultra/unit/test_lookup_utils.py +23 -1
  199. imap_processing/tests/ultra/unit/test_spacecraft_pset.py +3 -4
  200. imap_processing/tests/ultra/unit/test_ultra_l1a.py +84 -307
  201. imap_processing/tests/ultra/unit/test_ultra_l1b.py +30 -12
  202. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
  203. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +4 -1
  204. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +163 -29
  205. imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
  206. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +32 -43
  207. imap_processing/tests/ultra/unit/test_ultra_l2.py +230 -0
  208. imap_processing/ultra/constants.py +1 -1
  209. imap_processing/ultra/l0/decom_tools.py +21 -34
  210. imap_processing/ultra/l0/decom_ultra.py +168 -204
  211. imap_processing/ultra/l0/ultra_utils.py +152 -136
  212. imap_processing/ultra/l1a/ultra_l1a.py +55 -243
  213. imap_processing/ultra/l1b/badtimes.py +1 -4
  214. imap_processing/ultra/l1b/cullingmask.py +2 -6
  215. imap_processing/ultra/l1b/de.py +62 -47
  216. imap_processing/ultra/l1b/extendedspin.py +8 -4
  217. imap_processing/ultra/l1b/lookup_utils.py +72 -9
  218. imap_processing/ultra/l1b/ultra_l1b.py +3 -8
  219. imap_processing/ultra/l1b/ultra_l1b_culling.py +4 -4
  220. imap_processing/ultra/l1b/ultra_l1b_extended.py +236 -78
  221. imap_processing/ultra/l1c/histogram.py +2 -6
  222. imap_processing/ultra/l1c/spacecraft_pset.py +2 -4
  223. imap_processing/ultra/l1c/ultra_l1c.py +1 -5
  224. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +107 -60
  225. imap_processing/ultra/l2/ultra_l2.py +299 -0
  226. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +526 -0
  227. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +526 -0
  228. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +526 -0
  229. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +526 -0
  230. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -2
  231. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -0
  232. imap_processing/ultra/packet_definitions/README.md +38 -0
  233. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +15302 -482
  234. imap_processing/ultra/utils/ultra_l1_utils.py +13 -12
  235. imap_processing/utils.py +1 -1
  236. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +3 -2
  237. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/RECORD +264 -225
  238. imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
  239. imap_processing/mag/imap_mag_sdc-configuration_v001.yaml +0 -6
  240. imap_processing/mag/l1b/__init__.py +0 -0
  241. imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
  242. imap_processing/swe/l1b/swe_l1b_science.py +0 -699
  243. imap_processing/tests/swe/test_swe_l1b_science.py +0 -103
  244. imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
  245. imap_processing/ultra/lookup_tables/ultra_90_dps_exposure_compressed.cdf +0 -0
  246. /imap_processing/idex/packet_definitions/{idex_packet_definition.xml → idex_science_packet_definition.xml} +0 -0
  247. /imap_processing/tests/ialirt/{test_data → data}/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  248. /imap_processing/tests/ialirt/{test_data → data}/l0/461971383-404.bin +0 -0
  249. /imap_processing/tests/ialirt/{test_data → data}/l0/461971384-405.bin +0 -0
  250. /imap_processing/tests/ialirt/{test_data → data}/l0/461971385-406.bin +0 -0
  251. /imap_processing/tests/ialirt/{test_data → data}/l0/461971386-407.bin +0 -0
  252. /imap_processing/tests/ialirt/{test_data → data}/l0/461971387-408.bin +0 -0
  253. /imap_processing/tests/ialirt/{test_data → data}/l0/461971388-409.bin +0 -0
  254. /imap_processing/tests/ialirt/{test_data → data}/l0/461971389-410.bin +0 -0
  255. /imap_processing/tests/ialirt/{test_data → data}/l0/461971390-411.bin +0 -0
  256. /imap_processing/tests/ialirt/{test_data → data}/l0/461971391-412.bin +0 -0
  257. /imap_processing/tests/ialirt/{test_data → data}/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  258. /imap_processing/tests/ialirt/{test_data → data}/l0/IALiRT Raw Packet Telemetry.txt +0 -0
  259. /imap_processing/tests/ialirt/{test_data → data}/l0/apid01152.tlm +0 -0
  260. /imap_processing/tests/ialirt/{test_data → data}/l0/eu_SWP_IAL_20240826_152033.csv +0 -0
  261. /imap_processing/tests/ialirt/{test_data → data}/l0/hi_fsw_view_1_ccsds.bin +0 -0
  262. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.ccsds +0 -0
  263. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.csv +0 -0
  264. /imap_processing/tests/ialirt/{test_data → data}/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +0 -0
  265. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  266. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  267. /imap_processing/tests/ialirt/{test_data → data}/l0/sample_decoded_i-alirt_data.csv +0 -0
  268. /imap_processing/tests/mag/validation/{imap_calibration_mag_20240229_v01.cdf → calibration/imap_mag_l1b-calibration_20240229_v001.cdf} +0 -0
  269. /imap_processing/{swe/l1b/engineering_unit_convert_table.csv → tests/swe/lut/imap_swe_eu-conversion_20240510_v000.csv} +0 -0
  270. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/LICENSE +0 -0
  271. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
  272. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
@@ -1,699 +0,0 @@
1
- """Contains code to perform SWE L1b science processing."""
2
-
3
- import logging
4
-
5
- import numpy as np
6
- import numpy.typing as npt
7
- import pandas as pd
8
- import xarray as xr
9
-
10
- from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
11
- from imap_processing.spice.time import met_to_ttj2000ns
12
- from imap_processing.swe.utils import swe_constants
13
- from imap_processing.swe.utils.swe_utils import (
14
- calculate_data_acquisition_time,
15
- combine_acquisition_time,
16
- read_lookup_table,
17
- )
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
-
22
- def get_esa_dataframe(esa_table_number: int) -> pd.DataFrame:
23
- """
24
- Read lookup table from file.
25
-
26
- Parameters
27
- ----------
28
- esa_table_number : int
29
- ESA table index number.
30
-
31
- Returns
32
- -------
33
- esa_steps : pandas.DataFrame
34
- ESA table_number and its associated values.
35
- """
36
- if esa_table_number not in [0, 1]:
37
- raise ValueError(f"Unknown ESA table number {esa_table_number}")
38
-
39
- # Get the lookup table DataFrame
40
- lookup_table = read_lookup_table()
41
-
42
- esa_steps = lookup_table.loc[lookup_table["table_index"] == esa_table_number]
43
- return esa_steps
44
-
45
-
46
- def deadtime_correction(counts: np.ndarray, acq_duration: int) -> npt.NDArray:
47
- """
48
- Calculate deadtime correction.
49
-
50
- Deadtime correction is a technique used in various fields, including
51
- nuclear physics, radiation detection, and particle counting, to compensate
52
- for the effects of the time period during which a detector is not able to
53
- record new events or measurements after detecting a previous event.
54
- This "deadtime" is essentially the time during which the detector is
55
- recovering from the previous detection and is unable to detect new events.
56
-
57
- In particle detectors, there is a finite time required for the detector to
58
- reset or recover after detecting a particle. During this deadtime, any
59
- subsequent particles that may have arrived go undetected. As a result,
60
- the recorded count rate appears to be lower than the actual count rate.
61
-
62
- Deadtime correction involves mathematically adjusting the measured count
63
- rates to compensate for this deadtime effect. This correction is crucial
64
- when dealing with high-intensity sources or particle fluxes, as the deadtime
65
- can significantly affect the accuracy of the measurements.
66
-
67
- Deadtime correction is important to ensure accurate measurements and data
68
- analysis in fields where event detection rates are high and where every
69
- detected event is critical for understanding physical processes.
70
-
71
- Parameters
72
- ----------
73
- counts : numpy.ndarray
74
- Counts data before deadtime corrections.
75
- acq_duration : int
76
- This is ACQ_DURATION from science packet. acq_duration is in microseconds.
77
-
78
- Returns
79
- -------
80
- corrected_count : numpy.ndarray
81
- Corrected counts.
82
- """
83
- # deadtime is 360 ns
84
- deadtime = 360e-9
85
- correct = 1.0 - (deadtime * (counts / (acq_duration * 1e-6)))
86
- correct = np.maximum(0.1, correct)
87
- corrected_count = np.divide(counts, correct)
88
- return corrected_count.astype(np.float64)
89
-
90
-
91
- def convert_counts_to_rate(data: np.ndarray, acq_duration: np.ndarray) -> npt.NDArray:
92
- """
93
- Convert counts to rate using sampling time.
94
-
95
- acq_duration is ACQ_DURATION from science packet.
96
-
97
- Parameters
98
- ----------
99
- data : numpy.ndarray
100
- Counts data.
101
- acq_duration : numpy.ndarray
102
- Acquisition duration. acq_duration is in microseconds.
103
-
104
- Returns
105
- -------
106
- numpy.ndarray
107
- Count rates array in seconds.
108
- """
109
- # convert microseconds to seconds
110
- acq_duration_sec = acq_duration * 1e-6
111
- count_rate = data / acq_duration_sec
112
- return count_rate.astype(np.float64)
113
-
114
-
115
- def read_in_flight_cal_data() -> pd.DataFrame:
116
- """
117
- Read in-flight calibration data.
118
-
119
- In-flight calibration data file will contain rows where each line
120
- has 8 numbers, with the first being a time stamp in MET, and the next
121
- 7 being the factors for the 7 detectors.
122
-
123
- This file will be updated weekly with new calibration data. In other
124
- words, one line of data will be added each week to the existing file.
125
- File will be in CSV format. Processing won't be kicked off until there
126
- is in-flight calibration data that covers science data.
127
-
128
- TODO: decide filename convention given this information. This function
129
- is a placeholder for reading in the calibration data until we decide on
130
- how to read calibration data through dependencies list.
131
-
132
- Returns
133
- -------
134
- in_flight_cal_df : pandas.DataFrame
135
- DataFrame with in-flight calibration data.
136
- """
137
- # TODO: Read in in-flight calibration file.
138
-
139
- # Define the column headers
140
- columns = ["met_time", "cem1", "cem2", "cem3", "cem4", "cem5", "cem6", "cem7"]
141
-
142
- # Create an empty DataFrame with the specified columns
143
- empty_df = pd.DataFrame(columns=columns)
144
- return empty_df
145
-
146
-
147
- def calculate_calibration_factor(
148
- acquisition_times: np.ndarray, cal_times: np.ndarray, cal_data: np.ndarray
149
- ) -> npt.NDArray:
150
- """
151
- Calculate calibration factor using linear interpolation.
152
-
153
- Steps to calculate calibration factor:
154
- 1. Convert input time to match time format in the calibration data file.
155
- Both times should be in S/C MET time.
156
- 2. Find the nearest in time calibration data point.
157
- 3. Linear interpolate between those two nearest time and get factor for
158
- input time.
159
-
160
- Parameters
161
- ----------
162
- acquisition_times : numpy.ndarray
163
- Data points to interpolate. Shape is (N_ESA_STEPS, N_ANGLE_SECTORS).
164
- cal_times : numpy.ndarray
165
- X-coordinates data points. Calibration times. Shape is (n,).
166
- cal_data : numpy.ndarray
167
- Y-coordinates data points. Calibration data of corresponding cal_times.
168
- Shape is (n, N_CEMS).
169
-
170
- Returns
171
- -------
172
- calibration_factor : numpy.ndarray
173
- Calibration factor for each CEM detector. Shape is
174
- (N_ESA_STEPS, N_ANGLE_SECTORS, N_CEMS) where last 7 dimension
175
- contains calibration factor for each CEM detector.
176
- """
177
- # Raise error if there is no pre or post time in cal_times. SWE does not
178
- # want to extrapolate calibration data.
179
- if (
180
- acquisition_times.min() < cal_times.min()
181
- or acquisition_times.max() > cal_times.max()
182
- ):
183
- error_msg = (
184
- f"Acquisition min/max times: {acquisition_times.min()} to "
185
- f"{acquisition_times.max()}. "
186
- f"Calibration min/max times: {cal_times.min()} to {cal_times.max()}. "
187
- "Acquisition times should be within calibration time range."
188
- )
189
- raise ValueError(error_msg)
190
-
191
- # This line of code finds the indices of acquisition_times in cal_times where
192
- # acquisition_times should be inserted to maintain order. As a result, it finds
193
- # its nearest pre and post time from cal_times.
194
- input_time_indices = np.searchsorted(cal_times, acquisition_times)
195
-
196
- # Assign to a variable for better readability
197
- x = acquisition_times
198
- xp = cal_times
199
- fp = cal_data
200
-
201
- # Given this situation which will be the case for SWE data
202
- # where data will fall in between two calibration times and
203
- # not be exactly equal to any calibration time,
204
- # >>> a = [1, 2, 3]
205
- # >>> np.searchsorted(a, [2.5])
206
- # array([2])
207
- # we need to use (j - 1) to get pre time indices. (j-1) is
208
- # pre time indices and j is post time indices.
209
- j = input_time_indices
210
- w = (x - xp[j - 1]) / (xp[j] - xp[j - 1])
211
- return fp[j - 1] + w[..., None] * (fp[j] - fp[j - 1])
212
-
213
-
214
- def apply_in_flight_calibration(
215
- corrected_counts: np.ndarray, acquisition_time: np.ndarray
216
- ) -> npt.NDArray:
217
- """
218
- Apply in flight calibration to full cycle data.
219
-
220
- These factors are used to account for changes in gain with time.
221
-
222
- They are derived from the weekly electron calibration data.
223
-
224
- Parameters
225
- ----------
226
- corrected_counts : numpy.ndarray
227
- Corrected count of full cycle data. Data shape is
228
- (N_ESA_STEPS, N_ANGLE_SECTORS, N_CEMS).
229
- acquisition_time : numpy.ndarray
230
- Acquisition time of full cycle data. Data shape is
231
- (N_ESA_STEPS, N_ANGLE_SECTORS).
232
-
233
- Returns
234
- -------
235
- corrected_counts : numpy.ndarray
236
- Corrected count of full cycle data after applying in-flight calibration.
237
- Array shape is (N_ESA_STEPS, N_ANGLE_SECTORS, N_CEMS).
238
- """
239
- # Read in in-flight calibration data
240
- in_flight_cal_df = read_in_flight_cal_data()
241
- # calculate calibration factor.
242
- # return shape of calculate_calibration_factor is
243
- # (N_ESA_STEPS, N_ANGLE_SECTORS, N_CEMS) where
244
- # last 7 dimension contains calibration factor for each CEM detector.
245
- cal_factor = calculate_calibration_factor(
246
- acquisition_time,
247
- in_flight_cal_df["met_time"].values,
248
- in_flight_cal_df.iloc[:, 1:].values,
249
- )
250
- # Apply to full cycle data
251
- return corrected_counts.astype(np.float64) * cal_factor
252
-
253
-
254
- def populate_full_cycle_data(
255
- l1a_data: xr.Dataset, packet_index: int, esa_table_num: int
256
- ) -> npt.NDArray:
257
- """
258
- Populate full cycle data array using esa lookup table and l1a_data.
259
-
260
- Parameters
261
- ----------
262
- l1a_data : xarray.Dataset
263
- L1a data with full cycle data only.
264
- packet_index : int
265
- Index of current packet in the whole packet list.
266
- esa_table_num : int
267
- ESA lookup table number.
268
-
269
- Returns
270
- -------
271
- full_cycle_ds : xarray.Dataset
272
- Full cycle data and its acquisition times.
273
- """
274
- esa_lookup_table = get_esa_dataframe(esa_table_num)
275
-
276
- # If esa lookup table number is 0, then populate using esa lookup table data
277
- # with information that esa step ramps up in even column and ramps down
278
- # in odd column every six steps.
279
- if esa_table_num == 0:
280
- # create new full cycle data array
281
- full_cycle_data = np.zeros(
282
- (
283
- swe_constants.N_ESA_STEPS,
284
- swe_constants.N_ANGLE_SECTORS,
285
- swe_constants.N_CEMS,
286
- )
287
- )
288
- # SWE needs to store acquisition time of each count data point
289
- # to use in level 2 processing to calculate
290
- # spin phase. This is done below by using information from
291
- # science packet.
292
- acquisition_times = np.zeros(
293
- (swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS)
294
- )
295
-
296
- # Store acquisition duration for later calculation in this function
297
- acq_duration_arr = np.zeros(
298
- (swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS)
299
- )
300
-
301
- # Initialize esa_step_number and column_index.
302
- # esa_step_number goes from 0 to 719 range where
303
- # 720 came from 24 x 30. full_cycle_data array has
304
- # (N_ESA_STEPS, N_ANGLE_SECTORS) dimension.
305
- esa_step_number = 0
306
- # column_index goes from 0 to 29 range where
307
- # 30 came from 30 column in full_cycle_data array
308
- column_index = -1
309
-
310
- # Go through four quarter cycle data packets
311
- for index in range(swe_constants.N_QUARTER_CYCLES):
312
- decompressed_counts = l1a_data["science_data"].data[packet_index + index]
313
- # Do deadtime correction
314
- acq_duration = l1a_data["acq_duration"].data[packet_index + index]
315
- settle_duration = l1a_data["settle_duration"].data[packet_index + index]
316
- corrected_counts = deadtime_correction(decompressed_counts, acq_duration)
317
-
318
- # Each quarter cycle data should have same acquisition start time coarse
319
- # and fine value. We will use that as base time to calculate each
320
- # acquisition time for each count data.
321
- base_quarter_cycle_acq_time = combine_acquisition_time(
322
- l1a_data["acq_start_coarse"].data[packet_index + index],
323
- l1a_data["acq_start_fine"].data[packet_index + index],
324
- )
325
-
326
- # Go through each quarter cycle's 180 ESA measurements
327
- # and put counts rate in full cycle data array
328
- for step in range(180):
329
- # Get esa voltage value from esa lookup table and
330
- # use that to get row index in full data array
331
- esa_voltage_value = esa_lookup_table.loc[esa_step_number]["esa_v"]
332
- esa_voltage_row_index = swe_constants.ESA_VOLTAGE_ROW_INDEX_DICT[
333
- esa_voltage_value
334
- ]
335
-
336
- # every six steps, increment column index
337
- if esa_step_number % 6 == 0:
338
- column_index += 1
339
- # Put counts rate in full cycle data array
340
- full_cycle_data[esa_voltage_row_index][column_index] = corrected_counts[
341
- step
342
- ]
343
- # Acquisition time (in seconds) of each count data point
344
- acquisition_times[esa_voltage_row_index][column_index] = (
345
- calculate_data_acquisition_time(
346
- base_quarter_cycle_acq_time,
347
- esa_step_number,
348
- acq_duration,
349
- settle_duration,
350
- )
351
- )
352
- # Store acquisition duration for later calculation
353
- acq_duration_arr[esa_voltage_row_index][column_index] = acq_duration
354
- esa_step_number += 1
355
-
356
- # reset column index for next quarter cycle
357
- column_index = -1
358
- # TODO: Apply in flight calibration to full cycle data
359
-
360
- # NOTE: We may get more lookup table with different setup when we get real
361
- # data. But for now, we are advice to continue with current setup and can
362
- # add/change it when we get real data.
363
-
364
- # Apply calibration based on in-flight calibration.
365
- calibrated_counts = apply_in_flight_calibration(full_cycle_data, acquisition_times)
366
-
367
- # Convert counts to rate
368
- counts_rate = convert_counts_to_rate(
369
- calibrated_counts, acq_duration_arr[:, :, np.newaxis]
370
- )
371
-
372
- # Store full cycle data in xr.Dataset for later use.
373
- full_cycle_ds = xr.Dataset(
374
- {
375
- "full_cycle_data": (["esa_step", "spin_sector", "cem_id"], counts_rate),
376
- "acquisition_time": (["esa_step", "spin_sector"], acquisition_times),
377
- "acq_duration": (["esa_step", "spin_sector"], acq_duration_arr),
378
- }
379
- )
380
-
381
- return full_cycle_ds
382
-
383
-
384
- def find_cycle_starts(cycles: np.ndarray) -> npt.NDArray:
385
- """
386
- Find index of where new cycle started.
387
-
388
- Brandon Stone helped developed this algorithm.
389
-
390
- Parameters
391
- ----------
392
- cycles : numpy.ndarray
393
- Array that contains quarter cycle information.
394
-
395
- Returns
396
- -------
397
- first_quarter_indices : numpy.ndarray
398
- Array of indices of start cycle.
399
- """
400
- if cycles.size < swe_constants.N_QUARTER_CYCLES:
401
- return np.array([], np.int64)
402
-
403
- # calculate difference between consecutive cycles
404
- diff = cycles[1:] - cycles[:-1]
405
-
406
- # This uses sliding window to find index where cycle starts.
407
- # This is what this below code line is doing:
408
- # [1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 0 0] # Is cycle zero?
409
- # [1 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1] # Next diff is one?
410
- # [1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1 0] # Next diff is one?
411
- # [0 1 1 1 0 1 0 0 1 0 1 1 1 0 1 0 0] # Next diff is one?
412
- #
413
- # [0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0] # And all?
414
- ione = diff == 1
415
- valid = (cycles == 0)[:-3] & ione[:-2] & ione[1:-1] & ione[2:]
416
- first_quarter_indices = np.where(valid)[0]
417
- return first_quarter_indices
418
-
419
-
420
- def get_indices_of_full_cycles(quarter_cycle: np.ndarray) -> npt.NDArray:
421
- """
422
- Get indices of full cycles.
423
-
424
- Parameters
425
- ----------
426
- quarter_cycle : numpy.ndarray
427
- Array that contains quarter cycles information.
428
-
429
- Returns
430
- -------
431
- full_cycles_indices : numpy.ndarray
432
- 1D array with indices of full cycle data.
433
- """
434
- indices_of_start = find_cycle_starts(quarter_cycle)
435
- # indices_of_start[..., None] creates array of shape(n, 1).
436
- # Eg. [[3], [8]]
437
- # np.arange(4)[None, ...] creates array of shape(1, 4)
438
- # Eg. [[0, 1, 2, 3]]
439
- # then we add both of them together to get an array of shape(n, 4)
440
- # Eg. [[3, 4, 5, 6], [8, 9, 10, 11]]
441
- full_cycles_indices = (
442
- indices_of_start[..., None]
443
- + np.arange(swe_constants.N_QUARTER_CYCLES)[None, ...]
444
- )
445
- return full_cycles_indices.reshape(-1)
446
-
447
-
448
- def filter_full_cycle_data(
449
- full_cycle_data_indices: np.ndarray, l1a_data: xr.Dataset
450
- ) -> xr.Dataset:
451
- """
452
- Filter metadata and science of packets that makes full cycles.
453
-
454
- Parameters
455
- ----------
456
- full_cycle_data_indices : numpy.ndarray
457
- Array with indices of full cycles.
458
- l1a_data : xarray.Dataset
459
- L1A dataset.
460
-
461
- Returns
462
- -------
463
- l1a_data : xarray.Dataset
464
- L1A dataset with filtered metadata.
465
- """
466
- for key, value in l1a_data.items():
467
- l1a_data[key] = value.data[full_cycle_data_indices]
468
- return l1a_data
469
-
470
-
471
- def swe_l1b_science(l1a_data: xr.Dataset, data_version: str) -> xr.Dataset:
472
- """
473
- SWE l1b science processing.
474
-
475
- Parameters
476
- ----------
477
- l1a_data : xarray.Dataset
478
- Input data.
479
- data_version : str
480
- Version of the data product being created.
481
-
482
- Returns
483
- -------
484
- dataset : xarray.Dataset
485
- Processed l1b data.
486
- """
487
- total_packets = len(l1a_data["science_data"].data)
488
-
489
- # Array to store list of table populated with data
490
- # of full cycles
491
- full_cycle_science_data = []
492
- # These two are carried in l1b for level 2 and 3 processing
493
- full_cycle_acq_times = []
494
- full_cycle_acq_duration = []
495
- packet_index = 0
496
- l1a_data_copy = l1a_data.copy(deep=True)
497
-
498
- full_cycle_data_indices = get_indices_of_full_cycles(l1a_data["quarter_cycle"].data)
499
- logger.debug(
500
- f"Quarter cycle data before filtering: {l1a_data_copy['quarter_cycle'].data}"
501
- )
502
-
503
- # Delete Raw Science Data from l1b and onwards
504
- del l1a_data_copy["raw_science_data"]
505
-
506
- if full_cycle_data_indices.size == 0:
507
- # Log that no data is found for science data
508
- return None
509
-
510
- if len(full_cycle_data_indices) != total_packets:
511
- # Filter metadata and science data of packets that makes full cycles
512
- full_cycle_l1a_data = l1a_data_copy.isel({"epoch": full_cycle_data_indices})
513
-
514
- # Update total packets
515
- total_packets = len(full_cycle_data_indices)
516
- logger.debug(
517
- "Quarters cycle after filtering: "
518
- f"{full_cycle_l1a_data['quarter_cycle'].data}"
519
- )
520
- if len(full_cycle_data_indices) != len(
521
- full_cycle_l1a_data["quarter_cycle"].data
522
- ):
523
- raise ValueError(
524
- "Error: full cycle data indices and filtered quarter cycle data size "
525
- "mismatch"
526
- )
527
-
528
- # Go through each cycle and populate full cycle data
529
- for packet_index in range(0, total_packets, swe_constants.N_QUARTER_CYCLES):
530
- # get ESA lookup table information
531
- esa_table_num = l1a_data["esa_table_num"].data[packet_index]
532
-
533
- # If ESA lookup table number is in-flight calibration
534
- # data, then skip current cycle per SWE teams specification.
535
- # SWE team only wants in-flight calibration data to be processed
536
- # upto l1a. In-flight calibration data looks same as science data
537
- # but it only measures one energy steps during the whole duration.
538
- if esa_table_num == 1:
539
- continue
540
-
541
- full_cycle_ds = populate_full_cycle_data(
542
- full_cycle_l1a_data, packet_index, esa_table_num
543
- )
544
-
545
- # save full data array to file
546
- full_cycle_science_data.append(full_cycle_ds["full_cycle_data"].data)
547
- full_cycle_acq_times.append(full_cycle_ds["acquisition_time"].data)
548
- full_cycle_acq_duration.append(full_cycle_ds["acq_duration"].data)
549
-
550
- # ------------------------------------------------------------------
551
- # Save data to dataset.
552
- # ------------------------------------------------------------------
553
- # Load CDF attrs
554
- cdf_attrs = ImapCdfAttributes()
555
- cdf_attrs.add_instrument_global_attrs("swe")
556
- cdf_attrs.add_instrument_variable_attrs("swe", "l1b")
557
- cdf_attrs.add_global_attribute("Data_version", data_version)
558
-
559
- # One full cycle data combines four quarter cycles data.
560
- # Epoch will store center of each science meansurement using
561
- # third acquisition start time coarse and fine value
562
- # of four quarter cycle data packets. For example, we want to
563
- # get indices of 3rd quarter cycle data packet in each full cycle
564
- # and use that to calculate center time of data acquisition time.
565
- # Quarter cycle indices: 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, ...
566
- indices_of_center_time = np.arange(2, total_packets, swe_constants.N_QUARTER_CYCLES)
567
-
568
- center_time = combine_acquisition_time(
569
- full_cycle_l1a_data["acq_start_coarse"].data[indices_of_center_time],
570
- full_cycle_l1a_data["acq_start_fine"].data[indices_of_center_time],
571
- )
572
-
573
- epoch_time = xr.DataArray(
574
- met_to_ttj2000ns(center_time),
575
- name="epoch",
576
- dims=["epoch"],
577
- attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False),
578
- )
579
-
580
- esa_step = xr.DataArray(
581
- np.arange(swe_constants.N_ESA_STEPS),
582
- name="esa_step",
583
- dims=["esa_step"],
584
- attrs=cdf_attrs.get_variable_attributes("esa_step", check_schema=False),
585
- )
586
-
587
- # NOTE: LABL_PTR_1 should be CDF_CHAR.
588
- esa_step_label = xr.DataArray(
589
- esa_step.values.astype(str),
590
- name="esa_step_label",
591
- dims=["esa_step"],
592
- attrs=cdf_attrs.get_variable_attributes("esa_step_label", check_schema=False),
593
- )
594
-
595
- spin_sector = xr.DataArray(
596
- np.arange(swe_constants.N_ANGLE_SECTORS),
597
- name="spin_sector",
598
- dims=["spin_sector"],
599
- attrs=cdf_attrs.get_variable_attributes("spin_sector", check_schema=False),
600
- )
601
-
602
- # NOTE: LABL_PTR_2 should be CDF_CHAR.
603
- spin_sector_label = xr.DataArray(
604
- spin_sector.values.astype(str),
605
- name="spin_sector_label",
606
- dims=["spin_sector"],
607
- attrs=cdf_attrs.get_variable_attributes(
608
- "spin_sector_label", check_schema=False
609
- ),
610
- )
611
-
612
- cycle = xr.DataArray(
613
- np.arange(swe_constants.N_QUARTER_CYCLES),
614
- name="cycle",
615
- dims=["cycle"],
616
- attrs=cdf_attrs.get_variable_attributes("cycle", check_schema=False),
617
- )
618
-
619
- cycle_label = xr.DataArray(
620
- cycle.values.astype(str),
621
- name="cycle_label",
622
- dims=["cycle"],
623
- attrs=cdf_attrs.get_variable_attributes("cycle_label", check_schema=False),
624
- )
625
-
626
- cem_id = xr.DataArray(
627
- np.arange(swe_constants.N_CEMS, dtype=np.int8),
628
- name="cem_id",
629
- dims=["cem_id"],
630
- attrs=cdf_attrs.get_variable_attributes("cem_id", check_schema=False),
631
- )
632
-
633
- # NOTE: LABL_PTR_3 should be CDF_CHAR.
634
- cem_id_label = xr.DataArray(
635
- cem_id.values.astype(str),
636
- name="cem_id_label",
637
- dims=["cem_id"],
638
- attrs=cdf_attrs.get_variable_attributes("cem_id_label", check_schema=False),
639
- )
640
-
641
- # Add science data and it's associated metadata into dataset.
642
- # SCIENCE_DATA has array of this shape:
643
- # (n, 24, 30, 7)
644
- # n = total number of full cycles
645
- # 24 rows --> 24 esa voltage measurements
646
- # 30 columns --> 30 spin angle measurements
647
- # 7 elements --> 7 CEMs counts
648
- #
649
- # The metadata array will need to have this shape:
650
- # (n, 4)
651
- # n = total number of full cycles
652
- # 4 rows --> metadata for each full cycle. Each element of 4 maps to
653
- # metadata of one quarter cycle.
654
-
655
- # Create the dataset
656
- dataset = xr.Dataset(
657
- coords={
658
- "epoch": epoch_time,
659
- "esa_step": esa_step,
660
- "spin_sector": spin_sector,
661
- "cem_id": cem_id,
662
- "cycle": cycle,
663
- "esa_step_label": esa_step_label,
664
- "spin_sector_label": spin_sector_label,
665
- "cem_id_label": cem_id_label,
666
- "cycle_label": cycle_label,
667
- },
668
- attrs=cdf_attrs.get_global_attributes("imap_swe_l1b_sci"),
669
- )
670
-
671
- dataset["science_data"] = xr.DataArray(
672
- full_cycle_science_data,
673
- dims=["epoch", "esa_step", "spin_sector", "cem_id"],
674
- attrs=cdf_attrs.get_variable_attributes("science_data"),
675
- )
676
- dataset["acquisition_time"] = xr.DataArray(
677
- full_cycle_acq_times,
678
- dims=["epoch", "esa_step", "spin_sector"],
679
- attrs=cdf_attrs.get_variable_attributes("acquisition_time"),
680
- )
681
- dataset["acq_duration"] = xr.DataArray(
682
- full_cycle_acq_duration,
683
- dims=["epoch", "esa_step", "spin_sector"],
684
- attrs=cdf_attrs.get_variable_attributes("acq_duration"),
685
- )
686
-
687
- # create xarray dataset for each metadata field
688
- for key, value in full_cycle_l1a_data.items():
689
- if key in ["science_data", "acq_duration"]:
690
- continue
691
- metadata_field = key.lower()
692
- dataset[metadata_field] = xr.DataArray(
693
- value.data.reshape(-1, swe_constants.N_QUARTER_CYCLES),
694
- dims=["epoch", "cycle"],
695
- attrs=cdf_attrs.get_variable_attributes(metadata_field),
696
- )
697
-
698
- logger.info("SWE L1b science processing completed")
699
- return dataset