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
@@ -1,125 +0,0 @@
1
- """Test creation of solid angle map."""
2
-
3
- import numpy as np
4
- import numpy.testing as npt
5
- import pytest
6
-
7
- from imap_processing.ultra.utils import spatial_utils
8
-
9
- # Parameterize with spacings (degrees here):
10
- valid_spacings = [0.1, 0.25, 0.5, 1, 5, 10, 20]
11
- invalid_spacings = [0, -1, 11]
12
- invalid_spacings_match_str = [
13
- "Spacing must be positive valued, non-zero.",
14
- "Spacing must be positive valued, non-zero.",
15
- "Spacing must divide evenly into pi radians.",
16
- ]
17
-
18
-
19
- def test_build_spatial_bins():
20
- """Tests build_spatial_bins function."""
21
- az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = (
22
- spatial_utils.build_spatial_bins()
23
- )
24
-
25
- assert az_bin_edges[0] == 0
26
- assert az_bin_edges[-1] == 360
27
- assert len(az_bin_edges) == 721
28
-
29
- assert el_bin_edges[0] == -90
30
- assert el_bin_edges[-1] == 90
31
- assert len(el_bin_edges) == 361
32
-
33
- assert len(az_bin_midpoints) == 720
34
- np.testing.assert_allclose(az_bin_midpoints[0], 0.25, atol=1e-4)
35
- np.testing.assert_allclose(az_bin_midpoints[-1], 359.75, atol=1e-4)
36
-
37
- assert len(el_bin_midpoints) == 360
38
- np.testing.assert_allclose(el_bin_midpoints[0], -89.75, atol=1e-4)
39
- np.testing.assert_allclose(el_bin_midpoints[-1], 89.75, atol=1e-4)
40
-
41
-
42
- @pytest.mark.parametrize("spacing", valid_spacings)
43
- def test_build_solid_angle_map(spacing):
44
- """Test build_solid_angle_map function."""
45
- solid_angle_map_steradians = spatial_utils.build_solid_angle_map(
46
- spacing, input_degrees=True, output_degrees=False
47
- )
48
- assert np.isclose(np.sum(solid_angle_map_steradians), 4 * np.pi, atol=0, rtol=1e-9)
49
-
50
- solid_angle_map_sqdeg = spatial_utils.build_solid_angle_map(
51
- np.deg2rad(spacing), input_degrees=False, output_degrees=True
52
- )
53
- assert np.isclose(
54
- np.sum(solid_angle_map_sqdeg), 4 * np.pi * (180 / np.pi) ** 2, atol=0, rtol=1e-9
55
- )
56
-
57
-
58
- @pytest.mark.parametrize(
59
- "spacing, match_str", zip(invalid_spacings, invalid_spacings_match_str)
60
- )
61
- def test_build_solid_angle_map_invalid_spacing(spacing, match_str):
62
- """Test build_solid_angle_map function raises error for invalid spacing."""
63
- with pytest.raises(ValueError, match=match_str):
64
- _ = spatial_utils.build_solid_angle_map(
65
- spacing, input_degrees=True, output_degrees=False
66
- )
67
-
68
-
69
- @pytest.mark.parametrize("spacing", valid_spacings)
70
- def test_build_az_el_grid(spacing):
71
- """Test build_az_el_grid function."""
72
- az_range, el_range, az_grid, el_grid = spatial_utils.build_az_el_grid(
73
- spacing=spacing,
74
- input_degrees=True,
75
- output_degrees=True,
76
- centered_azimuth=False,
77
- centered_elevation=True,
78
- )
79
-
80
- # Size checks
81
- assert az_range.size == int(360 / spacing)
82
- assert el_range.size == int(180 / spacing)
83
- assert az_range.size == az_grid.shape[1]
84
- assert el_range.size == el_grid.shape[0]
85
-
86
- # Check grid values
87
- expected_az_range = np.arange((spacing / 2), 360 + (spacing / 2), spacing)
88
- expected_el_range = np.arange(-90 + (spacing / 2), 90 + (spacing / 2), spacing)[
89
- ::-1
90
- ] # Note el order is reversed
91
-
92
- npt.assert_allclose(az_range, expected_az_range, atol=1e-12)
93
- npt.assert_allclose(el_range, expected_el_range, atol=1e-12)
94
-
95
-
96
- def test_rewrap_even_spaced_el_az_grid_1d():
97
- """Test rewrap_even_spaced_el_az_grid function, without extra axis."""
98
- orig_shape = (180 * 12, 360 * 12)
99
- orig_grid = np.fromfunction(lambda i, j: i**2 + j, orig_shape, dtype=int)
100
- raveled_values = orig_grid.ravel(order="F")
101
- rewrapped_grid_infer_shape = spatial_utils.rewrap_even_spaced_el_az_grid(
102
- raveled_values
103
- )
104
- rewrapped_grid_known_shape = spatial_utils.rewrap_even_spaced_el_az_grid(
105
- raveled_values, shape=orig_shape
106
- )
107
-
108
- assert np.array_equal(rewrapped_grid_infer_shape, orig_grid)
109
- assert np.array_equal(rewrapped_grid_known_shape, orig_grid)
110
-
111
-
112
- def test_rewrap_even_spaced_el_az_grid_2d():
113
- """Test rewrap_even_spaced_el_az_grid function, with extra axis."""
114
- orig_shape = (180 * 12, 360 * 12, 5)
115
- orig_grid = np.fromfunction(lambda i, j, k: i**2 + j + k, orig_shape, dtype=int)
116
- raveled_values = orig_grid.reshape(-1, 5, order="F")
117
- rewrapped_grid_infer_shape = spatial_utils.rewrap_even_spaced_el_az_grid(
118
- raveled_values, extra_axis=True
119
- )
120
- rewrapped_grid_known_shape = spatial_utils.rewrap_even_spaced_el_az_grid(
121
- raveled_values, shape=orig_shape, extra_axis=True
122
- )
123
- assert raveled_values.shape == (180 * 12 * 360 * 12, 5)
124
- assert np.array_equal(rewrapped_grid_infer_shape, orig_grid)
125
- assert np.array_equal(rewrapped_grid_known_shape, orig_grid)
@@ -1,221 +0,0 @@
1
- """IMAP Ultra utils for spatial binning and grid creation."""
2
-
3
- import typing
4
-
5
- import numpy as np
6
- from numpy.typing import NDArray
7
-
8
-
9
- def build_spatial_bins(
10
- az_spacing: float = 0.5,
11
- el_spacing: float = 0.5,
12
- ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
13
- """
14
- Build spatial bin boundaries for azimuth and elevation.
15
-
16
- Parameters
17
- ----------
18
- az_spacing : float, optional
19
- The azimuth bin spacing in degrees (default is 0.5 degrees).
20
- el_spacing : float, optional
21
- The elevation bin spacing in degrees (default is 0.5 degrees).
22
-
23
- Returns
24
- -------
25
- az_bin_edges : np.ndarray
26
- Array of azimuth bin boundary values.
27
- el_bin_edges : np.ndarray
28
- Array of elevation bin boundary values.
29
- az_bin_midpoints : np.ndarray
30
- Array of azimuth bin midpoint values.
31
- el_bin_midpoints : np.ndarray
32
- Array of elevation bin midpoint values.
33
- """
34
- # Azimuth bins from 0 to 360 degrees.
35
- az_bin_edges = np.arange(0, 360 + az_spacing, az_spacing)
36
- az_bin_midpoints = az_bin_edges[:-1] + az_spacing / 2 # Midpoints between edges
37
-
38
- # Elevation bins from -90 to 90 degrees.
39
- el_bin_edges = np.arange(-90, 90 + el_spacing, el_spacing)
40
- el_bin_midpoints = el_bin_edges[:-1] + el_spacing / 2 # Midpoints between edges
41
-
42
- return az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints
43
-
44
-
45
- def build_solid_angle_map(
46
- spacing: float, input_degrees: bool = True, output_degrees: bool = False
47
- ) -> NDArray:
48
- """
49
- Build a solid angle map for a given spacing in degrees.
50
-
51
- Parameters
52
- ----------
53
- spacing : float
54
- The bin spacing in the specified units.
55
- input_degrees : bool, optional
56
- If True, the input spacing is in degrees
57
- (default is True for radians).
58
- output_degrees : bool, optional
59
- If True, the output solid angle map is in square degrees
60
- (default is False for steradians).
61
-
62
- Returns
63
- -------
64
- solid_angle_grid : np.ndarray
65
- The solid angle map grid in steradians (default) or square degrees.
66
- First index is latitude/el, second index is longitude/az.
67
- """
68
- if input_degrees:
69
- spacing = np.deg2rad(spacing)
70
-
71
- if spacing <= 0:
72
- raise ValueError("Spacing must be positive valued, non-zero.")
73
-
74
- if not np.isclose((np.pi / spacing) % 1, 0):
75
- raise ValueError("Spacing must divide evenly into pi radians.")
76
-
77
- latitudes = np.arange(-np.pi / 2, np.pi / 2 + spacing, step=spacing)
78
- sine_latitudes = np.sin(latitudes)
79
- delta_sine_latitudes = np.diff(sine_latitudes)
80
- solid_angle_by_latitude = np.abs(spacing * delta_sine_latitudes)
81
-
82
- solid_angle_grid = np.repeat(
83
- solid_angle_by_latitude[:, np.newaxis], (2 * np.pi) / spacing, axis=1
84
- )
85
-
86
- if output_degrees:
87
- solid_angle_grid *= (180 / np.pi) ** 2
88
-
89
- return solid_angle_grid
90
-
91
-
92
- def build_az_el_grid(
93
- spacing: float,
94
- input_degrees: bool = False,
95
- output_degrees: bool = False,
96
- centered_azimuth: bool = False,
97
- centered_elevation: bool = True,
98
- ) -> tuple[NDArray, NDArray, NDArray, NDArray]:
99
- """
100
- Build a 2D grid of azimuth and elevation angles.
101
-
102
- Azimuth and Elevation values represent the center of each grid cell,
103
- so the grid is offset by half the spacing.
104
-
105
- Parameters
106
- ----------
107
- spacing : float
108
- Spacing of the grid in degrees if `input_degrees` is True, else radians.
109
- input_degrees : bool, optional
110
- Whether the spacing is specified in degrees and must be converted to radians,
111
- by default False (indicating radians).
112
- output_degrees : bool, optional
113
- Whether the output azimuth and elevation angles should be in degrees,
114
- by default False (indicating radians).
115
- centered_azimuth : bool, optional
116
- Whether the azimuth grid should be centered around 0 degrees/0 radians,
117
- i.e. from -pi to pi radians, by default False, indicating 0 to 2pi radians.
118
- If True, the azimuth grid will be from -pi to pi radians.
119
- centered_elevation : bool, optional
120
- Whether the elevation grid should be centered around 0 degrees/0 radians,
121
- i.e. from -pi/2 to pi/2 radians, by default True.
122
- If False, the elevation grid will be from 0 to pi radians.
123
-
124
- Returns
125
- -------
126
- tuple[NDArray, NDArray, NDArray, NDArray]
127
- - The evenly spaced, 1D range of azimuth angles
128
- e.g.(0, 0.5, 1, ..., 359.5) deg.
129
- - The evenly spaced, 1D range of elevation angles
130
- e.g.(-90, -89.5, ..., 89.5) deg.
131
- - The 2D grid of azimuth angles (azimuths for each elevation).
132
- This grid will be constant along the elevation (0th) axis.
133
- - The 2D grid of elevation angles (elevations for each azimuth).
134
- This grid will be constant along the azimuth (1st) axis.
135
-
136
- Raises
137
- ------
138
- ValueError
139
- If the spacing is not positive or does not divide evenly into pi radians.
140
- """
141
- if input_degrees:
142
- spacing = np.deg2rad(spacing)
143
-
144
- if spacing <= 0:
145
- raise ValueError("Spacing must be positive valued, non-zero.")
146
-
147
- if not np.isclose((np.pi / spacing) % 1, 0):
148
- raise ValueError("Spacing must divide evenly into pi radians.")
149
-
150
- el_range = np.arange(spacing / 2, np.pi, spacing)
151
- az_range = np.arange(spacing / 2, 2 * np.pi, spacing)
152
- if centered_azimuth:
153
- az_range = az_range - np.pi
154
- if centered_elevation:
155
- el_range = el_range - np.pi / 2
156
-
157
- # Reverse the elevation range so that the grid is in the order
158
- # defined by the Ultra prototype code (`build_dps_grid.m`).
159
- el_range = el_range[::-1]
160
-
161
- az_grid, el_grid = np.meshgrid(az_range, el_range)
162
-
163
- if output_degrees:
164
- az_range = np.rad2deg(az_range)
165
- el_range = np.rad2deg(el_range)
166
- az_grid = np.rad2deg(az_grid)
167
- el_grid = np.rad2deg(el_grid)
168
-
169
- return az_range, el_range, az_grid, el_grid
170
-
171
-
172
- @typing.no_type_check
173
- def rewrap_even_spaced_el_az_grid(
174
- raveled_values: NDArray,
175
- shape: typing.Optional[tuple[int]] = None,
176
- extra_axis: bool = False,
177
- ) -> NDArray:
178
- """
179
- Take an unwrapped (raveled) 1D array and reshapes it into a 2D el/az grid.
180
-
181
- Assumes the following must be true of the original grid:
182
- 1. Grid was evenly spaced in angular space,
183
- 2. Grid had the same spacing in both azimuth and elevation.
184
- 3. Elevation is the 0th axis (and extends a total of 180 degrees),
185
- 4. Azimuth is the 1st axis (and extends a total of 360 degrees).
186
- 5. The grid was raveled in Fortran (F) order.
187
-
188
- Parameters
189
- ----------
190
- raveled_values : NDArray
191
- 1D array of values to be reshaped into a 2D grid.
192
- shape : tuple[int], optional
193
- The shape of the original grid, if known, by default None.
194
- If None, the shape will be inferred from the size of the input array.
195
- extra_axis : bool, optional
196
- If True, input is a 2D array with latter axis being 'extra', non-spatial axis.
197
- This axis (e.g. energy bins) will be preserved in the reshaped grid.
198
-
199
- Returns
200
- -------
201
- NDArray
202
- The reshaped 2D grid of values.
203
-
204
- Raises
205
- ------
206
- ValueError
207
- If the input is not a 1D array or 2D array with an extra axis.
208
- """
209
- if raveled_values.ndim not in (1, 2) or (
210
- raveled_values.ndim == 2 and not extra_axis
211
- ):
212
- raise ValueError("Input must be a 1D array or 2D array with extra axis.")
213
-
214
- # We can infer the shape if its evenly spaced and 2D
215
- if not shape:
216
- spacing_deg = 1 / np.sqrt(raveled_values.shape[0] / (360 * 180))
217
- shape = (int(180 // spacing_deg), int(360 // spacing_deg))
218
-
219
- if extra_axis:
220
- shape = (shape[0], shape[1], raveled_values.shape[1])
221
- return raveled_values.reshape(shape, order="F")