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,226 @@
1
+ """IMAP utils for spatial binning and az/el grid creation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typing
6
+
7
+ import numpy as np
8
+ from numpy.typing import NDArray
9
+
10
+
11
+ def build_spatial_bins(
12
+ az_spacing_deg: float = 0.5,
13
+ el_spacing_deg: float = 0.5,
14
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
15
+ """
16
+ Build spatial bin boundaries for azimuth and elevation.
17
+
18
+ Input angles in degrees for consistency with map inputs,
19
+ output angles in radians for internal use.
20
+
21
+ Parameters
22
+ ----------
23
+ az_spacing_deg : float, optional
24
+ The azimuth bin spacing in degrees (default is 0.5 degrees).
25
+ el_spacing_deg : float, optional
26
+ The elevation bin spacing in degrees (default is 0.5 degrees).
27
+
28
+ Returns
29
+ -------
30
+ az_bin_edges : np.ndarray
31
+ Array of azimuth bin boundary values in radians.
32
+ el_bin_edges : np.ndarray
33
+ Array of elevation bin boundary values in radians.
34
+ az_bin_midpoints : np.ndarray
35
+ Array of azimuth bin midpoint values in radians.
36
+ el_bin_midpoints : np.ndarray
37
+ Array of elevation bin midpoint values in radians.
38
+ """
39
+ # Azimuth bins from 0 to 360 degrees.
40
+ az_bin_edges = np.arange(0, 360 + az_spacing_deg, az_spacing_deg)
41
+ az_bin_midpoints = az_bin_edges[:-1] + az_spacing_deg / 2 # Midpoints between edges
42
+
43
+ # Elevation bins from -90 to 90 degrees.
44
+ el_bin_edges = np.arange(-90, 90 + el_spacing_deg, el_spacing_deg)
45
+ el_bin_midpoints = el_bin_edges[:-1] + el_spacing_deg / 2 # Midpoints between edges
46
+
47
+ # Convert all angles to radians and return them
48
+ return (
49
+ np.deg2rad(az_bin_edges),
50
+ np.deg2rad(el_bin_edges),
51
+ np.deg2rad(az_bin_midpoints),
52
+ np.deg2rad(el_bin_midpoints),
53
+ )
54
+
55
+
56
+ def build_solid_angle_map(
57
+ spacing_deg: float,
58
+ ) -> NDArray:
59
+ """
60
+ Build a solid angle map in steradians for a given spacing in degrees.
61
+
62
+ Parameters
63
+ ----------
64
+ spacing_deg : float
65
+ The bin spacing in degrees.
66
+
67
+ Returns
68
+ -------
69
+ solid_angle_grid : np.ndarray
70
+ The solid angle map grid in steradians.
71
+ First index is latitude/el, second index is longitude/az.
72
+ """
73
+ # Degrees are the preferred input units of angle, given map definitions,
74
+ # but we'll convert to radians for internal calculations and output steradians.
75
+ spacing = np.deg2rad(spacing_deg)
76
+
77
+ if spacing <= 0:
78
+ raise ValueError("Spacing must be positive valued, non-zero.")
79
+
80
+ if not np.isclose((np.pi / spacing) % 1, 0):
81
+ raise ValueError("Spacing must divide evenly into pi radians.")
82
+
83
+ latitudes = np.arange(-np.pi / 2, np.pi / 2 + spacing, step=spacing)
84
+ sine_latitudes = np.sin(latitudes)
85
+ delta_sine_latitudes = np.diff(sine_latitudes)
86
+ solid_angle_by_latitude = np.abs(spacing * delta_sine_latitudes)
87
+
88
+ # Order ensures agreement with build_az_el_grid's order of tiling az/el grid.
89
+ solid_angle_grid = np.repeat(
90
+ solid_angle_by_latitude[np.newaxis, :], (2 * np.pi) / spacing, axis=0
91
+ )
92
+
93
+ return solid_angle_grid
94
+
95
+
96
+ @typing.no_type_check
97
+ def rewrap_even_spaced_az_el_grid(
98
+ raveled_values: NDArray,
99
+ shape: tuple[int] | None = None,
100
+ order: typing.Literal["C"] | typing.Literal["F"] = "C",
101
+ ) -> NDArray:
102
+ """
103
+ Take an unwrapped (raveled) 1D array and reshapes it into a 2D az/el grid.
104
+
105
+ Assumes the following must be true of the original grid:
106
+ 1. Grid was evenly spaced in angular space,
107
+ 2. Grid had the same spacing in both azimuth and elevation.
108
+ 3. Azimuth is axis 0 (and extends a total of 360 degrees).
109
+ 4. Elevation is axis 1 (and extends a total of 180 degrees),
110
+
111
+ Parameters
112
+ ----------
113
+ raveled_values : NDArray
114
+ 1D array of values to be reshaped into a 2D grid.
115
+ shape : tuple[int], optional
116
+ The shape of the original grid, if known, by default None.
117
+ If None, the shape will be inferred from the size of the input array.
118
+ order : {'C', 'F'}, optional
119
+ The order in which to rewrap the values, by default 'C'.
120
+
121
+ Returns
122
+ -------
123
+ NDArray
124
+ The reshaped 2D grid of values.
125
+
126
+ Raises
127
+ ------
128
+ ValueError
129
+ If the input is not a 1D array or 2D array with an 'extra' non-spatial axis.
130
+ """
131
+ if raveled_values.ndim > 2:
132
+ raise ValueError(
133
+ "Input must be a 1D array or 2D array with only one spatial axis as axis 0."
134
+ )
135
+
136
+ # We can infer the shape if its evenly spaced and 2D
137
+ if not shape:
138
+ spacing_deg = 1 / np.sqrt(raveled_values.shape[0] / (360 * 180))
139
+ shape = (int(360 // spacing_deg), int(180 // spacing_deg))
140
+
141
+ if raveled_values.ndim == 2:
142
+ shape = (shape[0], shape[1], raveled_values.shape[1])
143
+ return raveled_values.reshape(shape, order=order)
144
+
145
+
146
+ class AzElSkyGrid:
147
+ """
148
+ Representation of a 2D grid of azimuth and elevation angles covering the sky.
149
+
150
+ All angles are stored internally in radians.
151
+ Azimuth is within the range [0, 2*pi) radians,
152
+ elevation is within the range [-pi/2, pi/2) radians.
153
+
154
+ Parameters
155
+ ----------
156
+ spacing_deg : float, optional
157
+ Spacing of the grid in degrees, by default 0.5.
158
+ reversed_elevation : bool, optional
159
+ Whether the elevation grid should be reversed, by default False.
160
+ If False, the elevation grid will be from -pi/2 to pi/2 radians (-90 to 90 deg).
161
+ If True, the elevation grid will be from pi/2 to -pi/2 radians (90 to -90 deg).
162
+
163
+ Raises
164
+ ------
165
+ ValueError
166
+ If the spacing is not positive or does not divide evenly into pi radians.
167
+ """
168
+
169
+ def __init__(
170
+ self,
171
+ spacing_deg: float = 0.5,
172
+ reversed_elevation: bool = False,
173
+ ) -> None:
174
+ # Store grid properties
175
+ self.reversed_elevation = reversed_elevation
176
+
177
+ # Internally, work in radians, regardless of desired output units
178
+ self.spacing = np.deg2rad(spacing_deg)
179
+
180
+ # Ensure valid grid spacing (positive, divides evenly into pi radians)
181
+ if self.spacing <= 0:
182
+ raise ValueError("Spacing must be positive valued, non-zero.")
183
+
184
+ if not np.isclose((np.pi / self.spacing) % 1, 0):
185
+ raise ValueError("Spacing must divide evenly into pi radians.")
186
+
187
+ # build_spacial_bins creates the bin edges and centers for azimuth and elevation
188
+ # E.g. for spacing=1, az_bin_edges = [0, 1, 2, ..., 359, 360] deg.
189
+ # However returned values are in radians.
190
+ (
191
+ self.az_bin_edges,
192
+ self.el_bin_edges,
193
+ self.az_bin_midpoints,
194
+ self.el_bin_midpoints,
195
+ ) = build_spatial_bins(az_spacing_deg=spacing_deg, el_spacing_deg=spacing_deg)
196
+
197
+ # If desired, reverse the elevation range so that the grid is in the order
198
+ # defined by the Ultra prototype code (`build_dps_grid.m`).
199
+ if self.reversed_elevation:
200
+ self.el_bin_midpoints = self.el_bin_midpoints[::-1]
201
+ self.el_bin_edges = self.el_bin_edges[::-1]
202
+
203
+ # Deriving our az/el grids with indexing "ij" allows for ravel_multi_index
204
+ # to work correctly with 1D digitized indices in each az and el,
205
+ # using the same ravel order ('C' or 'F') as the grid points were unwrapped.
206
+ self.az_grid, self.el_grid = np.meshgrid(
207
+ self.az_bin_midpoints, self.el_bin_midpoints, indexing="ij"
208
+ )
209
+
210
+ # Keep track of number of points on the grid
211
+ self.grid_shape = self.az_grid.shape
212
+ self.grid_size = self.az_grid.size
213
+
214
+ def __repr__(self) -> str:
215
+ """
216
+ Return a string representation of the AzElSkyGrid.
217
+
218
+ Returns
219
+ -------
220
+ str
221
+ A string representation of the AzElSkyGrid.
222
+ """
223
+ return (
224
+ f"AzElSkyGrid with a spacing of {self.spacing:.4e} radians. "
225
+ f"{self.grid_shape} Grid."
226
+ )
@@ -1 +1,4 @@
1
1
  __version__ = "v001"
2
+
3
+ # Quality flag list length. Used in L1B and L2.
4
+ FLAG_LENGTH = 17
@@ -0,0 +1,52 @@
1
+ {
2
+ "description": "Settings for ground-processing pipeline for IMAP/GLOWS data",
3
+ "version": "0.1",
4
+ "date_of_creation_yyyymmdd": "20230527",
5
+ "filter_based_on_daily_statistical_error": {
6
+ "n_sigma_threshold_lower": 3.0,
7
+ "n_sigma_threshold_upper": 3.0
8
+ },
9
+ "filter_based_on_comparison_of_spin_periods": {
10
+ "relative_difference_threshold": 7.0e-5
11
+ },
12
+ "filter_based_on_temperature_std_dev": {
13
+ "std_dev_threshold__celsius_deg": 2.03
14
+ },
15
+ "filter_based_on_hv_voltage_std_dev": {
16
+ "std_dev_threshold__volt": 50.0
17
+ },
18
+ "filter_based_on_spin_period_std_dev": {
19
+ "std_dev_threshold__sec": 0.033333
20
+ },
21
+ "filter_based_on_pulse_length_std_dev": {
22
+ "std_dev_threshold__usec": 1.0
23
+ },
24
+ "filter_based_on_maps": {
25
+ "angular_distance_for_flaging__deg": 2.5
26
+ },
27
+ "active_bad_time_flags": {
28
+ "is_pps_missing": true,
29
+ "is_time_status_missing": true,
30
+ "is_phase_missing": true,
31
+ "is_spin_period_missing": true,
32
+ "is_overexposed": true,
33
+ "is_direct_event_non_monotonic": true,
34
+ "is_night": true,
35
+ "is_hv_test_in_progress": true,
36
+ "is_test_pulse_in_progress": true,
37
+ "is_memory_error_detected": true,
38
+ "is_generated_on_ground": true,
39
+ "is_beyond_daily_statistical_error": true,
40
+ "is_temperature_std_dev_beyond_threshold": true,
41
+ "is_hv_voltage_std_dev_beyond_threshold": true,
42
+ "is_spin_period_std_dev_beyond_threshold": true,
43
+ "is_pulse_length_std_dev_beyond_threshold": true,
44
+ "is_spin_period_difference_beyond_threshold": false
45
+ },
46
+ "active_bad_angle_flags": {
47
+ "is_close_to_uv_source": true,
48
+ "is_inside_excluded_region": true,
49
+ "is_excluded_by_instr_team": true,
50
+ "is_suspected_transient": true
51
+ }
52
+ }
@@ -2,15 +2,20 @@
2
2
 
3
3
  from collections import defaultdict
4
4
  from pathlib import Path
5
+ from typing import Optional
5
6
 
6
7
  import numpy as np
7
8
  import xarray as xr
8
9
 
9
10
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
10
11
  from imap_processing.glows.l0.decom_glows import decom_packets
11
- from imap_processing.glows.l0.glows_l0_data import DirectEventL0
12
+ from imap_processing.glows.l0.glows_l0_data import DirectEventL0, HistogramL0
12
13
  from imap_processing.glows.l1a.glows_l1a_data import DirectEventL1A, HistogramL1A
13
- from imap_processing.spice.time import J2000_EPOCH, met_to_j2000ns
14
+ from imap_processing.glows.l1b.glows_l1b_data import HistogramL1B
15
+ from imap_processing.spice.time import (
16
+ met_to_datetime64,
17
+ met_to_ttj2000ns,
18
+ )
14
19
 
15
20
 
16
21
  def create_glows_attr_obj(data_version: str) -> ImapCdfAttributes:
@@ -66,19 +71,25 @@ def glows_l1a(packet_filepath: Path, data_version: str) -> list[xr.Dataset]:
66
71
  # Create dictionaries to group data by day
67
72
  de_by_day = process_de_l0(de_l0)
68
73
  hists_by_day = defaultdict(list)
74
+ # Assume the observational day starts with the first packet, then find any new
75
+ # observation days.
76
+ # TODO: replace determine_observational_day with spin table API
77
+ obs_days = [hist_l0[0].SEC]
78
+ obs_days += determine_observational_day(hist_l0)
69
79
 
70
- # TODO: Make this its own function?
71
80
  for hist in hist_l0:
72
81
  hist_l1a = HistogramL1A(hist)
73
- # Split by IMAP start time
74
- # TODO: Should this be MET?
75
- hist_day = (J2000_EPOCH + met_to_j2000ns(hist.SEC)).astype("datetime64[D]")
82
+ # Determine the day the histogram belongs to. This finds the observation
83
+ # day in obs_day that is nearest the histogram timestamp without going over.
84
+ hist_day = next(
85
+ (day for day in reversed(obs_days) if day <= hist.SEC), obs_days[-1]
86
+ )
76
87
  hists_by_day[hist_day].append(hist_l1a)
77
88
 
78
89
  # Generate CDF files for each day
79
90
  output_datasets = []
80
- for hist_l1a_list in hists_by_day.values():
81
- dataset = generate_histogram_dataset(hist_l1a_list, glows_attrs)
91
+ for obs_day, hist_l1a_list in hists_by_day.items():
92
+ dataset = generate_histogram_dataset(hist_l1a_list, glows_attrs, obs_day)
82
93
  output_datasets.append(dataset)
83
94
 
84
95
  for de_l1a_list in de_by_day.values():
@@ -88,6 +99,36 @@ def glows_l1a(packet_filepath: Path, data_version: str) -> list[xr.Dataset]:
88
99
  return output_datasets
89
100
 
90
101
 
102
+ def determine_observational_day(hist_l0: list[HistogramL0]) -> list:
103
+ """
104
+ Find the timestamps for each observational day.
105
+
106
+ This function temporarily uses the is_night flag to determine the start of a new
107
+ observational day, but should eventually use the spin table APIs.
108
+
109
+ Parameters
110
+ ----------
111
+ hist_l0 : list[HistogramL0]
112
+ List of HistogramL0 objects.
113
+
114
+ Returns
115
+ -------
116
+ list
117
+ List of start times for each observational day.
118
+ """
119
+ prev_is_night = -1
120
+ obs_day_change = []
121
+ for hist in hist_l0:
122
+ flags = HistogramL1B.deserialize_flags(hist.FLAGS)
123
+ is_night: int = int(flags[6])
124
+ if prev_is_night and not is_night:
125
+ obs_day_change.append(hist.SEC)
126
+
127
+ prev_is_night = is_night
128
+
129
+ return obs_day_change
130
+
131
+
91
132
  def process_de_l0(
92
133
  de_l0: list[DirectEventL0],
93
134
  ) -> dict[np.datetime64, list[DirectEventL1A]]:
@@ -111,7 +152,7 @@ def process_de_l0(
111
152
  de_by_day = dict()
112
153
 
113
154
  for de in de_l0:
114
- de_day = (J2000_EPOCH + met_to_j2000ns(de.MET)).astype("datetime64[D]")
155
+ de_day = (met_to_datetime64(de.MET)).astype("datetime64[D]")
115
156
  if de_day not in de_by_day:
116
157
  de_by_day[de_day] = [DirectEventL1A(de)]
117
158
  # Putting not first data int o last direct event list.
@@ -186,7 +227,7 @@ def generate_de_dataset(
186
227
 
187
228
  for index, de in enumerate(de_l1a_list):
188
229
  # Set the timestamp to the first timestamp of the direct event list
189
- epoch_time = met_to_j2000ns(de.l0.MET).astype("datetime64[ns]")
230
+ epoch_time = met_to_ttj2000ns(de.l0.MET).astype("datetime64[ns]")
190
231
 
191
232
  # determine if the length of the direct_events numpy array is long enough,
192
233
  # and extend the direct_events length dimension if necessary.
@@ -297,7 +338,9 @@ def generate_de_dataset(
297
338
 
298
339
 
299
340
  def generate_histogram_dataset(
300
- hist_l1a_list: list[HistogramL1A], glows_cdf_attributes: ImapCdfAttributes
341
+ hist_l1a_list: list[HistogramL1A],
342
+ glows_cdf_attributes: ImapCdfAttributes,
343
+ obs_day: Optional[int] = None,
301
344
  ) -> xr.Dataset:
302
345
  """
303
346
  Generate a dataset for GLOWS L1A histogram data CDF files.
@@ -308,6 +351,9 @@ def generate_histogram_dataset(
308
351
  List of HistogramL1A objects for a given day.
309
352
  glows_cdf_attributes : ImapCdfAttributes
310
353
  Object containing l1a CDF attributes for instrument glows.
354
+ obs_day : int, optional
355
+ Observational day counter. If supplied, it will be included in the
356
+ output file name.
311
357
 
312
358
  Returns
313
359
  -------
@@ -315,7 +361,7 @@ def generate_histogram_dataset(
315
361
  Dataset containing the GLOWS L1A histogram CDF output.
316
362
  """
317
363
  # Store timestamps for each HistogramL1A object.
318
- time_data = np.zeros(len(hist_l1a_list), dtype="datetime64[ns]")
364
+ time_data = np.zeros(len(hist_l1a_list), dtype="int64")
319
365
  # TODO Add daily average of histogram counts
320
366
  # TODO compute average temperature etc
321
367
  # Data in lists, for each of the 25 time varying datapoints in HistogramL1A
@@ -352,7 +398,7 @@ def generate_histogram_dataset(
352
398
 
353
399
  for index, hist in enumerate(hist_l1a_list):
354
400
  # TODO: Should this be MET?
355
- epoch_time = met_to_j2000ns(hist.imap_start_time.to_seconds())
401
+ epoch_time = met_to_ttj2000ns(hist.imap_start_time.to_seconds())
356
402
  hist_data[index] = hist.histogram
357
403
 
358
404
  support_data["flags_set_onboard"].append(hist.flags["flags_set_onboard"])
@@ -384,6 +430,15 @@ def generate_histogram_dataset(
384
430
  attrs=glows_cdf_attributes.get_variable_attributes("bins_attrs"),
385
431
  )
386
432
 
433
+ bin_label = xr.DataArray(
434
+ bins.data.astype(str),
435
+ name="bins_label",
436
+ dims=["bins_label"],
437
+ attrs=glows_cdf_attributes.get_variable_attributes(
438
+ "bins_label", check_schema=False
439
+ ),
440
+ )
441
+
387
442
  hist = xr.DataArray(
388
443
  hist_data,
389
444
  name="histogram",
@@ -395,9 +450,12 @@ def generate_histogram_dataset(
395
450
  )
396
451
 
397
452
  attrs = glows_cdf_attributes.get_global_attributes("imap_glows_l1a_hist")
453
+ if obs_day:
454
+ # this needs to be 5 digits, so truncate it from the temporary obs day
455
+ attrs["Repointing"] = int(str(obs_day)[-5:])
398
456
 
399
457
  output = xr.Dataset(
400
- coords={"epoch": epoch_time, "bins": bins},
458
+ coords={"epoch": epoch_time, "bins": bins, "bins_label": bin_label},
401
459
  attrs=attrs,
402
460
  )
403
461
 
@@ -6,6 +6,7 @@ import numpy as np
6
6
  import xarray as xr
7
7
 
8
8
  from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
9
+ from imap_processing.glows import FLAG_LENGTH
9
10
  from imap_processing.glows.l1b.glows_l1b_data import DirectEventL1B, HistogramL1B
10
11
 
11
12
 
@@ -45,7 +46,7 @@ def glows_l1b(input_dataset: xr.Dataset, data_version: str) -> xr.Dataset:
45
46
 
46
47
  if "hist" in logical_source:
47
48
  flag_data = xr.DataArray(
48
- np.arange(17),
49
+ np.arange(FLAG_LENGTH),
49
50
  name="flag_dim",
50
51
  dims=["flag_dim"],
51
52
  attrs=cdf_attrs.get_variable_attributes("flag_dim"),
@@ -9,6 +9,7 @@ from typing import Optional
9
9
 
10
10
  import numpy as np
11
11
 
12
+ from imap_processing.glows import FLAG_LENGTH
12
13
  from imap_processing.glows.utils.constants import TimeTuple
13
14
 
14
15
 
@@ -595,7 +596,7 @@ class HistogramL1B:
595
596
  # self.unique_block_identifier = np.datetime_as_string(
596
597
  # np.datetime64(int(self.imap_start_time), "ns"), "s"
597
598
  # )
598
- self.flags = np.ones((17,), dtype=np.uint8)
599
+ self.flags = np.ones((FLAG_LENGTH,), dtype=np.uint8)
599
600
 
600
601
  def output_data(self) -> tuple:
601
602
  """
@@ -610,3 +611,26 @@ class HistogramL1B:
610
611
  A tuple containing each attribute value in the class.
611
612
  """
612
613
  return tuple(getattr(self, out.name) for out in dataclasses.fields(self))
614
+
615
+ @staticmethod
616
+ def deserialize_flags(raw: int) -> np.ndarray[int]:
617
+ """
618
+ Deserialize the flags into a list.
619
+
620
+ Parameters
621
+ ----------
622
+ raw : int
623
+ 16 bit integer containing the on-board flags to deserialize.
624
+
625
+ Returns
626
+ -------
627
+ flags : np.ndarray
628
+ Array of flags as a boolean.
629
+ """
630
+ # there are only 10 flags in the on-board flag array, additional flags are added
631
+ # later.
632
+ flags: np.ndarray[bool] = np.array(
633
+ [bool((raw >> i) & 1) for i in range(10)], dtype=bool
634
+ )
635
+
636
+ return flags