imap-processing 0.11.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 (415) hide show
  1. imap_processing/__init__.py +11 -11
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/ccsds_data.py +1 -2
  4. imap_processing/ccsds/excel_to_xtce.py +66 -18
  5. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +24 -40
  6. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +934 -42
  7. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
  8. imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +0 -5
  9. imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +10 -11
  10. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +17 -19
  11. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +27 -14
  12. imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +106 -116
  13. imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +120 -145
  14. imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +14 -0
  15. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +25 -9
  16. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +6 -4
  17. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +3 -3
  18. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +0 -12
  19. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +1 -1
  20. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +23 -20
  21. imap_processing/cdf/config/imap_mag_l1a_variable_attrs.yaml +361 -0
  22. imap_processing/cdf/config/imap_mag_l1b_variable_attrs.yaml +160 -0
  23. imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +160 -0
  24. imap_processing/cdf/config/imap_spacecraft_global_cdf_attrs.yaml +18 -0
  25. imap_processing/cdf/config/imap_spacecraft_variable_attrs.yaml +40 -0
  26. imap_processing/cdf/config/imap_swapi_global_cdf_attrs.yaml +1 -5
  27. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +22 -0
  28. imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +12 -4
  29. imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +16 -2
  30. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +64 -52
  31. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +71 -47
  32. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +180 -19
  33. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
  34. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +80 -17
  35. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +32 -57
  36. imap_processing/cdf/utils.py +52 -38
  37. imap_processing/cli.py +477 -233
  38. imap_processing/codice/codice_l1a.py +466 -131
  39. imap_processing/codice/codice_l1b.py +51 -152
  40. imap_processing/codice/constants.py +1360 -569
  41. imap_processing/codice/decompress.py +2 -6
  42. imap_processing/ena_maps/ena_maps.py +1103 -146
  43. imap_processing/ena_maps/utils/coordinates.py +19 -0
  44. imap_processing/ena_maps/utils/map_utils.py +14 -17
  45. imap_processing/ena_maps/utils/spatial_utils.py +55 -52
  46. imap_processing/glows/l1a/glows_l1a.py +28 -99
  47. imap_processing/glows/l1a/glows_l1a_data.py +2 -2
  48. imap_processing/glows/l1b/glows_l1b.py +1 -4
  49. imap_processing/glows/l1b/glows_l1b_data.py +1 -3
  50. imap_processing/glows/l2/glows_l2.py +2 -5
  51. imap_processing/hi/l1a/hi_l1a.py +54 -29
  52. imap_processing/hi/l1a/histogram.py +0 -1
  53. imap_processing/hi/l1a/science_direct_event.py +6 -8
  54. imap_processing/hi/l1b/hi_l1b.py +111 -82
  55. imap_processing/hi/l1c/hi_l1c.py +416 -32
  56. imap_processing/hi/utils.py +58 -12
  57. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
  58. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt0-factors_20250219_v002.csv +205 -0
  59. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt1-factors_20250219_v002.csv +205 -0
  60. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt2-factors_20250219_v002.csv +205 -0
  61. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt3-factors_20250219_v002.csv +205 -0
  62. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-summed-dt0-factors_20250219_v002.csv +68 -0
  63. imap_processing/hit/hit_utils.py +235 -5
  64. imap_processing/hit/l0/constants.py +20 -11
  65. imap_processing/hit/l0/decom_hit.py +21 -5
  66. imap_processing/hit/l1a/hit_l1a.py +71 -75
  67. imap_processing/hit/l1b/constants.py +321 -0
  68. imap_processing/hit/l1b/hit_l1b.py +377 -67
  69. imap_processing/hit/l2/constants.py +318 -0
  70. imap_processing/hit/l2/hit_l2.py +723 -0
  71. imap_processing/hit/packet_definitions/hit_packet_definitions.xml +1323 -71
  72. imap_processing/ialirt/l0/mag_l0_ialirt_data.py +155 -0
  73. imap_processing/ialirt/l0/parse_mag.py +374 -0
  74. imap_processing/ialirt/l0/process_swapi.py +69 -0
  75. imap_processing/ialirt/l0/process_swe.py +548 -0
  76. imap_processing/ialirt/packet_definitions/ialirt.xml +216 -208
  77. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +1 -1
  78. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +1 -1
  79. imap_processing/ialirt/packet_definitions/ialirt_mag.xml +115 -0
  80. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +14 -14
  81. imap_processing/ialirt/utils/grouping.py +114 -0
  82. imap_processing/ialirt/utils/time.py +29 -0
  83. imap_processing/idex/atomic_masses.csv +22 -0
  84. imap_processing/idex/decode.py +2 -2
  85. imap_processing/idex/idex_constants.py +33 -0
  86. imap_processing/idex/idex_l0.py +22 -8
  87. imap_processing/idex/idex_l1a.py +81 -51
  88. imap_processing/idex/idex_l1b.py +13 -39
  89. imap_processing/idex/idex_l2a.py +823 -0
  90. imap_processing/idex/idex_l2b.py +120 -0
  91. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +11 -11
  92. imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
  93. imap_processing/lo/l0/lo_science.py +7 -2
  94. imap_processing/lo/l1a/lo_l1a.py +1 -5
  95. imap_processing/lo/l1b/lo_l1b.py +702 -29
  96. imap_processing/lo/l1b/tof_conversions.py +11 -0
  97. imap_processing/lo/l1c/lo_l1c.py +1 -4
  98. imap_processing/mag/constants.py +51 -0
  99. imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
  100. imap_processing/mag/l0/decom_mag.py +10 -3
  101. imap_processing/mag/l1a/mag_l1a.py +23 -19
  102. imap_processing/mag/l1a/mag_l1a_data.py +35 -10
  103. imap_processing/mag/l1b/mag_l1b.py +259 -50
  104. imap_processing/mag/l1c/interpolation_methods.py +388 -0
  105. imap_processing/mag/l1c/mag_l1c.py +621 -17
  106. imap_processing/mag/l2/mag_l2.py +140 -0
  107. imap_processing/mag/l2/mag_l2_data.py +288 -0
  108. imap_processing/quality_flags.py +1 -0
  109. imap_processing/spacecraft/packet_definitions/scid_x252.xml +538 -0
  110. imap_processing/spacecraft/quaternions.py +121 -0
  111. imap_processing/spice/geometry.py +19 -22
  112. imap_processing/spice/kernels.py +0 -276
  113. imap_processing/spice/pointing_frame.py +257 -0
  114. imap_processing/spice/repoint.py +149 -0
  115. imap_processing/spice/spin.py +38 -33
  116. imap_processing/spice/time.py +24 -0
  117. imap_processing/swapi/l1/swapi_l1.py +20 -12
  118. imap_processing/swapi/l2/swapi_l2.py +116 -5
  119. imap_processing/swapi/swapi_utils.py +32 -0
  120. imap_processing/swe/l1a/swe_l1a.py +44 -12
  121. imap_processing/swe/l1a/swe_science.py +13 -13
  122. imap_processing/swe/l1b/swe_l1b.py +898 -23
  123. imap_processing/swe/l2/swe_l2.py +75 -136
  124. imap_processing/swe/packet_definitions/swe_packet_definition.xml +1121 -1
  125. imap_processing/swe/utils/swe_constants.py +64 -0
  126. imap_processing/swe/utils/swe_utils.py +85 -28
  127. imap_processing/tests/ccsds/test_data/expected_output.xml +40 -1
  128. imap_processing/tests/ccsds/test_excel_to_xtce.py +24 -21
  129. imap_processing/tests/cdf/test_data/imap_instrument2_global_cdf_attrs.yaml +0 -2
  130. imap_processing/tests/cdf/test_utils.py +14 -16
  131. imap_processing/tests/codice/conftest.py +44 -33
  132. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  133. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  134. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-ialirt_20241110193700_v0.0.0.cdf +0 -0
  135. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-omni_20241110193700_v0.0.0.cdf +0 -0
  136. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
  137. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-priorities_20241110193700_v0.0.0.cdf +0 -0
  138. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-sectored_20241110193700_v0.0.0.cdf +0 -0
  139. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  140. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  141. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  142. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-angular_20241110193700_v0.0.0.cdf +0 -0
  143. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-priority_20241110193700_v0.0.0.cdf +0 -0
  144. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-species_20241110193700_v0.0.0.cdf +0 -0
  145. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
  146. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-angular_20241110193700_v0.0.0.cdf +0 -0
  147. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-priority_20241110193700_v0.0.0.cdf +0 -0
  148. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-species_20241110193700_v0.0.0.cdf +0 -0
  149. imap_processing/tests/codice/test_codice_l1a.py +126 -53
  150. imap_processing/tests/codice/test_codice_l1b.py +6 -7
  151. imap_processing/tests/codice/test_decompress.py +4 -4
  152. imap_processing/tests/conftest.py +239 -27
  153. imap_processing/tests/ena_maps/conftest.py +51 -0
  154. imap_processing/tests/ena_maps/test_ena_maps.py +1068 -110
  155. imap_processing/tests/ena_maps/test_map_utils.py +66 -43
  156. imap_processing/tests/ena_maps/test_spatial_utils.py +17 -21
  157. imap_processing/tests/glows/conftest.py +10 -14
  158. imap_processing/tests/glows/test_glows_decom.py +4 -4
  159. imap_processing/tests/glows/test_glows_l1a_cdf.py +6 -27
  160. imap_processing/tests/glows/test_glows_l1a_data.py +6 -8
  161. imap_processing/tests/glows/test_glows_l1b.py +11 -11
  162. imap_processing/tests/glows/test_glows_l1b_data.py +5 -5
  163. imap_processing/tests/glows/test_glows_l2.py +2 -8
  164. imap_processing/tests/hi/conftest.py +1 -1
  165. imap_processing/tests/hi/data/l0/H45_diag_fee_20250208.bin +0 -0
  166. imap_processing/tests/hi/data/l0/H45_diag_fee_20250208_verify.csv +205 -0
  167. imap_processing/tests/hi/test_hi_l1b.py +22 -27
  168. imap_processing/tests/hi/test_hi_l1c.py +249 -18
  169. imap_processing/tests/hi/test_l1a.py +35 -7
  170. imap_processing/tests/hi/test_science_direct_event.py +3 -3
  171. imap_processing/tests/hi/test_utils.py +24 -2
  172. imap_processing/tests/hit/helpers/l1_validation.py +74 -73
  173. imap_processing/tests/hit/test_data/hskp_sample.ccsds +0 -0
  174. imap_processing/tests/hit/test_data/imap_hit_l0_raw_20100105_v001.pkts +0 -0
  175. imap_processing/tests/hit/test_decom_hit.py +5 -1
  176. imap_processing/tests/hit/test_hit_l1a.py +32 -36
  177. imap_processing/tests/hit/test_hit_l1b.py +300 -81
  178. imap_processing/tests/hit/test_hit_l2.py +716 -0
  179. imap_processing/tests/hit/test_hit_utils.py +184 -7
  180. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
  181. imap_processing/tests/hit/validation_data/hskp_sample_eu_3_6_2025.csv +89 -0
  182. imap_processing/tests/hit/validation_data/hskp_sample_raw.csv +89 -88
  183. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +1 -1
  184. imap_processing/tests/ialirt/data/l0/461971383-404.bin +0 -0
  185. imap_processing/tests/ialirt/data/l0/461971384-405.bin +0 -0
  186. imap_processing/tests/ialirt/data/l0/461971385-406.bin +0 -0
  187. imap_processing/tests/ialirt/data/l0/461971386-407.bin +0 -0
  188. imap_processing/tests/ialirt/data/l0/461971387-408.bin +0 -0
  189. imap_processing/tests/ialirt/data/l0/461971388-409.bin +0 -0
  190. imap_processing/tests/ialirt/data/l0/461971389-410.bin +0 -0
  191. imap_processing/tests/ialirt/data/l0/461971390-411.bin +0 -0
  192. imap_processing/tests/ialirt/data/l0/461971391-412.bin +0 -0
  193. imap_processing/tests/ialirt/data/l0/sample_decoded_i-alirt_data.csv +383 -0
  194. imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
  195. imap_processing/tests/ialirt/unit/test_grouping.py +81 -0
  196. imap_processing/tests/ialirt/unit/test_parse_mag.py +223 -0
  197. imap_processing/tests/ialirt/unit/test_process_codicehi.py +3 -3
  198. imap_processing/tests/ialirt/unit/test_process_codicelo.py +3 -10
  199. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +4 -4
  200. imap_processing/tests/ialirt/unit/test_process_hit.py +3 -3
  201. imap_processing/tests/ialirt/unit/test_process_swapi.py +24 -16
  202. imap_processing/tests/ialirt/unit/test_process_swe.py +319 -6
  203. imap_processing/tests/ialirt/unit/test_time.py +16 -0
  204. imap_processing/tests/idex/conftest.py +127 -6
  205. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231218_v001.pkts +0 -0
  206. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20241206_v001.pkts +0 -0
  207. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20250108_v001.pkts +0 -0
  208. imap_processing/tests/idex/test_data/impact_14_tof_high_data.txt +4508 -4508
  209. imap_processing/tests/idex/test_idex_l0.py +33 -11
  210. imap_processing/tests/idex/test_idex_l1a.py +92 -21
  211. imap_processing/tests/idex/test_idex_l1b.py +106 -27
  212. imap_processing/tests/idex/test_idex_l2a.py +399 -0
  213. imap_processing/tests/idex/test_idex_l2b.py +93 -0
  214. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20241022_v002.cdf +0 -0
  215. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20241022_v002.cdf +0 -0
  216. imap_processing/tests/lo/test_lo_l1a.py +3 -3
  217. imap_processing/tests/lo/test_lo_l1b.py +515 -6
  218. imap_processing/tests/lo/test_lo_l1c.py +1 -1
  219. imap_processing/tests/lo/test_lo_science.py +7 -7
  220. imap_processing/tests/lo/test_star_sensor.py +1 -1
  221. imap_processing/tests/mag/conftest.py +120 -2
  222. imap_processing/tests/mag/test_mag_decom.py +5 -4
  223. imap_processing/tests/mag/test_mag_l1a.py +51 -7
  224. imap_processing/tests/mag/test_mag_l1b.py +40 -59
  225. imap_processing/tests/mag/test_mag_l1c.py +354 -19
  226. imap_processing/tests/mag/test_mag_l2.py +130 -0
  227. imap_processing/tests/mag/test_mag_validation.py +247 -26
  228. imap_processing/tests/mag/validation/L1b/T009/MAGScience-normal-(2,2)-8s-20250204-16h39.csv +17 -0
  229. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +16 -16
  230. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +16 -16
  231. imap_processing/tests/mag/validation/L1b/T010/MAGScience-normal-(2,2)-8s-20250206-12h05.csv +17 -0
  232. imap_processing/tests/mag/validation/L1b/T011/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
  233. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +16 -16
  234. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +16 -16
  235. imap_processing/tests/mag/validation/L1b/T012/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
  236. imap_processing/tests/mag/validation/L1b/T012/data.bin +0 -0
  237. imap_processing/tests/mag/validation/L1b/T012/field_like_all_ranges.txt +19200 -0
  238. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-cal.cdf +0 -0
  239. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-in.csv +17 -0
  240. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-magi-out.csv +17 -0
  241. imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-mago-out.csv +17 -0
  242. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-in.csv +1217 -0
  243. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-out.csv +1857 -0
  244. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-in.csv +1217 -0
  245. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-out.csv +1857 -0
  246. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-in.csv +1217 -0
  247. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-out.csv +1793 -0
  248. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-in.csv +1217 -0
  249. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-out.csv +1793 -0
  250. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-burst-in.csv +2561 -0
  251. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-in.csv +961 -0
  252. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-out.csv +1539 -0
  253. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-in.csv +1921 -0
  254. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-out.csv +2499 -0
  255. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-in.csv +865 -0
  256. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-out.csv +1196 -0
  257. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-in.csv +1729 -0
  258. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-out.csv +3053 -0
  259. imap_processing/tests/mag/validation/L2/imap_mag_l1b_norm-mago_20251017_v002.cdf +0 -0
  260. imap_processing/tests/mag/validation/calibration/imap_mag_l1b-calibration_20240229_v001.cdf +0 -0
  261. imap_processing/tests/mag/validation/calibration/imap_mag_l2-calibration-matrices_20251017_v004.cdf +0 -0
  262. imap_processing/tests/mag/validation/calibration/imap_mag_l2-offsets-norm_20251017_20251017_v001.cdf +0 -0
  263. imap_processing/tests/spacecraft/data/SSR_2024_190_20_08_12_0483851794_2_DA_apid0594_1packet.pkts +0 -0
  264. imap_processing/tests/spacecraft/test_quaternions.py +71 -0
  265. imap_processing/tests/spice/test_data/fake_repoint_data.csv +5 -0
  266. imap_processing/tests/spice/test_data/fake_spin_data.csv +11 -11
  267. imap_processing/tests/spice/test_geometry.py +9 -12
  268. imap_processing/tests/spice/test_kernels.py +1 -200
  269. imap_processing/tests/spice/test_pointing_frame.py +185 -0
  270. imap_processing/tests/spice/test_repoint.py +121 -0
  271. imap_processing/tests/spice/test_spin.py +50 -9
  272. imap_processing/tests/spice/test_time.py +14 -0
  273. imap_processing/tests/swapi/lut/imap_swapi_esa-unit-conversion_20250211_v000.csv +73 -0
  274. imap_processing/tests/swapi/lut/imap_swapi_lut-notes_20250211_v000.csv +1025 -0
  275. imap_processing/tests/swapi/test_swapi_l1.py +13 -11
  276. imap_processing/tests/swapi/test_swapi_l2.py +180 -8
  277. imap_processing/tests/swe/l0_data/2024051010_SWE_HK_packet.bin +0 -0
  278. imap_processing/tests/swe/l0_data/2024051011_SWE_CEM_RAW_packet.bin +0 -0
  279. imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_APP_HK_20240510_092742.csv +49 -0
  280. imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_CEM_RAW_20240510_092742.csv +593 -0
  281. imap_processing/tests/swe/lut/checker-board-indices.csv +24 -0
  282. imap_processing/tests/swe/lut/imap_swe_esa-lut_20250301_v000.csv +385 -0
  283. imap_processing/tests/swe/lut/imap_swe_l1b-in-flight-cal_20240510_20260716_v000.csv +3 -0
  284. imap_processing/tests/swe/test_swe_l1a.py +20 -2
  285. imap_processing/tests/swe/test_swe_l1a_cem_raw.py +52 -0
  286. imap_processing/tests/swe/test_swe_l1a_hk.py +68 -0
  287. imap_processing/tests/swe/test_swe_l1a_science.py +3 -3
  288. imap_processing/tests/swe/test_swe_l1b.py +162 -24
  289. imap_processing/tests/swe/test_swe_l2.py +153 -91
  290. imap_processing/tests/test_cli.py +171 -88
  291. imap_processing/tests/test_utils.py +140 -17
  292. imap_processing/tests/ultra/data/l0/FM45_UltraFM45_Functional_2024-01-22T0105_20240122T010548.CCSDS +0 -0
  293. imap_processing/tests/ultra/data/l0/ultra45_raw_sc_ultraimgrates_20220530_00.csv +164 -0
  294. imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3243 -3243
  295. imap_processing/tests/ultra/data/mock_data.py +369 -0
  296. imap_processing/tests/ultra/unit/conftest.py +115 -89
  297. imap_processing/tests/ultra/unit/test_badtimes.py +4 -4
  298. imap_processing/tests/ultra/unit/test_cullingmask.py +8 -6
  299. imap_processing/tests/ultra/unit/test_de.py +14 -13
  300. imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
  301. imap_processing/tests/ultra/unit/test_decom_apid_881.py +54 -11
  302. imap_processing/tests/ultra/unit/test_decom_apid_883.py +12 -10
  303. imap_processing/tests/ultra/unit/test_decom_apid_896.py +202 -55
  304. imap_processing/tests/ultra/unit/test_lookup_utils.py +23 -1
  305. imap_processing/tests/ultra/unit/test_spacecraft_pset.py +77 -0
  306. imap_processing/tests/ultra/unit/test_ultra_l1a.py +98 -305
  307. imap_processing/tests/ultra/unit/test_ultra_l1b.py +60 -14
  308. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
  309. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +26 -27
  310. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +239 -70
  311. imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
  312. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +114 -83
  313. imap_processing/tests/ultra/unit/test_ultra_l2.py +230 -0
  314. imap_processing/ultra/constants.py +1 -1
  315. imap_processing/ultra/l0/decom_tools.py +27 -39
  316. imap_processing/ultra/l0/decom_ultra.py +168 -204
  317. imap_processing/ultra/l0/ultra_utils.py +152 -136
  318. imap_processing/ultra/l1a/ultra_l1a.py +55 -271
  319. imap_processing/ultra/l1b/badtimes.py +1 -4
  320. imap_processing/ultra/l1b/cullingmask.py +2 -6
  321. imap_processing/ultra/l1b/de.py +116 -57
  322. imap_processing/ultra/l1b/extendedspin.py +20 -18
  323. imap_processing/ultra/l1b/lookup_utils.py +72 -9
  324. imap_processing/ultra/l1b/ultra_l1b.py +36 -16
  325. imap_processing/ultra/l1b/ultra_l1b_culling.py +66 -30
  326. imap_processing/ultra/l1b/ultra_l1b_extended.py +297 -94
  327. imap_processing/ultra/l1c/histogram.py +2 -6
  328. imap_processing/ultra/l1c/spacecraft_pset.py +84 -0
  329. imap_processing/ultra/l1c/ultra_l1c.py +8 -9
  330. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +206 -108
  331. imap_processing/ultra/l2/ultra_l2.py +299 -0
  332. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +526 -0
  333. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +526 -0
  334. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +526 -0
  335. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +526 -0
  336. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -2
  337. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -0
  338. imap_processing/ultra/packet_definitions/README.md +38 -0
  339. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +15302 -482
  340. imap_processing/ultra/utils/ultra_l1_utils.py +31 -12
  341. imap_processing/utils.py +69 -29
  342. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +10 -6
  343. imap_processing-0.13.0.dist-info/RECORD +578 -0
  344. imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +0 -237
  345. imap_processing/hi/l1a/housekeeping.py +0 -27
  346. imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
  347. imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
  348. imap_processing/swe/l1b/swe_l1b_science.py +0 -652
  349. imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-aggregated_20240429_v001.cdf +0 -0
  350. imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-singles_20240429_v001.cdf +0 -0
  351. imap_processing/tests/codice/data/imap_codice_l1a_hi-omni_20240429_v001.cdf +0 -0
  352. imap_processing/tests/codice/data/imap_codice_l1a_hi-sectored_20240429_v001.cdf +0 -0
  353. imap_processing/tests/codice/data/imap_codice_l1a_hskp_20100101_v001.cdf +0 -0
  354. imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-aggregated_20240429_v001.cdf +0 -0
  355. imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-singles_20240429_v001.cdf +0 -0
  356. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-angular_20240429_v001.cdf +0 -0
  357. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-priority_20240429_v001.cdf +0 -0
  358. imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-species_20240429_v001.cdf +0 -0
  359. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-angular_20240429_v001.cdf +0 -0
  360. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-priority_20240429_v001.cdf +0 -0
  361. imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-species_20240429_v001.cdf +0 -0
  362. imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-aggregated_20240429_v001.cdf +0 -0
  363. imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-singles_20240429_v001.cdf +0 -0
  364. imap_processing/tests/codice/data/imap_codice_l1b_hi-omni_20240429_v001.cdf +0 -0
  365. imap_processing/tests/codice/data/imap_codice_l1b_hi-sectored_20240429_v001.cdf +0 -0
  366. imap_processing/tests/codice/data/imap_codice_l1b_hskp_20100101_v001.cdf +0 -0
  367. imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-aggregated_20240429_v001.cdf +0 -0
  368. imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-singles_20240429_v001.cdf +0 -0
  369. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-angular_20240429_v001.cdf +0 -0
  370. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-priority_20240429_v001.cdf +0 -0
  371. imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-species_20240429_v001.cdf +0 -0
  372. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-angular_20240429_v001.cdf +0 -0
  373. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-priority_20240429_v001.cdf +0 -0
  374. imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-species_20240429_v001.cdf +0 -0
  375. imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
  376. imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1251.pkts +0 -0
  377. imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1252.pkts +0 -0
  378. imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +0 -89
  379. imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +0 -29
  380. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231214_v001.pkts +0 -0
  381. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20100101_v001.cdf +0 -0
  382. imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20100101_v001.cdf +0 -0
  383. imap_processing/tests/swe/test_swe_l1b_science.py +0 -84
  384. imap_processing/tests/ultra/test_data/mock_data.py +0 -161
  385. imap_processing/ultra/l1c/pset.py +0 -40
  386. imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
  387. imap_processing-0.11.0.dist-info/RECORD +0 -488
  388. /imap_processing/idex/packet_definitions/{idex_packet_definition.xml → idex_science_packet_definition.xml} +0 -0
  389. /imap_processing/tests/ialirt/{test_data → data}/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  390. /imap_processing/tests/ialirt/{test_data → data}/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  391. /imap_processing/tests/ialirt/{test_data → data}/l0/IALiRT Raw Packet Telemetry.txt +0 -0
  392. /imap_processing/tests/ialirt/{test_data → data}/l0/apid01152.tlm +0 -0
  393. /imap_processing/tests/ialirt/{test_data → data}/l0/eu_SWP_IAL_20240826_152033.csv +0 -0
  394. /imap_processing/tests/ialirt/{test_data → data}/l0/hi_fsw_view_1_ccsds.bin +0 -0
  395. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.ccsds +0 -0
  396. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.csv +0 -0
  397. /imap_processing/tests/ialirt/{test_data → data}/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +0 -0
  398. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  399. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  400. /imap_processing/{mag/l1b → tests/spacecraft}/__init__.py +0 -0
  401. /imap_processing/{swe/l1b/engineering_unit_convert_table.csv → tests/swe/lut/imap_swe_eu-conversion_20240510_v000.csv} +0 -0
  402. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_40P_Phi28p5_BeamCal_LinearScan_phi28.50_theta-0.00_20240207T102740.CCSDS +0 -0
  403. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_7P_Phi0.0_BeamCal_LinearScan_phi0.04_theta-0.01_20230821T121304.CCSDS +0 -0
  404. /imap_processing/tests/ultra/{test_data → data}/l0/FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.CCSDS +0 -0
  405. /imap_processing/tests/ultra/{test_data → data}/l0/Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.CCSDS +0 -0
  406. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_auxdata_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
  407. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_enaphxtofhangimg_FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.csv +0 -0
  408. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultraimgrates_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
  409. /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimgevent_FM45_7P_Phi00_BeamCal_LinearScan_phi004_theta-001_20230821T121304.csv +0 -0
  410. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E1.cdf +0 -0
  411. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E12.cdf +0 -0
  412. /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E24.cdf +0 -0
  413. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/LICENSE +0 -0
  414. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
  415. {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
@@ -1,50 +1,925 @@
1
- """Contains code to perform SWE L1b processing."""
1
+ """Contains code to perform SWE L1b science processing."""
2
2
 
3
3
  import logging
4
+ from pathlib import Path
5
+ from typing import Union
4
6
 
7
+ import numpy as np
8
+ import numpy.typing as npt
9
+ import pandas as pd
5
10
  import xarray as xr
11
+ from imap_data_access.processing_input import ProcessingInputCollection
6
12
 
7
- from imap_processing import imap_module_directory
8
- from imap_processing.swe.l1b.swe_l1b_science import swe_l1b_science
9
- from imap_processing.swe.utils.swe_utils import SWEAPID
13
+ from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
14
+ from imap_processing.cdf.utils import load_cdf
15
+ from imap_processing.spice.time import met_to_ttj2000ns
16
+ from imap_processing.swe.utils import swe_constants
17
+ from imap_processing.swe.utils.swe_utils import (
18
+ SWEAPID,
19
+ calculate_data_acquisition_time,
20
+ combine_acquisition_time,
21
+ read_lookup_table,
22
+ )
10
23
  from imap_processing.utils import convert_raw_to_eu
11
24
 
12
25
  logger = logging.getLogger(__name__)
13
26
 
14
27
 
15
- def swe_l1b(l1a_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
28
+ def get_esa_dataframe(esa_table_number: int) -> pd.DataFrame:
16
29
  """
17
- Will process data to L1B.
30
+ Read lookup table from file.
18
31
 
19
32
  Parameters
20
33
  ----------
21
- l1a_dataset : xarray.Dataset
22
- The l1a data input.
23
- data_version : str
24
- Version of the data product being created.
34
+ esa_table_number : int
35
+ ESA table index number.
25
36
 
26
37
  Returns
27
38
  -------
28
- data : xarray.Dataset
29
- Processed data to L1B.
39
+ esa_steps : pandas.DataFrame
40
+ ESA table_number and its associated values.
30
41
  """
31
- apid = int(l1a_dataset.attrs["packet_apid"])
42
+ if esa_table_number not in [0, 1]:
43
+ raise ValueError(f"Unknown ESA table number {esa_table_number}")
32
44
 
33
- # convert value from raw to engineering units as needed
34
- conversion_table_path = str(
35
- imap_module_directory / "swe/l1b/engineering_unit_convert_table.csv"
45
+ # Get the lookup table DataFrame
46
+ lookup_table = read_lookup_table()
47
+
48
+ esa_steps = lookup_table.loc[lookup_table["table_index"] == esa_table_number]
49
+ return esa_steps
50
+
51
+
52
+ def deadtime_correction(
53
+ counts: np.ndarray, acq_duration: Union[int, npt.NDArray]
54
+ ) -> npt.NDArray:
55
+ """
56
+ Calculate deadtime correction.
57
+
58
+ Deadtime correction is a technique used in various fields, including
59
+ nuclear physics, radiation detection, and particle counting, to compensate
60
+ for the effects of the time period during which a detector is not able to
61
+ record new events or measurements after detecting a previous event.
62
+ This "deadtime" is essentially the time during which the detector is
63
+ recovering from the previous detection and is unable to detect new events.
64
+
65
+ In particle detectors, there is a finite time required for the detector to
66
+ reset or recover after detecting a particle. During this deadtime, any
67
+ subsequent particles that may have arrived go undetected. As a result,
68
+ the recorded count rate appears to be lower than the actual count rate.
69
+
70
+ Deadtime correction involves mathematically adjusting the measured count
71
+ rates to compensate for this deadtime effect. This correction is crucial
72
+ when dealing with high-intensity sources or particle fluxes, as the deadtime
73
+ can significantly affect the accuracy of the measurements.
74
+
75
+ Deadtime correction is important to ensure accurate measurements and data
76
+ analysis in fields where event detection rates are high and where every
77
+ detected event is critical for understanding physical processes.
78
+
79
+ Parameters
80
+ ----------
81
+ counts : numpy.ndarray
82
+ Counts data before deadtime corrections.
83
+ acq_duration : int or numpy.ndarray
84
+ This is ACQ_DURATION from science packet. acq_duration is in microseconds.
85
+
86
+ Returns
87
+ -------
88
+ corrected_count : numpy.ndarray
89
+ Corrected counts.
90
+ """
91
+ # deadtime is 360 ns
92
+ deadtime = 360e-9
93
+ if isinstance(acq_duration, int):
94
+ # Convert acq_duration to a numpy array for consistency
95
+ acq_duration = np.array([acq_duration])
96
+ correct = 1.0 - (deadtime * (counts / (acq_duration[..., np.newaxis] * 1e-6)))
97
+ # NOTE: 0.1 is defined in SWE algorithm document. It says
98
+ # 'arbitrary x10 cutoff' in the document.
99
+ correct = np.maximum(0.1, correct)
100
+ corrected_count = counts.astype(np.float64) / correct
101
+ return corrected_count
102
+
103
+
104
+ def convert_counts_to_rate(data: np.ndarray, acq_duration: np.ndarray) -> npt.NDArray:
105
+ """
106
+ Convert counts to rate using sampling time.
107
+
108
+ acq_duration is ACQ_DURATION from science packet.
109
+
110
+ Parameters
111
+ ----------
112
+ data : numpy.ndarray
113
+ Counts data.
114
+ acq_duration : numpy.ndarray
115
+ Acquisition duration. acq_duration is in microseconds.
116
+
117
+ Returns
118
+ -------
119
+ numpy.ndarray
120
+ Count rates array in seconds.
121
+ """
122
+ # Convert microseconds to seconds without modifying the original acq_duration
123
+ acq_duration_sec = acq_duration * 1e-6
124
+
125
+ # Ensure acq_duration_sec is broadcastable to data
126
+ if acq_duration_sec.ndim < data.ndim:
127
+ acq_duration_sec = acq_duration_sec[
128
+ ..., np.newaxis
129
+ ] # Add a new axis for broadcasting
130
+
131
+ # Perform element-wise division
132
+ count_rate = data.astype(np.float64) / acq_duration_sec
133
+ return count_rate
134
+
135
+
136
+ def read_in_flight_cal_data(in_flight_cal_files: list) -> pd.DataFrame:
137
+ """
138
+ Read in-flight calibration data.
139
+
140
+ In-flight calibration data file will contain rows where each line
141
+ has 8 numbers, with the first being a time stamp in MET, and the next
142
+ 7 being the factors for the 7 detectors.
143
+
144
+ This file will be updated weekly with new calibration data. In other
145
+ words, one line of data will be added each week to the existing file.
146
+ File will be in CSV format. Processing won't be kicked off until there
147
+ is in-flight calibration data that covers science data.
148
+
149
+ Parameters
150
+ ----------
151
+ in_flight_cal_files : list
152
+ List of in-flight calibration files.
153
+
154
+ Returns
155
+ -------
156
+ in_flight_cal_df : pandas.DataFrame
157
+ DataFrame with in-flight calibration data.
158
+ """
159
+ column_names = [
160
+ "met_time",
161
+ "cem1",
162
+ "cem2",
163
+ "cem3",
164
+ "cem4",
165
+ "cem5",
166
+ "cem6",
167
+ "cem7",
168
+ ]
169
+ in_flight_cal_df = pd.concat(
170
+ [
171
+ pd.read_csv(file_path, header=0, names=column_names)
172
+ for file_path in in_flight_cal_files
173
+ ]
174
+ )
175
+ # Drop duplicates and keep only last occurrence
176
+ in_flight_cal_df = in_flight_cal_df.drop_duplicates(
177
+ subset=["met_time"], keep="last"
36
178
  )
179
+ # Sort by 'met_time' column
180
+ in_flight_cal_df = in_flight_cal_df.sort_values(by="met_time")
181
+ return in_flight_cal_df
182
+
183
+
184
+ def calculate_calibration_factor(
185
+ acquisition_times: np.ndarray, cal_times: np.ndarray, cal_data: np.ndarray
186
+ ) -> npt.NDArray:
187
+ """
188
+ Calculate calibration factor using linear interpolation.
189
+
190
+ Steps to calculate calibration factor:
191
+ 1. Convert input time to match time format in the calibration data file.
192
+ Both times should be in S/C MET time.
193
+ 2. Find the nearest in time calibration data point.
194
+ 3. Linear interpolate between those two nearest time and get factor for
195
+ input time.
196
+
197
+ Parameters
198
+ ----------
199
+ acquisition_times : numpy.ndarray
200
+ Data points to interpolate. Shape is (N_ESA_STEPS, N_ANGLE_SECTORS).
201
+ cal_times : numpy.ndarray
202
+ X-coordinates data points. Calibration times. Shape is (n,).
203
+ cal_data : numpy.ndarray
204
+ Y-coordinates data points. Calibration data of corresponding cal_times.
205
+ Shape is (n, N_CEMS).
206
+
207
+ Returns
208
+ -------
209
+ calibration_factor : numpy.ndarray
210
+ Calibration factor for each CEM detector. Shape is
211
+ (N_ESA_STEPS, N_ANGLE_SECTORS, N_CEMS) where last 7 dimension
212
+ contains calibration factor for each CEM detector.
213
+ """
214
+ # Raise error if there is no pre or post time in cal_times. SWE does not
215
+ # want to extrapolate calibration data.
216
+ if (
217
+ acquisition_times.min() < cal_times.min()
218
+ or acquisition_times.max() > cal_times.max()
219
+ ):
220
+ raise ValueError(
221
+ f"Acquisition min/max times: {acquisition_times.min()} to "
222
+ f"{acquisition_times.max()}. "
223
+ f"Calibration min/max times: {cal_times.min()} to {cal_times.max()}. "
224
+ "Acquisition times should be within calibration time range."
225
+ )
226
+
227
+ # This line of code finds the indices of acquisition_times in cal_times where
228
+ # acquisition_times should be inserted to maintain order. As a result, it finds
229
+ # its nearest pre and post time from cal_times.
230
+ input_time_indices = np.searchsorted(cal_times, acquisition_times)
231
+
232
+ # Assign to a variable for better readability
233
+ x = acquisition_times
234
+ xp = cal_times
235
+ fp = cal_data
236
+
237
+ # Given this situation which will be the case for SWE data
238
+ # where data will fall in between two calibration times and
239
+ # not be exactly equal to any calibration time,
240
+ # >>> a = [1, 2, 3]
241
+ # >>> np.searchsorted(a, [2.5])
242
+ # array([2])
243
+ # we need to use (j - 1) to get pre time indices. (j-1) is
244
+ # pre time indices and j is post time indices.
245
+ j = input_time_indices
246
+ w = (x - xp[j - 1]) / (xp[j] - xp[j - 1])
247
+ return fp[j - 1] + w[..., None] * (fp[j] - fp[j - 1])
248
+
249
+
250
+ def apply_in_flight_calibration(
251
+ corrected_counts: np.ndarray,
252
+ acquisition_time: np.ndarray,
253
+ in_flight_cal_files: list,
254
+ ) -> npt.NDArray:
255
+ """
256
+ Apply in flight calibration to full cycle data.
257
+
258
+ These factors are used to account for changes in gain with time.
259
+
260
+ They are derived from the weekly electron calibration data.
261
+
262
+ Parameters
263
+ ----------
264
+ corrected_counts : numpy.ndarray
265
+ Corrected count of full cycle data. Data shape is
266
+ (N_ESA_STEPS, N_ANGLE_SECTORS, N_CEMS).
267
+ acquisition_time : numpy.ndarray
268
+ Acquisition time of full cycle data. Data shape is
269
+ (N_ESA_STEPS, N_ANGLE_SECTORS).
270
+ in_flight_cal_files : list
271
+ List of in-flight calibration files.
272
+
273
+ Returns
274
+ -------
275
+ corrected_counts : numpy.ndarray
276
+ Corrected count of full cycle data after applying in-flight calibration.
277
+ Array shape is (N_ESA_STEPS, N_ANGLE_SECTORS, N_CEMS).
278
+ """
279
+ # Read in in-flight calibration data
280
+ in_flight_cal_df = read_in_flight_cal_data(in_flight_cal_files)
281
+ # calculate calibration factor.
282
+ # return shape of calculate_calibration_factor is
283
+ # (N_ESA_STEPS, N_ANGLE_SECTORS, N_CEMS) where
284
+ # last 7 dimension contains calibration factor for each CEM detector.
285
+ cal_factor = calculate_calibration_factor(
286
+ acquisition_time,
287
+ in_flight_cal_df["met_time"].values,
288
+ in_flight_cal_df.iloc[:, 1:].values,
289
+ )
290
+ # Apply to full cycle data
291
+ return corrected_counts.astype(np.float64) * cal_factor
292
+
293
+
294
+ def find_cycle_starts(cycles: np.ndarray) -> npt.NDArray:
295
+ """
296
+ Find index of where new cycle started.
297
+
298
+ Brandon Stone helped developed this algorithm.
299
+
300
+ Parameters
301
+ ----------
302
+ cycles : numpy.ndarray
303
+ Array that contains quarter cycle information.
304
+
305
+ Returns
306
+ -------
307
+ first_quarter_indices : numpy.ndarray
308
+ Array of indices of start cycle.
309
+ """
310
+ if cycles.size < swe_constants.N_QUARTER_CYCLES:
311
+ return np.array([], np.int64)
312
+
313
+ # calculate difference between consecutive cycles
314
+ diff = cycles[1:] - cycles[:-1]
315
+
316
+ # This uses sliding window to find index where cycle starts.
317
+ # This is what this below code line is doing:
318
+ # [1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 0 0] # Is cycle zero?
319
+ # [1 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1] # Next diff is one?
320
+ # [1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1 0] # Next diff is one?
321
+ # [0 1 1 1 0 1 0 0 1 0 1 1 1 0 1 0 0] # Next diff is one?
322
+ #
323
+ # [0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0] # And all?
324
+ ione = diff == 1
325
+ valid = (cycles == 0)[:-3] & ione[:-2] & ione[1:-1] & ione[2:]
326
+ first_quarter_indices = np.where(valid)[0]
327
+ return first_quarter_indices
328
+
329
+
330
+ def get_indices_of_full_cycles(quarter_cycle: np.ndarray) -> npt.NDArray:
331
+ """
332
+ Get indices of full cycles.
333
+
334
+ Parameters
335
+ ----------
336
+ quarter_cycle : numpy.ndarray
337
+ Array that contains quarter cycles information.
338
+
339
+ Returns
340
+ -------
341
+ full_cycles_indices : numpy.ndarray
342
+ 1D array with indices of full cycle data.
343
+ """
344
+ indices_of_start = find_cycle_starts(quarter_cycle)
345
+ # indices_of_start[..., None] creates array of shape(n, 1).
346
+ # Eg. [[3], [8]]
347
+ # np.arange(4)[None, ...] creates array of shape(1, 4)
348
+ # Eg. [[0, 1, 2, 3]]
349
+ # then we add both of them together to get an array of shape(n, 4)
350
+ # Eg. [[3, 4, 5, 6], [8, 9, 10, 11]]
351
+ full_cycles_indices = (
352
+ indices_of_start[..., None]
353
+ + np.arange(swe_constants.N_QUARTER_CYCLES)[None, ...]
354
+ )
355
+ return full_cycles_indices.reshape(-1)
356
+
357
+
358
+ def get_esa_energy_pattern(esa_lut_file: Path, esa_table_num: int = 0) -> npt.NDArray:
359
+ """
360
+ Get energy in the checkerboard pattern of a full cycle of SWE data.
361
+
362
+ This uses ESA Table index number to look up which pattern to use from
363
+ ESA LUT. This is used in L2 to process data further.
364
+
365
+ Parameters
366
+ ----------
367
+ esa_lut_file : pathlib.Path
368
+ ESA LUT file.
369
+ esa_table_num : int
370
+ ESA table number. Default is 0.
371
+
372
+ Returns
373
+ -------
374
+ energy_pattern : numpy.ndarray
375
+ (esa_step, spin_sector) array with energies of cycle data.
376
+ """
377
+ esa_lut_df = pd.read_csv(esa_lut_file)
378
+ # Get the pattern from the ESA LUT
379
+ esa_table_df = esa_lut_df[esa_lut_df["table_idx"] == esa_table_num]
380
+
381
+ # Now define variable to store pattern for the first two columns
382
+ # because that pattern is repeated in the rest of the columns.
383
+ first_two_columns = np.zeros((swe_constants.N_ESA_STEPS, 2), dtype=np.float64)
384
+ # Get row indices of all four quarter cycles. Then minus 1 to get
385
+ # the row indices in 0-23 instead of 1-24.
386
+ cycle_row_indices = esa_table_df["v_index"].values - 1
387
+ esa_v = esa_table_df["esa_v"].values
388
+ # Reshaping the 'v_index' into 4 x 12 gets 12 repeated row_indices and
389
+ # energy steps of each quarter cycle
390
+ row_indices = cycle_row_indices.reshape(4, 12)
391
+ esa_v = esa_v.reshape(4, 12)
392
+ for i in range(4):
393
+ # Split each quarter's 12 steps into 2 x 6 blocks for even
394
+ # and odd columns
395
+ even_odd_column_info = row_indices[i].reshape(2, 6)
396
+ even_row_indices = even_odd_column_info[0]
397
+ odd_row_indices = even_odd_column_info[1]
398
+
399
+ # Get even and odd column's ESA voltage information
400
+ esa_v_info = esa_v[i].reshape(2, 6)
401
+ first_two_columns[even_row_indices, 0] = esa_v_info[0]
402
+ first_two_columns[odd_row_indices, 1] = esa_v_info[1]
403
+
404
+ # Repeat the first 2 column pattern 15 times across 30 columns
405
+ # (2 columns x 15 = 30)
406
+ energy_pattern = np.tile(first_two_columns, (1, 15))
407
+
408
+ # Convert
409
+ return energy_pattern
410
+
411
+
412
+ def get_checker_board_pattern(
413
+ esa_lut_file: pd.DataFrame, esa_table_num: int = 0
414
+ ) -> npt.NDArray:
415
+ """
416
+ Generate the checkerboard pattern index map for a full cycle of SWE data.
417
+
418
+ Find indices of where full cycle data goes in the checkerboard pattern.
419
+ This is used to populate full cycle data in the full cycle data array.
420
+ This uses ESA Table index number to look up which pattern to use from
421
+ ESA LUT.
422
+
423
+ Parameters
424
+ ----------
425
+ esa_lut_file : pathlib.Path
426
+ ESA LUT file.
427
+ esa_table_num : int
428
+ ESA table number. Default is 0.
429
+
430
+ Returns
431
+ -------
432
+ checkerboard_pattern : numpy.ndarray
433
+ (esa_step * spin_sector) array with indices of where each cycle data goes in.
434
+ """
435
+ esa_lut_df = pd.read_csv(esa_lut_file)
436
+ # Get the pattern from the ESA LUT
437
+ esa_table_df = esa_lut_df[esa_lut_df["table_idx"] == esa_table_num]
438
+
439
+ # Now define variable to store pattern for the first two columns
440
+ # because that pattern is repeated in the rest of the columns.
441
+ first_two_columns = np.zeros((24, 2), dtype=np.int64)
442
+ # Get row indices of all four quarter cycles. Then minus 1 to get
443
+ # the row indices in 0-23 instead of 1-24.
444
+ cycle_row_indices = esa_table_df["v_index"].values - 1
445
+ esa_step = esa_table_df["esa_step"].values
446
+ # Reshaping the 'v_index' into 4 x 12 gets 12 repeated row_indices and
447
+ # energy steps of each quarter cycle
448
+ row_indices = cycle_row_indices.reshape(4, 12)
449
+ esa_step = esa_step.reshape(4, 12)
450
+ for i in range(4):
451
+ # Split each quarter's 12 steps into 2 x 6 blocks for even
452
+ # and odd columns
453
+ even_odd_column_info = row_indices[i].reshape(2, 6)
454
+ even_row_indices = even_odd_column_info[0]
455
+ odd_row_indices = even_odd_column_info[1]
456
+
457
+ # Starting ESA step value for this quarter cycle. Eg.
458
+ # 0, 180, 360, 540
459
+ start_esa_step = esa_step[i][0]
460
+ # Populate the first two columns of the checkerboard pattern
461
+ # using the start_esa_step value. Eg.
462
+ # Even row indices: 0, 1, 2, 3, 4, 5
463
+ # Odd row indices: 6, 7, 8, 9, 10, 11
464
+ first_two_columns[even_row_indices, 0] = np.arange(6) + start_esa_step
465
+ first_two_columns[odd_row_indices, 1] = np.arange(6) + start_esa_step + 6
466
+
467
+ # Repeat the first 2 column pattern 15 times across 30 columns
468
+ # (2 columns x 15 = 30)
469
+ base_pattern = np.tile(first_two_columns, (1, 15))
470
+
471
+ # Generate increment offsets: [0, 0, 12, 12, ..., 168, 168] -
472
+ # shape: (30,)
473
+ column_offsets = np.repeat(np.arange(15) * 12, 2)
474
+ increment_by = np.tile(column_offsets, (24, 1))
475
+
476
+ # Final checkerboard pattern with index offsets applied
477
+ checkerboard_pattern = base_pattern + increment_by
478
+ return checkerboard_pattern
479
+
480
+
481
+ def populated_data_in_checkerboard_pattern(
482
+ data_ds: xr.Dataset, checkerboard_pattern: npt.NDArray
483
+ ) -> dict:
484
+ """
485
+ Put input data in the checkerboard pattern.
486
+
487
+ Put these data variables from l1a data into the checkerboard pattern:
488
+ a. science_data
489
+ b. acq_start_coarse
490
+ c. acq_start_fine
491
+ d. acq_duration
492
+ e. settle_duration
493
+ f. esa_steps_number (This is created in the code and not from science packet)
494
+
495
+ These last five variables are used to calculate acquisition time of each
496
+ count data. Acquisition time and duration are carried in l1b for level 2
497
+ and 3 processing.
498
+
499
+ Parameters
500
+ ----------
501
+ data_ds : xarray.Dataset
502
+ Input data to be populated in the checkerboard pattern.
503
+ checkerboard_pattern : numpy.ndarray
504
+ Array with indices of where each cycle data goes in the checkerboard
505
+ pattern.
506
+
507
+ Returns
508
+ -------
509
+ var_names : dict
510
+ Dictionary with subset data populated in the checkerboard pattern.
511
+ """
512
+ # Flatten with top-down and left-right order. This will be used as
513
+ # indices to take and put data in the checkerboard pattern.
514
+ checkerboard_pattern = checkerboard_pattern.flatten(order="F")
515
+
516
+ # Variables that need to be put in the checkerboard pattern
517
+ var_names = {
518
+ "science_data": np.empty(
519
+ (
520
+ 0,
521
+ swe_constants.N_ESA_STEPS,
522
+ swe_constants.N_ANGLE_SECTORS,
523
+ swe_constants.N_CEMS,
524
+ )
525
+ ),
526
+ "acq_start_coarse": np.empty(
527
+ (0, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS)
528
+ ),
529
+ "acq_start_fine": np.empty(
530
+ (0, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS)
531
+ ),
532
+ "acq_duration": np.empty(
533
+ (0, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS)
534
+ ),
535
+ "settle_duration": np.empty(
536
+ (0, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS)
537
+ ),
538
+ "esa_step_number": np.empty(
539
+ (0, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS)
540
+ ),
541
+ }
542
+
543
+ for var_name in var_names:
544
+ # Reshape the data of input variable for easier processing.
545
+ if var_name == "science_data":
546
+ # Science data shape before reshaping is
547
+ # (number of packets, 180, 7)
548
+ # Reshape it to
549
+ # (number of full cycle, 720, N_CEMS)
550
+ data = data_ds[var_name].data.reshape(
551
+ -1,
552
+ swe_constants.N_QUARTER_CYCLES * swe_constants.N_QUARTER_CYCLE_STEPS,
553
+ swe_constants.N_CEMS,
554
+ )
555
+ # Apply the checkerboard pattern directly
556
+ populated_data = data[:, checkerboard_pattern, :]
557
+ # Reshape back into (n, 24, 30, 7)
558
+ populated_data = populated_data.reshape(
559
+ -1,
560
+ swe_constants.N_ESA_STEPS,
561
+ swe_constants.N_ANGLE_SECTORS,
562
+ swe_constants.N_CEMS,
563
+ order="F",
564
+ )
565
+ elif var_name == "esa_step_number":
566
+ # This needs to be created to capture information about esa step number
567
+ # from 0 to 179 for each quarter cycle. This is used to calculate data
568
+ # acquisition time.
569
+ epoch_data = data_ds["epoch"].data
570
+ total_cycles = epoch_data.reshape(-1, swe_constants.N_QUARTER_CYCLES).shape[
571
+ 0
572
+ ]
573
+ # Now repeat this pattern n number of cycles
574
+ data = np.tile(
575
+ np.tile(
576
+ np.arange(swe_constants.N_QUARTER_CYCLE_STEPS),
577
+ swe_constants.N_QUARTER_CYCLES,
578
+ ),
579
+ (total_cycles, 1),
580
+ )
581
+ # Apply the checkerboard pattern directly
582
+ populated_data = data[:, checkerboard_pattern]
583
+ # Reshape back into (n, 24, 30)
584
+ populated_data = populated_data.reshape(
585
+ -1, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS, order="F"
586
+ )
587
+ else:
588
+ # Input shape is number of packets. Reshape it to
589
+ # (number of full cycle, 4)
590
+ # This is because we have the same value for each quarter
591
+ # cycle.
592
+ data = data_ds[var_name].data.reshape(-1, swe_constants.N_QUARTER_CYCLES)
593
+ # Repeat the data 180 times to match the checkerboard pattern
594
+ data = np.repeat(data, swe_constants.N_QUARTER_CYCLE_STEPS).reshape(-1, 720)
595
+ # Apply the checkerboard pattern directly
596
+ populated_data = data[:, checkerboard_pattern]
597
+ # Reshape back into (n, 24, 30)
598
+ populated_data = populated_data.reshape(
599
+ -1, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS, order="F"
600
+ )
601
+
602
+ # Save the populated data in the dictionary
603
+ var_names[var_name] = populated_data
604
+
605
+ return var_names
606
+
607
+
608
+ def filter_full_cycle_data(
609
+ full_cycle_data_indices: np.ndarray, l1a_data: xr.Dataset
610
+ ) -> xr.Dataset:
611
+ """
612
+ Filter metadata and science of packets that makes full cycles.
613
+
614
+ Parameters
615
+ ----------
616
+ full_cycle_data_indices : numpy.ndarray
617
+ Array with indices of full cycles.
618
+ l1a_data : xarray.Dataset
619
+ L1A dataset.
620
+
621
+ Returns
622
+ -------
623
+ l1a_data : xarray.Dataset
624
+ L1A dataset with filtered metadata.
625
+ """
626
+ for key, value in l1a_data.items():
627
+ l1a_data[key] = value.data[full_cycle_data_indices]
628
+ return l1a_data
629
+
630
+
631
+ def swe_l1b(dependencies: ProcessingInputCollection) -> xr.Dataset:
632
+ """
633
+ SWE l1b science processing.
634
+
635
+ Parameters
636
+ ----------
637
+ dependencies : ProcessingInputCollection
638
+ Object containing lists of dependencies that CLI dependency
639
+ parameter received.
640
+
641
+ Returns
642
+ -------
643
+ dataset : xarray.Dataset
644
+ Processed l1b data.
645
+ """
646
+ # Read science data
647
+ science_files = dependencies.get_file_paths(descriptor="sci")
648
+ l1a_data = load_cdf(science_files[0])
649
+
650
+ total_packets = len(l1a_data["science_data"].data)
651
+
652
+ l1a_data_copy = l1a_data.copy(deep=True)
653
+
654
+ # First convert some science data to engineering units
655
+ # ---------------------------------------------------------------
656
+ apid = int(l1a_data_copy.attrs["packet_apid"])
657
+
658
+ # convert value from raw to engineering units as needed
659
+ conversion_table_path = dependencies.get_file_paths(descriptor="eu-conversion")[0]
37
660
  # Look up packet name from APID
38
661
  packet_name = next(packet for packet in SWEAPID if packet.value == apid)
39
662
 
40
663
  # Convert raw data to engineering units as needed
41
- eu_data = convert_raw_to_eu(
42
- l1a_dataset,
664
+ l1a_data_copy = convert_raw_to_eu(
665
+ l1a_data_copy,
43
666
  conversion_table_path=conversion_table_path,
44
667
  packet_name=packet_name.name,
45
668
  )
46
- data = swe_l1b_science(eu_data, data_version)
47
- if data is None:
48
- logger.info("No data to write to CDF")
49
- return []
50
- return [data]
669
+
670
+ # Filter out all in-flight calibration data
671
+ # -----------------------------------------
672
+ # If ESA lookup table number is in-flight calibration
673
+ # mode, then skip all those data per SWE teams specification.
674
+ # SWE team only wants in-flight calibration data to be processed
675
+ # upto l1a. In-flight calibration data looks same as science data
676
+ # but it only measures one energy or specific energy steps during
677
+ # the whole duration. Right now, only index 0 in LUT collects
678
+ # science data.
679
+ science_data = l1a_data_copy["esa_table_num"].data == 0
680
+ # Filter out all in-flight calibration data
681
+ l1a_data_copy = l1a_data_copy.isel({"epoch": science_data})
682
+
683
+ full_cycle_data_indices = get_indices_of_full_cycles(
684
+ l1a_data_copy["quarter_cycle"].data
685
+ )
686
+ logger.debug(
687
+ f"Quarter cycle data before filtering: {l1a_data_copy['quarter_cycle'].data}"
688
+ )
689
+
690
+ # Delete Raw Science Data from l1b and onwards
691
+ del l1a_data_copy["raw_science_data"]
692
+
693
+ if full_cycle_data_indices.size == 0:
694
+ # Log that no data is found for science data
695
+ logger.info("No full cycle data found. Skipping.")
696
+ return None
697
+
698
+ # In this case, we found incomplete cycle data. We need to filter
699
+ # out all the data that does not make a full cycle.
700
+ if len(full_cycle_data_indices) != total_packets:
701
+ # Filter metadata and science data of packets that makes full cycles
702
+ full_cycle_l1a_data = l1a_data_copy.isel({"epoch": full_cycle_data_indices})
703
+
704
+ # Update total packets
705
+ total_packets = len(full_cycle_data_indices)
706
+ logger.debug(
707
+ "Quarters cycle after filtering: "
708
+ f"{full_cycle_l1a_data['quarter_cycle'].data}"
709
+ )
710
+ if len(full_cycle_data_indices) != len(
711
+ full_cycle_l1a_data["quarter_cycle"].data
712
+ ):
713
+ raise ValueError(
714
+ "Error: full cycle data indices and filtered quarter cycle data size "
715
+ "mismatch"
716
+ )
717
+
718
+ # Main science processing steps
719
+ # ---------------------------------------------------------------
720
+ # 1. Populate data in the checkerboard pattern. This can return
721
+ # data in a dictionary.
722
+ # 2. Apply deadtime correction to each count data
723
+ # 3. Apply in-flight calibration to count data
724
+ # 4. Convert counts to rate using acquisition duration
725
+
726
+ # Read ESA lookup table
727
+ esa_lut_files = dependencies.get_file_paths(descriptor="esa-lut")
728
+ if len(esa_lut_files) > 1:
729
+ logger.warning(
730
+ f"More than one ESA lookup table file found: {esa_lut_files}. "
731
+ "Using the first one."
732
+ )
733
+
734
+ # Get checkerboard pattern
735
+ checkerboard_pattern = get_checker_board_pattern(esa_lut_files[0])
736
+
737
+ # Put data in the checkerboard pattern
738
+ populated_data = populated_data_in_checkerboard_pattern(
739
+ full_cycle_l1a_data, checkerboard_pattern
740
+ )
741
+ acq_duration = populated_data["acq_duration"]
742
+ acq_start_time = combine_acquisition_time(
743
+ populated_data["acq_start_coarse"],
744
+ populated_data["acq_start_fine"],
745
+ )
746
+ acq_time = calculate_data_acquisition_time(
747
+ acq_start_time,
748
+ populated_data["esa_step_number"],
749
+ acq_duration,
750
+ populated_data["settle_duration"],
751
+ )
752
+ corrected_count = deadtime_correction(populated_data["science_data"], acq_duration)
753
+
754
+ # Read in-flight calibration data
755
+ in_flight_cal_files = dependencies.get_file_paths(descriptor="l1b-in-flight-cal")
756
+
757
+ inflight_applied_count = apply_in_flight_calibration(
758
+ corrected_count, acq_time, in_flight_cal_files
759
+ )
760
+
761
+ count_rate = convert_counts_to_rate(inflight_applied_count, acq_duration)
762
+
763
+ # Store ESA energies of full cycle for L2 purposes.
764
+ esa_energies = get_esa_energy_pattern(esa_lut_files[0])
765
+ # Repeat energies to be in the same shape as the science data
766
+ esa_energies = np.repeat(esa_energies, total_packets // 4).reshape(
767
+ -1, swe_constants.N_ESA_STEPS, swe_constants.N_ANGLE_SECTORS
768
+ )
769
+ # Convert voltage to electron energy in eV by apply conversion factor
770
+ esa_energies = esa_energies * swe_constants.ENERGY_CONVERSION_FACTOR
771
+ # ------------------------------------------------------------------
772
+ # Save data to dataset.
773
+ # ------------------------------------------------------------------
774
+ # Load CDF attrs
775
+ cdf_attrs = ImapCdfAttributes()
776
+ cdf_attrs.add_instrument_global_attrs("swe")
777
+ cdf_attrs.add_instrument_variable_attrs("swe", "l1b")
778
+
779
+ # One full cycle data combines four quarter cycles data.
780
+ # Epoch will store center of each science meansurement using
781
+ # third acquisition start time coarse and fine value
782
+ # of four quarter cycle data packets. For example, we want to
783
+ # get indices of 3rd quarter cycle data packet in each full cycle
784
+ # and use that to calculate center time of data acquisition time.
785
+ # Quarter cycle indices: 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, ...
786
+ indices_of_center_time = np.arange(2, total_packets, swe_constants.N_QUARTER_CYCLES)
787
+
788
+ center_time = combine_acquisition_time(
789
+ full_cycle_l1a_data["acq_start_coarse"].data[indices_of_center_time],
790
+ full_cycle_l1a_data["acq_start_fine"].data[indices_of_center_time],
791
+ )
792
+
793
+ epoch_time = xr.DataArray(
794
+ met_to_ttj2000ns(center_time),
795
+ name="epoch",
796
+ dims=["epoch"],
797
+ attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False),
798
+ )
799
+
800
+ esa_step = xr.DataArray(
801
+ np.arange(swe_constants.N_ESA_STEPS),
802
+ name="esa_step",
803
+ dims=["esa_step"],
804
+ attrs=cdf_attrs.get_variable_attributes("esa_step", check_schema=False),
805
+ )
806
+
807
+ # NOTE: LABL_PTR_1 should be CDF_CHAR.
808
+ esa_step_label = xr.DataArray(
809
+ esa_step.values.astype(str),
810
+ name="esa_step_label",
811
+ dims=["esa_step"],
812
+ attrs=cdf_attrs.get_variable_attributes("esa_step_label", check_schema=False),
813
+ )
814
+
815
+ spin_sector = xr.DataArray(
816
+ np.arange(swe_constants.N_ANGLE_SECTORS),
817
+ name="spin_sector",
818
+ dims=["spin_sector"],
819
+ attrs=cdf_attrs.get_variable_attributes("spin_sector", check_schema=False),
820
+ )
821
+
822
+ # NOTE: LABL_PTR_2 should be CDF_CHAR.
823
+ spin_sector_label = xr.DataArray(
824
+ spin_sector.values.astype(str),
825
+ name="spin_sector_label",
826
+ dims=["spin_sector"],
827
+ attrs=cdf_attrs.get_variable_attributes(
828
+ "spin_sector_label", check_schema=False
829
+ ),
830
+ )
831
+
832
+ cycle = xr.DataArray(
833
+ np.arange(swe_constants.N_QUARTER_CYCLES),
834
+ name="cycle",
835
+ dims=["cycle"],
836
+ attrs=cdf_attrs.get_variable_attributes("cycle", check_schema=False),
837
+ )
838
+
839
+ cycle_label = xr.DataArray(
840
+ cycle.values.astype(str),
841
+ name="cycle_label",
842
+ dims=["cycle"],
843
+ attrs=cdf_attrs.get_variable_attributes("cycle_label", check_schema=False),
844
+ )
845
+
846
+ cem_id = xr.DataArray(
847
+ np.arange(swe_constants.N_CEMS, dtype=np.int8),
848
+ name="cem_id",
849
+ dims=["cem_id"],
850
+ attrs=cdf_attrs.get_variable_attributes("cem_id", check_schema=False),
851
+ )
852
+
853
+ # NOTE: LABL_PTR_3 should be CDF_CHAR.
854
+ cem_id_label = xr.DataArray(
855
+ cem_id.values.astype(str),
856
+ name="cem_id_label",
857
+ dims=["cem_id"],
858
+ attrs=cdf_attrs.get_variable_attributes("cem_id_label", check_schema=False),
859
+ )
860
+
861
+ # Add science data and it's associated metadata into dataset.
862
+ # SCIENCE_DATA has array of this shape:
863
+ # (n, 24, 30, 7)
864
+ # n = total number of full cycles
865
+ # 24 rows --> 24 esa voltage measurements
866
+ # 30 columns --> 30 spin angle measurements
867
+ # 7 elements --> 7 CEMs counts
868
+ #
869
+ # The metadata array will need to have this shape:
870
+ # (n, 4)
871
+ # n = total number of full cycles
872
+ # 4 rows --> metadata for each full cycle. Each element of 4 maps to
873
+ # metadata of one quarter cycle.
874
+
875
+ # Create the dataset
876
+ dataset = xr.Dataset(
877
+ coords={
878
+ "epoch": epoch_time,
879
+ "esa_step": esa_step,
880
+ "spin_sector": spin_sector,
881
+ "cem_id": cem_id,
882
+ "cycle": cycle,
883
+ "esa_step_label": esa_step_label,
884
+ "spin_sector_label": spin_sector_label,
885
+ "cem_id_label": cem_id_label,
886
+ "cycle_label": cycle_label,
887
+ },
888
+ attrs=cdf_attrs.get_global_attributes("imap_swe_l1b_sci"),
889
+ )
890
+
891
+ dataset["science_data"] = xr.DataArray(
892
+ count_rate,
893
+ dims=["epoch", "esa_step", "spin_sector", "cem_id"],
894
+ attrs=cdf_attrs.get_variable_attributes("science_data"),
895
+ )
896
+ dataset["acquisition_time"] = xr.DataArray(
897
+ acq_time,
898
+ dims=["epoch", "esa_step", "spin_sector"],
899
+ attrs=cdf_attrs.get_variable_attributes("acquisition_time"),
900
+ )
901
+ dataset["acq_duration"] = xr.DataArray(
902
+ acq_duration,
903
+ dims=["epoch", "esa_step", "spin_sector"],
904
+ attrs=cdf_attrs.get_variable_attributes("acq_duration"),
905
+ )
906
+
907
+ dataset["esa_energy"] = xr.DataArray(
908
+ esa_energies,
909
+ dims=["epoch", "esa_step", "spin_sector"],
910
+ attrs=cdf_attrs.get_variable_attributes("esa_energy"),
911
+ )
912
+
913
+ # create xarray dataarray for each data field
914
+ for key, value in full_cycle_l1a_data.items():
915
+ if key in ["science_data", "acq_duration"]:
916
+ continue
917
+ varname = key.lower()
918
+ dataset[varname] = xr.DataArray(
919
+ value.data.reshape(-1, swe_constants.N_QUARTER_CYCLES),
920
+ dims=["epoch", "cycle"],
921
+ attrs=cdf_attrs.get_variable_attributes(varname),
922
+ )
923
+
924
+ logger.info("SWE L1b science processing completed")
925
+ return [dataset]