imap-processing 0.9.0__py3-none-any.whl → 0.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of imap-processing might be problematic. Click here for more details.

Files changed (243) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +749 -442
  3. imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +7 -0
  4. imap_processing/cdf/config/imap_glows_l1a_variable_attrs.yaml +8 -2
  5. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +0 -1
  6. imap_processing/cdf/config/imap_glows_l2_variable_attrs.yaml +358 -0
  7. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +59 -25
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +22 -0
  9. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +32 -8
  10. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +94 -5
  11. imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +65 -37
  12. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +16 -1
  13. imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +7 -0
  14. imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +14 -14
  15. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +25 -24
  16. imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +238 -0
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +100 -92
  18. imap_processing/cdf/utils.py +2 -2
  19. imap_processing/cli.py +45 -9
  20. imap_processing/codice/codice_l1a.py +104 -58
  21. imap_processing/codice/constants.py +111 -155
  22. imap_processing/codice/data/esa_sweep_values.csv +256 -256
  23. imap_processing/codice/data/lo_stepping_values.csv +128 -128
  24. imap_processing/ena_maps/ena_maps.py +519 -0
  25. imap_processing/ena_maps/utils/map_utils.py +145 -0
  26. imap_processing/ena_maps/utils/spatial_utils.py +226 -0
  27. imap_processing/glows/__init__.py +3 -0
  28. imap_processing/glows/ancillary/imap_glows_pipeline_settings_v001.json +52 -0
  29. imap_processing/glows/l1a/glows_l1a.py +72 -14
  30. imap_processing/glows/l1b/glows_l1b.py +2 -1
  31. imap_processing/glows/l1b/glows_l1b_data.py +25 -1
  32. imap_processing/glows/l2/glows_l2.py +324 -0
  33. imap_processing/glows/l2/glows_l2_data.py +156 -51
  34. imap_processing/hi/l1a/science_direct_event.py +57 -51
  35. imap_processing/hi/l1b/hi_l1b.py +43 -28
  36. imap_processing/hi/l1c/hi_l1c.py +225 -42
  37. imap_processing/hi/utils.py +20 -3
  38. imap_processing/hit/l0/constants.py +2 -2
  39. imap_processing/hit/l0/decom_hit.py +1 -1
  40. imap_processing/hit/l1a/hit_l1a.py +94 -13
  41. imap_processing/hit/l1b/hit_l1b.py +158 -9
  42. imap_processing/ialirt/l0/process_codicehi.py +156 -0
  43. imap_processing/ialirt/l0/process_codicelo.py +5 -2
  44. imap_processing/ialirt/packet_definitions/ialirt.xml +28 -20
  45. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +241 -0
  46. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +170 -0
  47. imap_processing/ialirt/packet_definitions/ialirt_swe.xml +258 -0
  48. imap_processing/ialirt/process_ephemeris.py +72 -40
  49. imap_processing/idex/decode.py +241 -0
  50. imap_processing/idex/idex_l1a.py +143 -81
  51. imap_processing/idex/idex_l1b.py +244 -10
  52. imap_processing/lo/l0/lo_science.py +61 -0
  53. imap_processing/lo/l1a/lo_l1a.py +98 -10
  54. imap_processing/lo/l1b/lo_l1b.py +2 -2
  55. imap_processing/lo/l1c/lo_l1c.py +2 -2
  56. imap_processing/lo/packet_definitions/lo_xtce.xml +1082 -9178
  57. imap_processing/mag/l0/decom_mag.py +2 -2
  58. imap_processing/mag/l1a/mag_l1a.py +7 -7
  59. imap_processing/mag/l1a/mag_l1a_data.py +62 -30
  60. imap_processing/mag/l1b/mag_l1b.py +11 -6
  61. imap_processing/quality_flags.py +18 -3
  62. imap_processing/spice/geometry.py +149 -177
  63. imap_processing/spice/kernels.py +26 -26
  64. imap_processing/spice/spin.py +233 -0
  65. imap_processing/spice/time.py +96 -31
  66. imap_processing/swapi/l1/swapi_l1.py +60 -31
  67. imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +363 -384
  68. imap_processing/swe/l1a/swe_l1a.py +8 -3
  69. imap_processing/swe/l1a/swe_science.py +24 -24
  70. imap_processing/swe/l1b/swe_l1b.py +2 -1
  71. imap_processing/swe/l1b/swe_l1b_science.py +181 -122
  72. imap_processing/swe/l2/swe_l2.py +337 -70
  73. imap_processing/swe/utils/swe_utils.py +28 -0
  74. imap_processing/tests/cdf/test_utils.py +2 -2
  75. imap_processing/tests/codice/conftest.py +20 -17
  76. imap_processing/tests/codice/data/validation/imap_codice_l1a_hskp_20241110193622_v0.0.0.cdf +0 -0
  77. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
  78. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-singles_20241110193700_v0.0.0.cdf +0 -0
  79. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-angular_20241110193700_v0.0.0.cdf +0 -0
  80. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-priority_20241110193700_v0.0.0.cdf +0 -0
  81. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-species_20241110193700_v0.0.0.cdf +0 -0
  82. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-angular_20241110193700_v0.0.0.cdf +0 -0
  83. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-priority_20241110193700_v0.0.0.cdf +0 -0
  84. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-species_20241110193700_v0.0.0.cdf +0 -0
  85. imap_processing/tests/codice/test_codice_l0.py +55 -121
  86. imap_processing/tests/codice/test_codice_l1a.py +147 -59
  87. imap_processing/tests/conftest.py +81 -22
  88. imap_processing/tests/ena_maps/test_ena_maps.py +309 -0
  89. imap_processing/tests/ena_maps/test_map_utils.py +286 -0
  90. imap_processing/tests/ena_maps/test_spatial_utils.py +161 -0
  91. imap_processing/tests/glows/conftest.py +7 -1
  92. imap_processing/tests/glows/test_glows_l1a_cdf.py +3 -7
  93. imap_processing/tests/glows/test_glows_l1a_data.py +34 -6
  94. imap_processing/tests/glows/test_glows_l1b_data.py +29 -17
  95. imap_processing/tests/glows/test_glows_l2.py +101 -0
  96. imap_processing/tests/hi/conftest.py +3 -3
  97. imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
  98. imap_processing/tests/hi/data/l1/imap_his_pset-calibration-prod-config_20240101_v001.csv +31 -0
  99. imap_processing/tests/hi/test_hi_l1b.py +14 -9
  100. imap_processing/tests/hi/test_hi_l1c.py +136 -36
  101. imap_processing/tests/hi/test_l1a.py +0 -2
  102. imap_processing/tests/hi/test_science_direct_event.py +18 -14
  103. imap_processing/tests/hi/test_utils.py +16 -11
  104. imap_processing/tests/hit/helpers/__init__.py +0 -0
  105. imap_processing/tests/hit/helpers/l1_validation.py +405 -0
  106. imap_processing/tests/hit/test_data/sci_sample.ccsds +0 -0
  107. imap_processing/tests/hit/test_decom_hit.py +8 -10
  108. imap_processing/tests/hit/test_hit_l1a.py +117 -180
  109. imap_processing/tests/hit/test_hit_l1b.py +149 -55
  110. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -0
  111. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +62 -0
  112. imap_processing/tests/ialirt/test_data/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  113. imap_processing/tests/ialirt/test_data/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  114. imap_processing/tests/ialirt/test_data/l0/eu_SWP_IAL_20240826_152033.csv +644 -0
  115. imap_processing/tests/ialirt/test_data/l0/hi_fsw_view_1_ccsds.bin +0 -0
  116. imap_processing/tests/ialirt/test_data/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +914 -0
  117. imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  118. imap_processing/tests/ialirt/unit/test_process_codicehi.py +106 -0
  119. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +33 -5
  120. imap_processing/tests/ialirt/unit/test_process_swapi.py +85 -0
  121. imap_processing/tests/ialirt/unit/test_process_swe.py +106 -0
  122. imap_processing/tests/idex/conftest.py +29 -1
  123. imap_processing/tests/idex/test_data/compressed_2023_102_14_24_55.pkts +0 -0
  124. imap_processing/tests/idex/test_data/non_compressed_2023_102_14_22_26.pkts +0 -0
  125. imap_processing/tests/idex/test_idex_l0.py +6 -3
  126. imap_processing/tests/idex/test_idex_l1a.py +151 -1
  127. imap_processing/tests/idex/test_idex_l1b.py +124 -2
  128. imap_processing/tests/lo/test_lo_l1a.py +62 -2
  129. imap_processing/tests/lo/test_lo_science.py +85 -0
  130. imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SPIN_EU.csv +2 -0
  131. imap_processing/tests/mag/conftest.py +16 -0
  132. imap_processing/tests/mag/test_mag_decom.py +6 -4
  133. imap_processing/tests/mag/test_mag_l1a.py +36 -7
  134. imap_processing/tests/mag/test_mag_l1b.py +55 -4
  135. imap_processing/tests/mag/test_mag_validation.py +148 -0
  136. imap_processing/tests/mag/validation/L1a/T001/all_p_ones.txt +19200 -0
  137. imap_processing/tests/mag/validation/L1a/T001/mag-l0-l1a-t001-in.bin +0 -0
  138. imap_processing/tests/mag/validation/L1a/T001/mag-l0-l1a-t001-out.csv +17 -0
  139. imap_processing/tests/mag/validation/L1a/T002/all_n_ones.txt +19200 -0
  140. imap_processing/tests/mag/validation/L1a/T002/mag-l0-l1a-t002-in.bin +0 -0
  141. imap_processing/tests/mag/validation/L1a/T002/mag-l0-l1a-t002-out.csv +17 -0
  142. imap_processing/tests/mag/validation/L1a/T003/field_like.txt +19200 -0
  143. imap_processing/tests/mag/validation/L1a/T003/mag-l0-l1a-t003-in.bin +0 -0
  144. imap_processing/tests/mag/validation/L1a/T003/mag-l0-l1a-t003-out.csv +17 -0
  145. imap_processing/tests/mag/validation/L1a/T004/field_like.txt +19200 -0
  146. imap_processing/tests/mag/validation/L1a/T004/mag-l0-l1a-t004-in.bin +0 -0
  147. imap_processing/tests/mag/validation/L1a/T004/mag-l0-l1a-t004-out.csv +17 -0
  148. imap_processing/tests/mag/validation/L1a/T005/field_like_range_change.txt +19200 -0
  149. imap_processing/tests/mag/validation/L1a/T005/mag-l0-l1a-t005-in.bin +0 -0
  150. imap_processing/tests/mag/validation/L1a/T005/mag-l0-l1a-t005-out.csv +17 -0
  151. imap_processing/tests/mag/validation/L1a/T006/hdr_field.txt +19200 -0
  152. imap_processing/tests/mag/validation/L1a/T006/mag-l0-l1a-t006-in.bin +0 -0
  153. imap_processing/tests/mag/validation/L1a/T006/mag-l0-l1a-t006-out.csv +17 -0
  154. imap_processing/tests/mag/validation/L1a/T007/hdr_field_and_range_change.txt +19200 -0
  155. imap_processing/tests/mag/validation/L1a/T007/mag-l0-l1a-t007-in.bin +0 -0
  156. imap_processing/tests/mag/validation/L1a/T007/mag-l0-l1a-t007-out.csv +17 -0
  157. imap_processing/tests/mag/validation/L1a/T008/field_like_range_change.txt +19200 -0
  158. imap_processing/tests/mag/validation/L1a/T008/mag-l0-l1a-t008-in.bin +0 -0
  159. imap_processing/tests/mag/validation/L1a/T008/mag-l0-l1a-t008-out.csv +17 -0
  160. imap_processing/tests/mag/validation/L1b/T009/data.bin +0 -0
  161. imap_processing/tests/mag/validation/L1b/T009/field_like_all_ranges.txt +19200 -0
  162. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-in.csv +17 -0
  163. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +17 -0
  164. imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +17 -0
  165. imap_processing/tests/mag/validation/L1b/T010/data.bin +0 -0
  166. imap_processing/tests/mag/validation/L1b/T010/field_like_all_ranges.txt +19200 -0
  167. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-in.csv +17 -0
  168. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-magi-out.csv +17 -0
  169. imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-mago-out.csv +17 -0
  170. imap_processing/tests/mag/validation/L1b/T011/data.bin +0 -0
  171. imap_processing/tests/mag/validation/L1b/T011/field_like_all_ranges.txt +19200 -0
  172. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-in.csv +17 -0
  173. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +17 -0
  174. imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +17 -0
  175. imap_processing/tests/spice/test_geometry.py +128 -133
  176. imap_processing/tests/spice/test_kernels.py +37 -37
  177. imap_processing/tests/spice/test_spin.py +184 -0
  178. imap_processing/tests/spice/test_time.py +43 -20
  179. imap_processing/tests/swapi/test_swapi_l1.py +11 -10
  180. imap_processing/tests/swapi/test_swapi_l2.py +13 -3
  181. imap_processing/tests/swe/test_swe_l1a.py +1 -1
  182. imap_processing/tests/swe/test_swe_l1b.py +20 -3
  183. imap_processing/tests/swe/test_swe_l1b_science.py +54 -35
  184. imap_processing/tests/swe/test_swe_l2.py +148 -5
  185. imap_processing/tests/test_cli.py +39 -7
  186. imap_processing/tests/test_quality_flags.py +19 -19
  187. imap_processing/tests/test_utils.py +3 -2
  188. imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
  189. imap_processing/tests/ultra/test_data/mock_data.py +161 -0
  190. imap_processing/tests/ultra/unit/conftest.py +73 -0
  191. imap_processing/tests/ultra/unit/test_badtimes.py +58 -0
  192. imap_processing/tests/ultra/unit/test_cullingmask.py +87 -0
  193. imap_processing/tests/ultra/unit/test_de.py +61 -60
  194. imap_processing/tests/ultra/unit/test_ultra_l1a.py +3 -3
  195. imap_processing/tests/ultra/unit/test_ultra_l1b.py +51 -77
  196. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +5 -5
  197. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +114 -0
  198. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +86 -26
  199. imap_processing/tests/ultra/unit/test_ultra_l1c.py +1 -1
  200. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +3 -3
  201. imap_processing/ultra/constants.py +11 -1
  202. imap_processing/ultra/l1a/ultra_l1a.py +2 -2
  203. imap_processing/ultra/l1b/badtimes.py +22 -5
  204. imap_processing/ultra/l1b/cullingmask.py +31 -5
  205. imap_processing/ultra/l1b/de.py +32 -37
  206. imap_processing/ultra/l1b/extendedspin.py +44 -20
  207. imap_processing/ultra/l1b/ultra_l1b.py +21 -22
  208. imap_processing/ultra/l1b/ultra_l1b_culling.py +190 -0
  209. imap_processing/ultra/l1b/ultra_l1b_extended.py +81 -30
  210. imap_processing/ultra/l1c/histogram.py +6 -2
  211. imap_processing/ultra/l1c/pset.py +6 -2
  212. imap_processing/ultra/l1c/ultra_l1c.py +2 -3
  213. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +4 -3
  214. imap_processing/ultra/utils/ultra_l1_utils.py +70 -14
  215. imap_processing/utils.py +2 -2
  216. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/METADATA +7 -2
  217. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/RECORD +235 -152
  218. imap_processing/tests/codice/data/eu_unit_lookup_table.csv +0 -101
  219. imap_processing/tests/codice/data/idle_export_eu.COD_NHK_20230822_122700 2.csv +0 -100
  220. imap_processing/tests/codice/data/idle_export_raw.COD_NHK_20230822_122700.csv +0 -100
  221. imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
  222. imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
  223. imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
  224. imap_processing/tests/ultra/unit/test_spatial_utils.py +0 -125
  225. imap_processing/ultra/utils/spatial_utils.py +0 -221
  226. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_APP_NHK.bin +0 -0
  227. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_APP_NHK.csv +0 -0
  228. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_SCI_CNT.bin +0 -0
  229. /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_SCI_DE.bin +0 -0
  230. /imap_processing/tests/hi/{test_data → data}/l0/H90_NHK_20241104.bin +0 -0
  231. /imap_processing/tests/hi/{test_data → data}/l0/H90_sci_cnt_20241104.bin +0 -0
  232. /imap_processing/tests/hi/{test_data → data}/l0/H90_sci_de_20241104.bin +0 -0
  233. /imap_processing/tests/hi/{test_data → data}/l0/README.txt +0 -0
  234. /imap_processing/tests/idex/{imap_idex_l0_raw_20231214_v001.pkts → test_data/imap_idex_l0_raw_20231214_v001.pkts} +0 -0
  235. /imap_processing/tests/idex/{impact_14_tof_high_data.txt → test_data/impact_14_tof_high_data.txt} +0 -0
  236. /imap_processing/tests/mag/{imap_mag_l1a_norm-magi_20251017_v001.cdf → validation/imap_mag_l1a_norm-magi_20251017_v001.cdf} +0 -0
  237. /imap_processing/tests/mag/{mag_l0_test_data.pkts → validation/mag_l0_test_data.pkts} +0 -0
  238. /imap_processing/tests/mag/{mag_l0_test_output.csv → validation/mag_l0_test_output.csv} +0 -0
  239. /imap_processing/tests/mag/{mag_l1_test_data.pkts → validation/mag_l1_test_data.pkts} +0 -0
  240. /imap_processing/tests/mag/{mag_l1a_test_output.csv → validation/mag_l1a_test_output.csv} +0 -0
  241. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/LICENSE +0 -0
  242. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/WHEEL +0 -0
  243. {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,405 @@
1
+ """helper functions for HIT L1 unit tests"""
2
+
3
+ import re
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ import xarray as xr
8
+
9
+ # <=== CONSTANTS ===>
10
+
11
+ # Dictionary of columns to consolidate
12
+ # key = new column name
13
+ # value = prefix of column names in the validation data
14
+ RATE_COLUMNS = {
15
+ "coinrates": "COINRATES_",
16
+ "pbufrates": "BUFRATES_",
17
+ "l2fgrates": "L2FGRATES_",
18
+ "l2bgrates": "L2BGRATES_",
19
+ "l3fgrates": "L3FGRATES_",
20
+ "l3bgrates": "L3BGRATES_",
21
+ "penfgrates": "PENFGRATES_",
22
+ "penbgrates": "PENBGRATES_",
23
+ "sectorates": "SECTORATES_",
24
+ "l4fgrates": "L4FGRATES_",
25
+ "l4bgrates": "L4BGRATES_",
26
+ "ialirtrates": "IALIRTRATES_",
27
+ "sngrates_hg": "SNGRATES_HG_",
28
+ "sngrates_lg": "SNGRATES_LG_",
29
+ }
30
+
31
+ # Dictionary of columns to rename
32
+ # key = existing column name
33
+ # value = new column name
34
+ RENAME_COLUMNS = {
35
+ "CCSDS_VERSION": "version",
36
+ "CCSDS_TYPE": "type",
37
+ "CCSDS_SEC_HDR_FLAG": "sec_hdr_flg",
38
+ "CCSDS_APPID": "pkt_apid",
39
+ "CCSDS_GRP_FLAG": "seq_flgs",
40
+ "CCSDS_SEQ_CNT": "src_seq_ctr",
41
+ "CCSDS_LENGTH": "pkt_len",
42
+ "SC_TICK": "sc_tick_by_frame",
43
+ "CODE_OK": "hdr_code_ok",
44
+ "HEATER_DUTY_CYCLE": "hdr_heater_duty_cycle",
45
+ "LEAK_CONV": "hdr_leak_conv",
46
+ "DY_TH_STATE": "hdr_dynamic_threshold_state",
47
+ "LIVE_TIME": "livetime_counter",
48
+ }
49
+
50
+ MOD_VALUE_TO_SPECIES_ENERGY_MAP = {
51
+ 0: {"species": "H", "energy_idx": 0},
52
+ 1: {"species": "H", "energy_idx": 1},
53
+ 2: {"species": "H", "energy_idx": 2},
54
+ 3: {"species": "He4", "energy_idx": 0},
55
+ 4: {"species": "He4", "energy_idx": 1},
56
+ 5: {"species": "CNO", "energy_idx": 0},
57
+ 6: {"species": "CNO", "energy_idx": 1},
58
+ 7: {"species": "NeMgSi", "energy_idx": 0},
59
+ 8: {"species": "NeMgSi", "energy_idx": 1},
60
+ 9: {"species": "Fe", "energy_idx": 0},
61
+ }
62
+
63
+
64
+ # <=== HELPER FUNCTIONS FOR L1 DATA VALIDATION ===>
65
+ def prepare_counts_validation_data(validation_data: pd.DataFrame) -> pd.DataFrame:
66
+ """Prepare validation data for comparison with processed data.
67
+
68
+ The L1A counts validation data is organized with each value in a
69
+ separate column. This function consolidates related data into
70
+ arrays to match the processed data. It also renames columns to
71
+ match the processed data.
72
+
73
+ Parameters
74
+ ----------
75
+ validation_data : pd.DataFrame
76
+ Validation data extracted from a csv file
77
+
78
+ Returns
79
+ -------
80
+ pd.DataFrame
81
+ Validation data formatted for comparison with processed data
82
+ """
83
+ validation_data.columns = validation_data.columns.str.strip()
84
+ validation_data.rename(columns=RENAME_COLUMNS, inplace=True)
85
+ validation_data = consolidate_rate_columns(validation_data, RATE_COLUMNS)
86
+ validation_data = process_single_rates(validation_data)
87
+ validation_data = add_species_energy(validation_data)
88
+ validation_data.columns = validation_data.columns.str.lower()
89
+ return validation_data
90
+
91
+
92
+ def prepare_standard_rates_validation_data(
93
+ validation_data: pd.DataFrame,
94
+ ) -> pd.DataFrame:
95
+ """Prepare validation data for comparison with processed data.
96
+
97
+ The L1B standard rates validation data is organized with each
98
+ value in a separate column. This function consolidates related
99
+ data into arrays that match the processed data.
100
+
101
+ Parameters
102
+ ----------
103
+ validation_data : pd.DataFrame
104
+ Validation data extracted from a csv file
105
+
106
+ Returns
107
+ -------
108
+ pd.DataFrame
109
+ Validation data formatted for comparison with processed data
110
+ """
111
+ validation_data.columns = validation_data.columns.str.strip()
112
+ validation_data = consolidate_rate_columns(
113
+ validation_data, {k: v for k, v in RATE_COLUMNS.items() if k != "sectorates"}
114
+ )
115
+ validation_data = process_single_rates(validation_data)
116
+ validation_data.columns = validation_data.columns.str.lower()
117
+ return validation_data
118
+
119
+
120
+ def consolidate_rate_columns(
121
+ data: pd.DataFrame, rate_columns: dict[str, str]
122
+ ) -> pd.DataFrame:
123
+ """Consolidate related data into arrays to match processed data.
124
+
125
+ The validation data has each value in a separate column. This
126
+ function aggregates related data into arrays to match processed
127
+ data. Each rate column has a corresponding delta plus and delta
128
+ minus column for uncertainty values.
129
+
130
+ Parameters
131
+ ----------
132
+ data : pd.DataFrame
133
+ Validation data
134
+
135
+ rate_columns : dict[str, str]
136
+ Dictionary of columns to consolidate
137
+
138
+ Returns
139
+ -------
140
+ pd.DataFrame
141
+ Validation data with rate columns consolidated into arrays
142
+ """
143
+ for new_col, prefix in rate_columns.items():
144
+ pattern_rates = re.compile(rf"^{prefix}\d+$")
145
+ pattern_delta_plus = re.compile(rf"^{prefix}\d+_DELTA_PLUS$")
146
+ pattern_delta_minus = re.compile(rf"^{prefix}\d+_DELTA_MINUS$")
147
+ data[new_col] = data.filter(regex=pattern_rates.pattern).apply(
148
+ lambda row: row.values, axis=1
149
+ )
150
+ data[f"{new_col}_delta_plus"] = data.filter(
151
+ regex=pattern_delta_plus.pattern
152
+ ).apply(lambda row: row.values, axis=1)
153
+ data[f"{new_col}_delta_minus"] = data.filter(
154
+ regex=pattern_delta_minus.pattern
155
+ ).apply(lambda row: row.values, axis=1)
156
+ if new_col == "sectorates":
157
+ data = consolidate_sectorates(data)
158
+ data.drop(
159
+ columns=data.filter(regex=pattern_rates.pattern).columns, inplace=True
160
+ )
161
+ data.drop(
162
+ columns=data.filter(regex=pattern_delta_plus.pattern).columns, inplace=True
163
+ )
164
+ data.drop(
165
+ columns=data.filter(regex=pattern_delta_minus.pattern).columns, inplace=True
166
+ )
167
+ return data
168
+
169
+
170
+ def consolidate_sectorates(data: pd.DataFrame) -> pd.DataFrame:
171
+ """Consolidate sector rate data into arrays.
172
+
173
+ This function distinguishes between sector rate columns with three digits
174
+ and those with four digits in their names.
175
+
176
+ SECTORATES_000 SECTORATES_000_0 SECTORATES_000_1 SECTORATES_000_2...SECTORATES_120_9
177
+ 0 0
178
+ 0 0
179
+ 0 0
180
+ :
181
+
182
+ Columns with three digits (e.g., SECTORATE_000) contain sectorate
183
+ values for the science frame, with 120 such columns in the validation
184
+ data. These will be organized into an array named "sectorates".
185
+
186
+ Columns with four digits (e.g., SECTORATES_000_0) include the sectorate
187
+ values with a mod 10 value appended (e.g., 0). The mod 10 value determines
188
+ the species and energy range the sector rates represent in the science frame.
189
+ There are 10 possible species and energy ranges, but only one has data per
190
+ science frame. The validation data has 10 columns per 120 sector rates,
191
+ totaling 1200 columns per science frame. Each set of 10 columns will have
192
+ only one value, resulting in an array that looks like this:
193
+
194
+ [nan, nan, nan, 0, nan, nan, nan, nan, nan, nan...nan, nan, nan, 0, nan...]
195
+
196
+ These will be consolidated into a "sectorates_by_mod_val" column in the
197
+ validation data.
198
+
199
+ Parameters
200
+ ----------
201
+ data : pd.DataFrame
202
+ Validation data
203
+
204
+ Returns
205
+ -------
206
+ pd.DataFrame
207
+ Validation data with sectorate columns consolidated into arrays
208
+ """
209
+ sectorates_three_digits = data.filter(regex=r"^SECTORATES_\d{3}$").columns
210
+ sectorates_delta_plus_three_digits = data.filter(
211
+ regex=r"^SECTORATES_\d{3}_DELTA_PLUS$"
212
+ ).columns
213
+ sectorates_delta_minus_three_digits = data.filter(
214
+ regex=r"^SECTORATES_\d{3}_DELTA_MINUS$"
215
+ ).columns
216
+
217
+ data["sectorates"] = data[sectorates_three_digits].apply(
218
+ lambda row: row.values.reshape(8, 15), axis=1
219
+ )
220
+ data["sectorates_delta_plus"] = data[sectorates_delta_plus_three_digits].apply(
221
+ lambda row: row.values.reshape(8, 15), axis=1
222
+ )
223
+ data["sectorates_delta_minus"] = data[sectorates_delta_minus_three_digits].apply(
224
+ lambda row: row.values.reshape(8, 15), axis=1
225
+ )
226
+
227
+ sectorates_four_digits = data.filter(regex=r"^SECTORATES_\d{3}_\d{1}$").columns
228
+ data["sectorates_by_mod_val"] = data[sectorates_four_digits].apply(
229
+ lambda row: row.values, axis=1
230
+ )
231
+ data.drop(
232
+ columns=data.filter(regex=r"^SECTORATES_\d{3}_\d{1}.*$").columns, inplace=True
233
+ )
234
+ return data
235
+
236
+
237
+ def process_single_rates(data: pd.DataFrame) -> pd.DataFrame:
238
+ """Combine the high and low gain single rates into 2D arrays
239
+
240
+ Parameters
241
+ ----------
242
+ data : pd.DataFrame
243
+ Validation data
244
+
245
+ Returns
246
+ -------
247
+ pd.DataFrame
248
+ Validation data with single rates combined into 2D arrays
249
+ """
250
+ data["sngrates"] = data.apply(
251
+ lambda row: np.array([row["sngrates_hg"], row["sngrates_lg"]]), axis=1
252
+ )
253
+ data["sngrates_delta_plus"] = data.apply(
254
+ lambda row: np.array(
255
+ [row["sngrates_hg_delta_plus"], row["sngrates_lg_delta_plus"]]
256
+ ),
257
+ axis=1,
258
+ )
259
+ data["sngrates_delta_minus"] = data.apply(
260
+ lambda row: np.array(
261
+ [row["sngrates_hg_delta_minus"], row["sngrates_lg_delta_minus"]]
262
+ ),
263
+ axis=1,
264
+ )
265
+ data.drop(
266
+ columns=[
267
+ "sngrates_hg",
268
+ "sngrates_lg",
269
+ "sngrates_hg_delta_plus",
270
+ "sngrates_lg_delta_plus",
271
+ "sngrates_hg_delta_minus",
272
+ "sngrates_lg_delta_minus",
273
+ ],
274
+ inplace=True,
275
+ )
276
+ return data
277
+
278
+
279
+ def add_species_energy(data: pd.DataFrame) -> pd.DataFrame:
280
+ """Add species and energy index to the validation data.
281
+
282
+ The sector rate data is organized by species and energy index
283
+ in the processed data so this function adds this information
284
+ to each row (i.e. science frame) in the validation data.
285
+
286
+ Parameters
287
+ ----------
288
+ data : pd.DataFrame
289
+ Validation data
290
+
291
+ Returns
292
+ -------
293
+ pd.DataFrame
294
+ Validation data with species and energy index added
295
+ """
296
+ # Find the mod value for each science frame which equals the
297
+ # first index in the sectorates_by_mod_val array that has a value
298
+ # instead of a nan or empty string.
299
+ # Then use the mod 10 value to determine the species and energy index
300
+ # for each science frame and add this information to the data frame
301
+ data["mod_10"] = data["sectorates_by_mod_val"].apply(
302
+ lambda row: next(
303
+ (i for i, value in enumerate(row) if pd.notna(value) and value != " "), None
304
+ )
305
+ )
306
+ data["species"] = data["mod_10"].apply(
307
+ lambda row: MOD_VALUE_TO_SPECIES_ENERGY_MAP[row]["species"].lower()
308
+ if row is not None
309
+ else None
310
+ )
311
+ data["energy_idx"] = data["mod_10"].apply(
312
+ lambda row: MOD_VALUE_TO_SPECIES_ENERGY_MAP[row]["energy_idx"]
313
+ if row is not None
314
+ else None
315
+ )
316
+ data.drop(columns=["sectorates_by_mod_val", "mod_10"], inplace=True)
317
+ return data
318
+
319
+
320
+ def compare_data(
321
+ expected_data: pd.DataFrame, actual_data: xr.Dataset, skip: list[str]
322
+ ) -> None:
323
+ """Compare the processed L1A counts data with the validation data.
324
+
325
+ Parameters
326
+ ----------
327
+ expected_data : pd.DataFrame
328
+ Validation data extracted from a csv file
329
+ and reformatted for comparison
330
+ actual_data : xr.Dataset
331
+ Processed counts data from l1a processing
332
+ skip : list
333
+ Fields to skip in comparison
334
+ """
335
+ for field in expected_data.columns:
336
+ if field not in [
337
+ "sc_tick_by_frame",
338
+ "species",
339
+ "energy_idx",
340
+ ]:
341
+ assert (
342
+ field in actual_data.data_vars.keys()
343
+ ), f"Field {field} not found in actual data variables"
344
+ if field not in skip:
345
+ for frame in range(expected_data.shape[0]):
346
+ if field == "species":
347
+ # Compare sector rates data using species and energy index.
348
+ # which are only present in the validation data. In the actual
349
+ # data, sector rates are organized by species in 4D arrays.
350
+ # i.e. h_counts_sectored has shape
351
+ # (epoch, h_energy_index, declination, azimuth).
352
+ # species and energy index are used to find the correct
353
+ # array of sector rate data from the actual data for comparison.
354
+ species = expected_data[field][frame]
355
+ energy_idx = expected_data["energy_idx"][frame]
356
+ if "sectorates_delta_plus" in expected_data.columns:
357
+ np.testing.assert_allclose(
358
+ actual_data[f"{species}_counts_sectored_delta_plus"][frame][
359
+ energy_idx
360
+ ].data,
361
+ expected_data["sectorates_delta_plus"][frame],
362
+ rtol=1e-7, # relative tolerance
363
+ atol=1e-8, # absolute tolerance
364
+ err_msg=f"Mismatch in {species}_counts_sectored_delta_"
365
+ f"plus at frame {frame}, energy_idx {energy_idx}",
366
+ )
367
+ if "sectorates_delta_minus" in expected_data.columns:
368
+ np.testing.assert_allclose(
369
+ actual_data[f"{species}_counts_sectored_delta_minus"][
370
+ frame
371
+ ][energy_idx].data,
372
+ expected_data["sectorates_delta_minus"][frame],
373
+ rtol=1e-7,
374
+ atol=1e-8,
375
+ err_msg=f"Mismatch in {species}_counts_sectored_delta_"
376
+ f"minus at frame {frame}, energy_idx {energy_idx}",
377
+ )
378
+ else:
379
+ np.testing.assert_allclose(
380
+ actual_data[f"{species}_counts_sectored"][frame][
381
+ energy_idx
382
+ ].data,
383
+ expected_data["sectorates"][frame],
384
+ rtol=1e-7,
385
+ atol=1e-8,
386
+ err_msg=f"Mismatch in {species}_counts_sectored at"
387
+ f"frame {frame}, energy_idx {energy_idx}",
388
+ )
389
+ elif field == "sc_tick_by_frame":
390
+ # Get the sc_tick values for each frame in the actual data
391
+ # to compare with the validation data
392
+ sc_tick = actual_data.sc_tick.values
393
+ sc_tick_by_frame = sc_tick[::20]
394
+ assert np.array_equal(
395
+ sc_tick_by_frame[frame], expected_data[field][frame]
396
+ ), f"Mismatch in {field} at frame {frame}"
397
+
398
+ else:
399
+ np.testing.assert_allclose(
400
+ actual_data[field][frame].data,
401
+ expected_data[field][frame],
402
+ rtol=1e-7,
403
+ atol=1e-8,
404
+ err_msg=f"Mismatch in {field} at frame {frame}",
405
+ )
@@ -79,7 +79,7 @@ def test_parse_count_rates(sci_dataset):
79
79
  "hdr_code_ok",
80
80
  "hdr_minute_cnt",
81
81
  "spare",
82
- "livetime",
82
+ "livetime_counter",
83
83
  "num_trig",
84
84
  "num_reject",
85
85
  "num_acc_w_pha",
@@ -107,7 +107,7 @@ def test_parse_count_rates(sci_dataset):
107
107
  "nerror",
108
108
  "nbadtags",
109
109
  "coinrates",
110
- "bufrates",
110
+ "pbufrates",
111
111
  "l2fgrates",
112
112
  "l2bgrates",
113
113
  "l3fgrates",
@@ -125,12 +125,10 @@ def test_parse_count_rates(sci_dataset):
125
125
 
126
126
  def test_is_sequential():
127
127
  """Test the is_sequential function."""
128
- counters = np.array([0, 1, 2, 3, 4])
129
- if is_sequential(counters):
130
- assert True
131
- counters = np.array([0, 2, 3, 4, 5])
132
- if not is_sequential(counters):
133
- assert True
128
+ assert is_sequential(np.array([0, 1, 2, 3, 4]))
129
+ assert not is_sequential(np.array([0, 2, 3, 4, 5]))
130
+ # Wrap-around case
131
+ assert is_sequential(np.array([16382, 16383, 0, 1, 2]))
134
132
 
135
133
 
136
134
  def test_get_valid_starting_indices():
@@ -244,7 +242,7 @@ def test_decom_hit(sci_dataset):
244
242
  "hdr_heater_duty_cycle",
245
243
  "hdr_code_ok",
246
244
  "hdr_minute_cnt",
247
- "livetime",
245
+ "livetime_counter",
248
246
  "num_trig",
249
247
  "num_reject",
250
248
  "num_acc_w_pha",
@@ -272,7 +270,7 @@ def test_decom_hit(sci_dataset):
272
270
  "nerror",
273
271
  "nbadtags",
274
272
  "coinrates",
275
- "bufrates",
273
+ "pbufrates",
276
274
  "l2fgrates",
277
275
  "l2bgrates",
278
276
  "l3fgrates",