imap-processing 0.12.0__py3-none-any.whl → 0.13.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (272) hide show
  1. imap_processing/__init__.py +1 -0
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/ccsds_data.py +1 -2
  4. imap_processing/ccsds/excel_to_xtce.py +1 -2
  5. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +18 -12
  6. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +569 -0
  7. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
  8. imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +5 -5
  9. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +20 -1
  10. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +6 -4
  11. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +3 -3
  12. imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +15 -0
  13. imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +22 -0
  14. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +16 -0
  15. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +178 -5
  16. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
  17. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -19
  18. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +8 -48
  19. imap_processing/cdf/utils.py +41 -33
  20. imap_processing/cli.py +463 -234
  21. imap_processing/codice/codice_l1a.py +260 -47
  22. imap_processing/codice/codice_l1b.py +51 -152
  23. imap_processing/codice/constants.py +38 -1
  24. imap_processing/ena_maps/ena_maps.py +658 -65
  25. imap_processing/ena_maps/utils/coordinates.py +1 -1
  26. imap_processing/ena_maps/utils/spatial_utils.py +10 -5
  27. imap_processing/glows/l1a/glows_l1a.py +28 -99
  28. imap_processing/glows/l1a/glows_l1a_data.py +2 -2
  29. imap_processing/glows/l1b/glows_l1b.py +1 -4
  30. imap_processing/glows/l1b/glows_l1b_data.py +1 -3
  31. imap_processing/glows/l2/glows_l2.py +2 -5
  32. imap_processing/hi/l1a/hi_l1a.py +31 -12
  33. imap_processing/hi/l1b/hi_l1b.py +80 -43
  34. imap_processing/hi/l1c/hi_l1c.py +12 -16
  35. imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
  36. imap_processing/hit/hit_utils.py +93 -35
  37. imap_processing/hit/l0/decom_hit.py +3 -1
  38. imap_processing/hit/l1a/hit_l1a.py +30 -25
  39. imap_processing/hit/l1b/constants.py +6 -2
  40. imap_processing/hit/l1b/hit_l1b.py +279 -318
  41. imap_processing/hit/l2/constants.py +37 -0
  42. imap_processing/hit/l2/hit_l2.py +373 -264
  43. imap_processing/ialirt/l0/parse_mag.py +138 -10
  44. imap_processing/ialirt/l0/process_swapi.py +69 -0
  45. imap_processing/ialirt/l0/process_swe.py +318 -22
  46. imap_processing/ialirt/packet_definitions/ialirt.xml +216 -212
  47. imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +1 -1
  48. imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +1 -1
  49. imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +14 -14
  50. imap_processing/ialirt/utils/grouping.py +1 -1
  51. imap_processing/idex/idex_constants.py +9 -1
  52. imap_processing/idex/idex_l0.py +22 -8
  53. imap_processing/idex/idex_l1a.py +75 -44
  54. imap_processing/idex/idex_l1b.py +9 -8
  55. imap_processing/idex/idex_l2a.py +79 -45
  56. imap_processing/idex/idex_l2b.py +120 -0
  57. imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +33 -39
  58. imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
  59. imap_processing/lo/l0/lo_science.py +1 -2
  60. imap_processing/lo/l1a/lo_l1a.py +1 -4
  61. imap_processing/lo/l1b/lo_l1b.py +527 -6
  62. imap_processing/lo/l1b/tof_conversions.py +11 -0
  63. imap_processing/lo/l1c/lo_l1c.py +1 -4
  64. imap_processing/mag/constants.py +43 -0
  65. imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
  66. imap_processing/mag/l1a/mag_l1a.py +2 -9
  67. imap_processing/mag/l1a/mag_l1a_data.py +10 -10
  68. imap_processing/mag/l1b/mag_l1b.py +84 -17
  69. imap_processing/mag/l1c/interpolation_methods.py +180 -3
  70. imap_processing/mag/l1c/mag_l1c.py +236 -70
  71. imap_processing/mag/l2/mag_l2.py +140 -0
  72. imap_processing/mag/l2/mag_l2_data.py +288 -0
  73. imap_processing/spacecraft/quaternions.py +1 -3
  74. imap_processing/spice/geometry.py +3 -3
  75. imap_processing/spice/kernels.py +0 -276
  76. imap_processing/spice/pointing_frame.py +257 -0
  77. imap_processing/spice/repoint.py +48 -19
  78. imap_processing/spice/spin.py +38 -33
  79. imap_processing/spice/time.py +24 -0
  80. imap_processing/swapi/l1/swapi_l1.py +16 -12
  81. imap_processing/swapi/l2/swapi_l2.py +116 -4
  82. imap_processing/swapi/swapi_utils.py +32 -0
  83. imap_processing/swe/l1a/swe_l1a.py +2 -9
  84. imap_processing/swe/l1a/swe_science.py +8 -11
  85. imap_processing/swe/l1b/swe_l1b.py +898 -23
  86. imap_processing/swe/l2/swe_l2.py +21 -77
  87. imap_processing/swe/utils/swe_constants.py +1 -0
  88. imap_processing/tests/ccsds/test_excel_to_xtce.py +1 -1
  89. imap_processing/tests/cdf/test_utils.py +14 -16
  90. imap_processing/tests/codice/conftest.py +44 -33
  91. imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
  92. imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
  93. imap_processing/tests/codice/test_codice_l1a.py +20 -11
  94. imap_processing/tests/codice/test_codice_l1b.py +6 -7
  95. imap_processing/tests/conftest.py +78 -22
  96. imap_processing/tests/ena_maps/test_ena_maps.py +462 -33
  97. imap_processing/tests/ena_maps/test_spatial_utils.py +1 -1
  98. imap_processing/tests/glows/conftest.py +10 -14
  99. imap_processing/tests/glows/test_glows_decom.py +4 -4
  100. imap_processing/tests/glows/test_glows_l1a_cdf.py +6 -27
  101. imap_processing/tests/glows/test_glows_l1a_data.py +6 -8
  102. imap_processing/tests/glows/test_glows_l1b.py +11 -11
  103. imap_processing/tests/glows/test_glows_l1b_data.py +5 -5
  104. imap_processing/tests/glows/test_glows_l2.py +2 -8
  105. imap_processing/tests/hi/conftest.py +1 -1
  106. imap_processing/tests/hi/test_hi_l1b.py +10 -12
  107. imap_processing/tests/hi/test_hi_l1c.py +27 -24
  108. imap_processing/tests/hi/test_l1a.py +7 -9
  109. imap_processing/tests/hi/test_science_direct_event.py +2 -2
  110. imap_processing/tests/hit/helpers/l1_validation.py +44 -43
  111. imap_processing/tests/hit/test_decom_hit.py +1 -1
  112. imap_processing/tests/hit/test_hit_l1a.py +9 -9
  113. imap_processing/tests/hit/test_hit_l1b.py +172 -217
  114. imap_processing/tests/hit/test_hit_l2.py +380 -118
  115. imap_processing/tests/hit/test_hit_utils.py +122 -55
  116. imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
  117. imap_processing/tests/hit/validation_data/sci_sample_raw.csv +1 -1
  118. imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
  119. imap_processing/tests/ialirt/unit/test_grouping.py +2 -2
  120. imap_processing/tests/ialirt/unit/test_parse_mag.py +71 -16
  121. imap_processing/tests/ialirt/unit/test_process_codicehi.py +3 -3
  122. imap_processing/tests/ialirt/unit/test_process_codicelo.py +3 -10
  123. imap_processing/tests/ialirt/unit/test_process_ephemeris.py +4 -4
  124. imap_processing/tests/ialirt/unit/test_process_hit.py +3 -3
  125. imap_processing/tests/ialirt/unit/test_process_swapi.py +24 -16
  126. imap_processing/tests/ialirt/unit/test_process_swe.py +115 -7
  127. imap_processing/tests/idex/conftest.py +72 -7
  128. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20241206_v001.pkts +0 -0
  129. imap_processing/tests/idex/test_data/imap_idex_l0_raw_20250108_v001.pkts +0 -0
  130. imap_processing/tests/idex/test_idex_l0.py +33 -11
  131. imap_processing/tests/idex/test_idex_l1a.py +50 -23
  132. imap_processing/tests/idex/test_idex_l1b.py +104 -25
  133. imap_processing/tests/idex/test_idex_l2a.py +48 -32
  134. imap_processing/tests/idex/test_idex_l2b.py +93 -0
  135. imap_processing/tests/lo/test_lo_l1a.py +3 -3
  136. imap_processing/tests/lo/test_lo_l1b.py +371 -6
  137. imap_processing/tests/lo/test_lo_l1c.py +1 -1
  138. imap_processing/tests/lo/test_lo_science.py +6 -7
  139. imap_processing/tests/lo/test_star_sensor.py +1 -1
  140. imap_processing/tests/mag/conftest.py +58 -9
  141. imap_processing/tests/mag/test_mag_decom.py +4 -3
  142. imap_processing/tests/mag/test_mag_l1a.py +13 -7
  143. imap_processing/tests/mag/test_mag_l1b.py +9 -9
  144. imap_processing/tests/mag/test_mag_l1c.py +151 -47
  145. imap_processing/tests/mag/test_mag_l2.py +130 -0
  146. imap_processing/tests/mag/test_mag_validation.py +144 -7
  147. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-in.csv +1217 -0
  148. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-out.csv +1857 -0
  149. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-in.csv +1217 -0
  150. imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-out.csv +1857 -0
  151. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-in.csv +1217 -0
  152. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-out.csv +1793 -0
  153. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-in.csv +1217 -0
  154. imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-out.csv +1793 -0
  155. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-burst-in.csv +2561 -0
  156. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-in.csv +961 -0
  157. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-out.csv +1539 -0
  158. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-in.csv +1921 -0
  159. imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-out.csv +2499 -0
  160. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-in.csv +865 -0
  161. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-out.csv +1196 -0
  162. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-in.csv +1729 -0
  163. imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-out.csv +3053 -0
  164. imap_processing/tests/mag/validation/L2/imap_mag_l1b_norm-mago_20251017_v002.cdf +0 -0
  165. imap_processing/tests/mag/validation/calibration/imap_mag_l2-calibration-matrices_20251017_v004.cdf +0 -0
  166. imap_processing/tests/mag/validation/calibration/imap_mag_l2-offsets-norm_20251017_20251017_v001.cdf +0 -0
  167. imap_processing/tests/spacecraft/test_quaternions.py +1 -1
  168. imap_processing/tests/spice/test_data/fake_repoint_data.csv +4 -4
  169. imap_processing/tests/spice/test_data/fake_spin_data.csv +11 -11
  170. imap_processing/tests/spice/test_geometry.py +3 -3
  171. imap_processing/tests/spice/test_kernels.py +1 -200
  172. imap_processing/tests/spice/test_pointing_frame.py +185 -0
  173. imap_processing/tests/spice/test_repoint.py +20 -10
  174. imap_processing/tests/spice/test_spin.py +50 -9
  175. imap_processing/tests/spice/test_time.py +14 -0
  176. imap_processing/tests/swapi/lut/imap_swapi_esa-unit-conversion_20250211_v000.csv +73 -0
  177. imap_processing/tests/swapi/lut/imap_swapi_lut-notes_20250211_v000.csv +1025 -0
  178. imap_processing/tests/swapi/test_swapi_l1.py +7 -9
  179. imap_processing/tests/swapi/test_swapi_l2.py +180 -8
  180. imap_processing/tests/swe/lut/checker-board-indices.csv +24 -0
  181. imap_processing/tests/swe/lut/imap_swe_esa-lut_20250301_v000.csv +385 -0
  182. imap_processing/tests/swe/lut/imap_swe_l1b-in-flight-cal_20240510_20260716_v000.csv +3 -0
  183. imap_processing/tests/swe/test_swe_l1a.py +6 -6
  184. imap_processing/tests/swe/test_swe_l1a_science.py +3 -3
  185. imap_processing/tests/swe/test_swe_l1b.py +162 -24
  186. imap_processing/tests/swe/test_swe_l2.py +82 -102
  187. imap_processing/tests/test_cli.py +171 -88
  188. imap_processing/tests/test_utils.py +2 -1
  189. imap_processing/tests/ultra/data/mock_data.py +49 -21
  190. imap_processing/tests/ultra/unit/conftest.py +53 -70
  191. imap_processing/tests/ultra/unit/test_badtimes.py +2 -4
  192. imap_processing/tests/ultra/unit/test_cullingmask.py +4 -6
  193. imap_processing/tests/ultra/unit/test_de.py +3 -10
  194. imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
  195. imap_processing/tests/ultra/unit/test_decom_apid_881.py +15 -16
  196. imap_processing/tests/ultra/unit/test_decom_apid_883.py +12 -10
  197. imap_processing/tests/ultra/unit/test_decom_apid_896.py +202 -55
  198. imap_processing/tests/ultra/unit/test_lookup_utils.py +23 -1
  199. imap_processing/tests/ultra/unit/test_spacecraft_pset.py +3 -4
  200. imap_processing/tests/ultra/unit/test_ultra_l1a.py +84 -307
  201. imap_processing/tests/ultra/unit/test_ultra_l1b.py +30 -12
  202. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
  203. imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +4 -1
  204. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +163 -29
  205. imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
  206. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +32 -43
  207. imap_processing/tests/ultra/unit/test_ultra_l2.py +230 -0
  208. imap_processing/ultra/constants.py +1 -1
  209. imap_processing/ultra/l0/decom_tools.py +21 -34
  210. imap_processing/ultra/l0/decom_ultra.py +168 -204
  211. imap_processing/ultra/l0/ultra_utils.py +152 -136
  212. imap_processing/ultra/l1a/ultra_l1a.py +55 -243
  213. imap_processing/ultra/l1b/badtimes.py +1 -4
  214. imap_processing/ultra/l1b/cullingmask.py +2 -6
  215. imap_processing/ultra/l1b/de.py +62 -47
  216. imap_processing/ultra/l1b/extendedspin.py +8 -4
  217. imap_processing/ultra/l1b/lookup_utils.py +72 -9
  218. imap_processing/ultra/l1b/ultra_l1b.py +3 -8
  219. imap_processing/ultra/l1b/ultra_l1b_culling.py +4 -4
  220. imap_processing/ultra/l1b/ultra_l1b_extended.py +236 -78
  221. imap_processing/ultra/l1c/histogram.py +2 -6
  222. imap_processing/ultra/l1c/spacecraft_pset.py +2 -4
  223. imap_processing/ultra/l1c/ultra_l1c.py +1 -5
  224. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +107 -60
  225. imap_processing/ultra/l2/ultra_l2.py +299 -0
  226. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +526 -0
  227. imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +526 -0
  228. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +526 -0
  229. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +526 -0
  230. imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -2
  231. imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -0
  232. imap_processing/ultra/packet_definitions/README.md +38 -0
  233. imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +15302 -482
  234. imap_processing/ultra/utils/ultra_l1_utils.py +13 -12
  235. imap_processing/utils.py +1 -1
  236. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +3 -2
  237. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/RECORD +264 -225
  238. imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
  239. imap_processing/mag/imap_mag_sdc-configuration_v001.yaml +0 -6
  240. imap_processing/mag/l1b/__init__.py +0 -0
  241. imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
  242. imap_processing/swe/l1b/swe_l1b_science.py +0 -699
  243. imap_processing/tests/swe/test_swe_l1b_science.py +0 -103
  244. imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
  245. imap_processing/ultra/lookup_tables/ultra_90_dps_exposure_compressed.cdf +0 -0
  246. /imap_processing/idex/packet_definitions/{idex_packet_definition.xml → idex_science_packet_definition.xml} +0 -0
  247. /imap_processing/tests/ialirt/{test_data → data}/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
  248. /imap_processing/tests/ialirt/{test_data → data}/l0/461971383-404.bin +0 -0
  249. /imap_processing/tests/ialirt/{test_data → data}/l0/461971384-405.bin +0 -0
  250. /imap_processing/tests/ialirt/{test_data → data}/l0/461971385-406.bin +0 -0
  251. /imap_processing/tests/ialirt/{test_data → data}/l0/461971386-407.bin +0 -0
  252. /imap_processing/tests/ialirt/{test_data → data}/l0/461971387-408.bin +0 -0
  253. /imap_processing/tests/ialirt/{test_data → data}/l0/461971388-409.bin +0 -0
  254. /imap_processing/tests/ialirt/{test_data → data}/l0/461971389-410.bin +0 -0
  255. /imap_processing/tests/ialirt/{test_data → data}/l0/461971390-411.bin +0 -0
  256. /imap_processing/tests/ialirt/{test_data → data}/l0/461971391-412.bin +0 -0
  257. /imap_processing/tests/ialirt/{test_data → data}/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
  258. /imap_processing/tests/ialirt/{test_data → data}/l0/IALiRT Raw Packet Telemetry.txt +0 -0
  259. /imap_processing/tests/ialirt/{test_data → data}/l0/apid01152.tlm +0 -0
  260. /imap_processing/tests/ialirt/{test_data → data}/l0/eu_SWP_IAL_20240826_152033.csv +0 -0
  261. /imap_processing/tests/ialirt/{test_data → data}/l0/hi_fsw_view_1_ccsds.bin +0 -0
  262. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.ccsds +0 -0
  263. /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.csv +0 -0
  264. /imap_processing/tests/ialirt/{test_data → data}/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +0 -0
  265. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
  266. /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
  267. /imap_processing/tests/ialirt/{test_data → data}/l0/sample_decoded_i-alirt_data.csv +0 -0
  268. /imap_processing/tests/mag/validation/{imap_calibration_mag_20240229_v01.cdf → calibration/imap_mag_l1b-calibration_20240229_v001.cdf} +0 -0
  269. /imap_processing/{swe/l1b/engineering_unit_convert_table.csv → tests/swe/lut/imap_swe_eu-conversion_20240510_v000.csv} +0 -0
  270. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/LICENSE +0 -0
  271. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
  272. {imap_processing-0.12.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
@@ -6,7 +6,7 @@ from imap_processing.ultra.l1c.histogram import calculate_histogram
6
6
  from imap_processing.ultra.l1c.spacecraft_pset import calculate_spacecraft_pset
7
7
 
8
8
 
9
- def ultra_l1c(data_dict: dict, data_version: str) -> list[xr.Dataset]:
9
+ def ultra_l1c(data_dict: dict) -> list[xr.Dataset]:
10
10
  """
11
11
  Will process ULTRA L1A and L1B data into L1C CDF files at output_filepath.
12
12
 
@@ -14,8 +14,6 @@ def ultra_l1c(data_dict: dict, data_version: str) -> list[xr.Dataset]:
14
14
  ----------
15
15
  data_dict : dict
16
16
  The data itself and its dependent data.
17
- data_version : str
18
- Version of the data product being created.
19
17
 
20
18
  Returns
21
19
  -------
@@ -31,7 +29,6 @@ def ultra_l1c(data_dict: dict, data_version: str) -> list[xr.Dataset]:
31
29
  histogram_dataset = calculate_histogram(
32
30
  data_dict[f"imap_ultra_l1a_{instrument_id}sensor-histogram"],
33
31
  f"imap_ultra_l1c_{instrument_id}sensor-histogram",
34
- data_version,
35
32
  )
36
33
  output_datasets = [histogram_dataset]
37
34
  elif (
@@ -44,7 +41,6 @@ def ultra_l1c(data_dict: dict, data_version: str) -> list[xr.Dataset]:
44
41
  data_dict[f"imap_ultra_l1b_{instrument_id}sensor-extendedspin"],
45
42
  data_dict[f"imap_ultra_l1b_{instrument_id}sensor-cullingmask"],
46
43
  f"imap_ultra_l1c_{instrument_id}sensor-spacecraftpset",
47
- data_version,
48
44
  )
49
45
  # TODO: add calculate_helio_pset here
50
46
  output_datasets = [spacecraft_pset]
@@ -3,14 +3,14 @@
3
3
  import astropy_healpix.healpy as hp
4
4
  import numpy as np
5
5
  import pandas
6
+ import pandas as pd
6
7
  from numpy.typing import NDArray
8
+ from scipy.interpolate import interp1d
7
9
 
8
- from imap_processing.ena_maps.utils.spatial_utils import build_spatial_bins
9
10
  from imap_processing.spice.geometry import (
10
11
  SpiceFrame,
11
12
  cartesian_to_spherical,
12
13
  imap_state,
13
- spherical_to_cartesian,
14
14
  )
15
15
  from imap_processing.ultra.constants import UltraConstants
16
16
 
@@ -70,7 +70,7 @@ def get_spacecraft_histogram(
70
70
  Array of energy bin edges.
71
71
  nside : int, optional
72
72
  The nside parameter of the Healpix tessellation.
73
- Default is 32.
73
+ Default is 128.
74
74
  nested : bool, optional
75
75
  Whether the Healpix tessellation is nested. Default is False.
76
76
 
@@ -174,54 +174,57 @@ def get_spacecraft_exposure_times(constant_exposure: pandas.DataFrame) -> NDArra
174
174
 
175
175
  def get_helio_exposure_times(
176
176
  time: np.ndarray,
177
- sc_exposure: np.ndarray,
177
+ df_exposure: pd.DataFrame,
178
+ nside: int = 128,
179
+ nested: bool = False,
178
180
  ) -> NDArray:
179
181
  """
180
- Compute a 3D array of the exposure in the helio frame.
182
+ Compute a 2D (Healpix index, energy) array of exposure in the helio frame.
181
183
 
182
184
  Parameters
183
185
  ----------
184
186
  time : np.ndarray
185
- Median time of pointing in J2000 seconds.
186
- sc_exposure : np.ndarray
187
- Spacecraft exposure.
187
+ Median time of pointing in et.
188
+ df_exposure : pd.DataFrame
189
+ Spacecraft exposure in healpix coordinates.
190
+ nside : int, optional
191
+ The nside parameter of the Healpix tessellation (default is 128).
192
+ nested : bool, optional
193
+ Whether the Healpix tessellation is nested (default is False).
188
194
 
189
195
  Returns
190
196
  -------
191
- exposure_3d : np.ndarray
192
- A 3D array with dimensions (az, el, energy).
197
+ helio_exposure : np.ndarray
198
+ A 2D array of shape (npix, n_energy_bins).
193
199
 
194
200
  Notes
195
201
  -----
196
202
  These calculations are performed once per pointing.
197
203
  """
198
- # Get bins and midpoints, with angles in degrees.
204
+ # Get energy midpoints.
199
205
  _, energy_midpoints, _ = build_energy_bins()
200
- az_bin_edges, el_bin_edges, az_bin_midpoints, el_bin_midpoints = (
201
- build_spatial_bins()
202
- )
203
-
204
- # Initialize the exposure grid.
205
- exposure_3d = np.zeros(
206
- (len(el_bin_midpoints), len(az_bin_midpoints), len(energy_midpoints))
207
- )
208
-
209
- # Create a 3D Cartesian grid from spherical coordinates
210
- # using azimuth and elevation midpoints.
211
- az_grid, el_grid = np.meshgrid(az_bin_midpoints, el_bin_midpoints[::-1])
212
-
213
- # Radial distance.
214
- r = np.ones(el_grid.shape)
215
- spherical_coords = np.stack((r, az_grid, el_grid), axis=-1)
216
- cartesian_coords = spherical_to_cartesian(spherical_coords)
217
- cartesian = cartesian_coords.reshape(-1, 3, order="F").T
206
+ # Extract (RA/Dec) and exposure from the spacecraft frame.
207
+ ra = df_exposure["Right Ascension (deg)"].values
208
+ dec = df_exposure["Declination (deg)"].values
209
+ exposure_flat = df_exposure["Exposure Time"].values
218
210
 
219
- # Spacecraft velocity in the pointing (DPS) frame wrt heliosphere.
211
+ # The Cartesian state vector representing the position and velocity of the
212
+ # IMAP spacecraft.
220
213
  state = imap_state(time, ref_frame=SpiceFrame.IMAP_DPS)
221
214
 
222
215
  # Extract the velocity part of the state vector
223
216
  spacecraft_velocity = state[3:6]
217
+ # Convert (RA, Dec) angles into 3D unit vectors.
218
+ # Each unit vector represents a direction in the sky where the spacecraft observed
219
+ # and accumulated exposure time.
220
+ unit_dirs = hp.ang2vec(ra, dec, lonlat=True).T # Shape (N, 3)
224
221
 
222
+ # Initialize output array.
223
+ # Each row corresponds to a HEALPix pixel, and each column to an energy bin.
224
+ npix = hp.nside2npix(nside)
225
+ helio_exposure = np.zeros((npix, len(energy_midpoints)))
226
+
227
+ # Loop through energy bins and compute transformed exposure.
225
228
  for i, energy_midpoint in enumerate(energy_midpoints):
226
229
  # Convert the midpoint energy to a velocity (km/s).
227
230
  # Based on kinetic energy equation: E = 1/2 * m * v^2.
@@ -234,41 +237,35 @@ def get_helio_exposure_times(
234
237
  # to the velocity wrt heliosphere.
235
238
  # energy_velocity * cartesian -> apply the magnitude of the velocity
236
239
  # to every position on the grid in the despun grid.
237
- helio_velocity = spacecraft_velocity.reshape(3, 1) + energy_velocity * cartesian
240
+ helio_velocity = spacecraft_velocity.reshape(1, 3) + energy_velocity * unit_dirs
238
241
 
239
242
  # Normalized vectors representing the direction of the heliocentric velocity.
240
- helio_normalized = helio_velocity.T / np.linalg.norm(
241
- helio_velocity.T, axis=1, keepdims=True
243
+ helio_normalized = helio_velocity / np.linalg.norm(
244
+ helio_velocity, axis=1, keepdims=True
242
245
  )
243
- # Converts vectors from Cartesian coordinates (x, y, z)
244
- # into spherical coordinates.
245
- spherical_coords = cartesian_to_spherical(helio_normalized)
246
- az, el = spherical_coords[..., 1], spherical_coords[..., 2]
247
246
 
248
- # Assign values from sc_exposure directly to bins.
249
- az_idx = np.digitize(az, az_bin_edges) - 1
250
- el_idx = np.digitize(el, el_bin_edges[::-1]) - 1
247
+ # Convert Cartesian heliocentric vectors into spherical coordinates.
248
+ # Result: azimuth (longitude) and elevation (latitude) in degrees.
249
+ helio_spherical = cartesian_to_spherical(helio_normalized)
250
+ az, el = helio_spherical[:, 1], helio_spherical[:, 2]
251
251
 
252
- # Ensure az_idx and el_idx are within bounds.
253
- az_idx = np.clip(az_idx, 0, len(az_bin_edges) - 2)
254
- el_idx = np.clip(el_idx, 0, len(el_bin_edges) - 2)
252
+ # Convert azimuth/elevation directions to HEALPix pixel indices.
253
+ hpix_idx = hp.ang2pix(nside, az, el, nest=nested, lonlat=True)
255
254
 
256
- # A 1D array of linear indices used to track the bin_id.
257
- idx = el_idx + az_idx * az_grid.shape[0]
258
- # Bins the transposed sc_exposure array.
259
- binned_exposure = sc_exposure.T.flatten(order="F")[idx]
260
- # Reshape the binned exposure.
261
- exposure_3d[:, :, i] = binned_exposure.reshape(az_grid.shape, order="F")
255
+ # Accumulate exposure values into HEALPix pixels for this energy bin.
256
+ helio_exposure[:, i] = np.bincount(
257
+ hpix_idx, weights=exposure_flat, minlength=npix
258
+ )
262
259
 
263
- return exposure_3d
260
+ return helio_exposure
264
261
 
265
262
 
266
263
  def get_spacecraft_sensitivity(
267
264
  efficiencies: pandas.DataFrame,
268
265
  geometric_function: pandas.DataFrame,
269
- ) -> pandas.DataFrame:
266
+ ) -> tuple[pandas.DataFrame, NDArray, NDArray, NDArray]:
270
267
  """
271
- Compute sensitivity.
268
+ Compute sensitivity as efficiency * geometric factor.
272
269
 
273
270
  Parameters
274
271
  ----------
@@ -281,19 +278,69 @@ def get_spacecraft_sensitivity(
281
278
  -------
282
279
  pointing_sensitivity : pandas.DataFrame
283
280
  Sensitivity with dimensions (HEALPIX pixel_number, energy).
281
+ energy_vals : NDArray
282
+ Energy values of dataframe.
283
+ right_ascension : NDArray
284
+ Right ascension (longitude/azimuth) values of dataframe (0 - 360 degrees).
285
+ declination : NDArray
286
+ Declination (latitude/elevation) values of dataframe (-90 to 90 degrees).
284
287
  """
285
288
  # Exclude "Right Ascension (deg)" and "Declination (deg)" from the multiplication
286
- energy_columns = efficiencies.columns.difference(
287
- ["Right Ascension (deg)", "Declination (deg)"]
288
- )
289
+ energy_columns = [
290
+ col
291
+ for col in efficiencies.columns
292
+ if col not in ["Right Ascension (deg)", "Declination (deg)"]
293
+ ]
289
294
  sensitivity = efficiencies[energy_columns].mul(
290
295
  geometric_function["Response (cm2-sr)"].values, axis=0
291
296
  )
292
297
 
293
- # Add "Right Ascension (deg)" and "Declination (deg)" to the result
294
- sensitivity.insert(
295
- 0, "Right Ascension (deg)", efficiencies["Right Ascension (deg)"]
298
+ right_ascension = efficiencies["Right Ascension (deg)"]
299
+ declination = efficiencies["Declination (deg)"]
300
+
301
+ energy_vals = np.array([float(col.replace("keV", "")) for col in energy_columns])
302
+
303
+ return sensitivity, energy_vals, right_ascension, declination
304
+
305
+
306
+ def grid_sensitivity(
307
+ efficiencies: pandas.DataFrame,
308
+ geometric_function: pandas.DataFrame,
309
+ energy: float,
310
+ ) -> NDArray:
311
+ """
312
+ Grid the sensitivity.
313
+
314
+ Parameters
315
+ ----------
316
+ efficiencies : pandas.DataFrame
317
+ Efficiencies at different energy levels.
318
+ geometric_function : pandas.DataFrame
319
+ Geometric function.
320
+ energy : np.ndarray
321
+ The particle energy.
322
+ energy : float
323
+ Energy to which we are interpolating.
324
+
325
+ Returns
326
+ -------
327
+ interpolated_sensitivity : np.ndarray
328
+ Sensitivity with dimensions (HEALPIX pixel_number, 1).
329
+ """
330
+ sensitivity, energy_vals, right_ascension, declination = get_spacecraft_sensitivity(
331
+ efficiencies, geometric_function
332
+ )
333
+
334
+ # Create interpolator over energy dimension for each pixel (axis=1)
335
+ interp_func = interp1d(
336
+ energy_vals,
337
+ sensitivity.values,
338
+ axis=1,
339
+ bounds_error=False,
340
+ fill_value=np.nan,
296
341
  )
297
- sensitivity.insert(1, "Declination (deg)", efficiencies["Declination (deg)"])
298
342
 
299
- return sensitivity
343
+ # Interpolate to energy
344
+ interpolated = interp_func(energy)
345
+
346
+ return interpolated
@@ -0,0 +1,299 @@
1
+ """Calculate ULTRA Level 2 (L2) ENA Map Product."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+
7
+ import numpy as np
8
+ import xarray as xr
9
+
10
+ from imap_processing.ena_maps import ena_maps
11
+ from imap_processing.ena_maps.utils.coordinates import CoordNames
12
+
13
+ logger = logging.getLogger(__name__)
14
+ logger.info("Importing ultra_l2 module")
15
+
16
+ # Default properties for the Ultra L2 map
17
+ DEFAULT_ULTRA_L2_MAP_STRUCTURE: ena_maps.RectangularSkyMap | ena_maps.HealpixSkyMap = (
18
+ ena_maps.AbstractSkyMap.from_dict(
19
+ {
20
+ "sky_tiling_type": "HEALPIX",
21
+ "spice_reference_frame": "ECLIPJ2000",
22
+ "values_to_push_project": [
23
+ "counts",
24
+ "exposure_factor",
25
+ "sensitivity",
26
+ "background_rates",
27
+ ],
28
+ "nside": 32,
29
+ "nested": False,
30
+ }
31
+ )
32
+ )
33
+
34
+ # Set some default Healpix parameters - these must be defined, even if also
35
+ # present in the DEFAULT_ULTRA_L2_MAP_STRUCTURE, because we always make a Healpix map
36
+ # regardless of the output map type
37
+ DEFAULT_L2_HEALPIX_NSIDE = 32
38
+ DEFAULT_L2_HEALPIX_NESTED = False
39
+
40
+
41
+ # These variables must always be present in each L1C dataset
42
+ REQUIRED_L1C_VARIABLES = [
43
+ "counts",
44
+ "exposure_factor",
45
+ "sensitivity",
46
+ "background_rates",
47
+ ]
48
+
49
+ # These variables are projected to the map as the mean of pointing set pixels value,
50
+ # weighted by that pointing set pixel's exposure and solid angle
51
+ VARIABLES_TO_WEIGHT_BY_POINTING_SET_EXPOSURE_TIMES_SOLID_ANGLE = [
52
+ "sensitivity",
53
+ "background_rates",
54
+ "observation_time",
55
+ ]
56
+
57
+ # These variables are dropped after they are used to calculate flux and flux uncertainty
58
+ # They will not be present in the final map
59
+ VARIABLES_TO_DROP_AFTER_FLUX_CALCULATION = [
60
+ "counts",
61
+ "background_rates",
62
+ "pointing_set_exposure_times_solid_angle",
63
+ "num_pointing_set_pixel_members",
64
+ "corrected_count_rate",
65
+ ]
66
+
67
+
68
+ def generate_ultra_healpix_skymap(
69
+ ultra_l1c_psets: list[str | xr.Dataset],
70
+ output_map_structure: (
71
+ ena_maps.RectangularSkyMap | ena_maps.HealpixSkyMap
72
+ ) = DEFAULT_ULTRA_L2_MAP_STRUCTURE,
73
+ ) -> ena_maps.HealpixSkyMap:
74
+ """
75
+ Generate a Healpix skymap from ULTRA L1C pointing sets.
76
+
77
+ This function combines IMAP Ultra L1C pointing sets into a single L2 HealpixSkyMap.
78
+ It handles the projection of values from pointing sets to the map, applies necessary
79
+ weighting and background subtraction, and calculates flux and flux uncertainty.
80
+
81
+ Parameters
82
+ ----------
83
+ ultra_l1c_psets : list[str | xr.Dataset]
84
+ List of paths to ULTRA L1C pointing set files or xarray Datasets containing
85
+ pointing set data.
86
+ output_map_structure : ena_maps.RectangularSkyMap | ena_maps.HealpixSkyMap, optional
87
+ Empty SkyMap structure providing the properties of the map to be generated.
88
+ Defaults to DEFAULT_ULTRA_L2_MAP_STRUCTURE defined in this module.
89
+
90
+ Returns
91
+ -------
92
+ ena_maps.HealpixSkyMap
93
+ HealpixSkyMap object containing the combined data from all pointing sets,
94
+ with calculated flux and flux uncertainty values.
95
+
96
+ Notes
97
+ -----
98
+ The structure of this function goes as follows:
99
+ 1. Initialize the HealpixSkyMap object with the specified properties.
100
+ 2. Iterate over the input pointing sets and read them into UltraPointingSet objects.
101
+ 3. For each pointing set, weight certain variables by exposure and solid angle of
102
+ the pointing set pixels.
103
+ 4. Project the pointing set values to the map using the push method.
104
+ 5. Perform subsequent processing for weighted quantities at the SkyMap level
105
+ (e.g., divide weighted quantities by their summed weights to
106
+ get their weighted mean)
107
+ 6. Calculate corrected count rate with background subtraction applied.
108
+ 7. Calculate flux and flux uncertainty.
109
+ 8. Drop unnecessary variables from the map.
110
+ """
111
+ if output_map_structure.tiling_type is ena_maps.SkyTilingType.HEALPIX:
112
+ map_nside, map_nested = (
113
+ output_map_structure.nside,
114
+ output_map_structure.nested,
115
+ )
116
+ else:
117
+ map_nside, map_nested = (DEFAULT_L2_HEALPIX_NSIDE, DEFAULT_L2_HEALPIX_NESTED)
118
+
119
+ # Initialize the HealpixSkyMap object
120
+ skymap = ena_maps.HealpixSkyMap(
121
+ nside=map_nside,
122
+ nested=map_nested,
123
+ spice_frame=output_map_structure.spice_reference_frame,
124
+ )
125
+
126
+ # Add additional data variables to the map
127
+ output_map_structure.values_to_push_project.extend(
128
+ [
129
+ "observation_time",
130
+ "pointing_set_exposure_times_solid_angle",
131
+ "num_pointing_set_pixel_members",
132
+ ]
133
+ )
134
+
135
+ # Get full list of variables to push to the map: all requested variables plus
136
+ # any which are required for L2 processing
137
+ value_keys_to_push_project = list(
138
+ set(output_map_structure.values_to_push_project + REQUIRED_L1C_VARIABLES)
139
+ )
140
+
141
+ for ultra_l1c_pset in ultra_l1c_psets:
142
+ pointing_set = ena_maps.UltraPointingSet(ultra_l1c_pset)
143
+ logger.info(
144
+ f"Projecting a PointingSet with {pointing_set.num_points} pixels "
145
+ f"at epoch:{pointing_set.epoch}\n"
146
+ f"These values will be projected: {value_keys_to_push_project}"
147
+ )
148
+
149
+ pointing_set.data["num_pointing_set_pixel_members"] = xr.DataArray(
150
+ np.ones(pointing_set.num_points, dtype=int),
151
+ dims=(CoordNames.HEALPIX_INDEX.value),
152
+ )
153
+ pointing_set.data["observation_time"] = xr.DataArray(
154
+ np.full(pointing_set.num_points, pointing_set.epoch),
155
+ dims=(CoordNames.HEALPIX_INDEX.value),
156
+ )
157
+ # Add solid_angle * exposure of pointing set as data_var
158
+ # so this quantity is projected to map pixels for use in weighted averaging
159
+ pointing_set.data["pointing_set_exposure_times_solid_angle"] = (
160
+ pointing_set.data["exposure_factor"] * pointing_set.solid_angle
161
+ )
162
+
163
+ # Initial processing for weighted quantities at PSET level
164
+ # Weight the values by exposure and solid angle
165
+ pointing_set.data[
166
+ VARIABLES_TO_WEIGHT_BY_POINTING_SET_EXPOSURE_TIMES_SOLID_ANGLE
167
+ ] *= pointing_set.data["pointing_set_exposure_times_solid_angle"]
168
+
169
+ skymap.project_pset_values_to_map(
170
+ pointing_set=pointing_set,
171
+ value_keys=value_keys_to_push_project,
172
+ index_match_method=ena_maps.IndexMatchMethod.PUSH,
173
+ )
174
+
175
+ # Subsequent processing for weighted quantities at SkyMap level
176
+ skymap.data_1d[VARIABLES_TO_WEIGHT_BY_POINTING_SET_EXPOSURE_TIMES_SOLID_ANGLE] /= (
177
+ skymap.data_1d["pointing_set_exposure_times_solid_angle"]
178
+ )
179
+
180
+ # TODO: Ask Ultra team about this - I think this is a decent
181
+ # but imperfect approximation for meaning the exposure:
182
+ # (dividing by the 1/(number of PSETs)) to fix the exposure being mean-ed over
183
+ # all pixels in all PSETs which feed into a map superpixel,
184
+ # rather than being mean-ed over pixels in a PSET and summed over PSETs
185
+ skymap.data_1d["exposure_factor"] /= skymap.data_1d[
186
+ "num_pointing_set_pixel_members"
187
+ ] / len(ultra_l1c_psets)
188
+
189
+ # TODO: Ask Ultra team about background rates - I think they should increase when
190
+ # binned to larger pixels, as I've done here, but that was never explicitly stated
191
+ skymap.data_1d["background_rates"] *= skymap.solid_angle / pointing_set.solid_angle
192
+
193
+ # Get the energy bin widths from a PointingSet (they will all be the same)
194
+ delta_energy = pointing_set.data["energy_bin_delta"]
195
+
196
+ # Core calculations of flux and flux uncertainty for L2
197
+ # Exposure time may contain 0s, producing NaNs in the corrected count rate and flux.
198
+ # These NaNs are not incorrect, so we temporarily ignore numpy div by 0 warnings.
199
+ with np.errstate(divide="ignore"):
200
+ # Get corrected count rate with background subtraction applied
201
+ skymap.data_1d["corrected_count_rate"] = (
202
+ skymap.data_1d["counts"].astype(float) / skymap.data_1d["exposure_factor"]
203
+ ) - skymap.data_1d["background_rates"]
204
+
205
+ # Calculate flux = corrected_counts / (sensitivity * solid_angle * delta_energy)
206
+ skymap.data_1d["flux"] = skymap.data_1d["corrected_count_rate"] / (
207
+ skymap.data_1d["sensitivity"] * skymap.solid_angle * delta_energy
208
+ )
209
+
210
+ skymap.data_1d["flux_uncertainty"] = (
211
+ skymap.data_1d["counts"].astype(float) ** 0.5
212
+ ) / (
213
+ skymap.data_1d["exposure_factor"]
214
+ * skymap.data_1d["sensitivity"]
215
+ * skymap.solid_angle
216
+ * delta_energy
217
+ )
218
+
219
+ # Drop the variables that are no longer needed
220
+ skymap.data_1d = skymap.data_1d.drop_vars(
221
+ VARIABLES_TO_DROP_AFTER_FLUX_CALCULATION,
222
+ )
223
+
224
+ return skymap
225
+
226
+
227
+ def ultra_l2(
228
+ data_dict: dict[str, xr.Dataset | str],
229
+ data_version: str,
230
+ output_map_structure: (
231
+ ena_maps.RectangularSkyMap | ena_maps.HealpixSkyMap
232
+ ) = DEFAULT_ULTRA_L2_MAP_STRUCTURE,
233
+ ) -> list[xr.Dataset]:
234
+ """
235
+ Generate and format Ultra L2 ENA Map Product from L1C Products.
236
+
237
+ Parameters
238
+ ----------
239
+ data_dict : dict[str, xr.Dataset]
240
+ Dict mapping l1c product identifiers to paths/Datasets containing l1c psets.
241
+ data_version : str
242
+ Version of the data product being created.
243
+ output_map_structure : ena_maps.RectangularSkyMap | ena_maps.HealpixSkyMap, optional
244
+ Empty SkyMap structure providing the properties of the map to be generated.
245
+ Defaults to DEFAULT_ULTRA_L2_MAP_STRUCTURE defined in this module.
246
+
247
+ Returns
248
+ -------
249
+ list[xarray.Dataset,]
250
+ L2 output dataset containing map of the counts on the sky.
251
+ Wrapped in a list for consistency with other product levels.
252
+
253
+ Raises
254
+ ------
255
+ NotImplementedError
256
+ If asked to project to a rectangular map.
257
+ # TODO: This is coming shortly
258
+ """
259
+ l1c_products = data_dict.values()
260
+ num_l1c_products = len(l1c_products)
261
+ logger.info(f"Running ultra_l2 processing on {num_l1c_products} L1C products")
262
+
263
+ # Regardless of the output sky tiling type, we will directly
264
+ # project the PSET values into a healpix map. However, if we are outputting
265
+ # a Healpix map, we can go directly to map with desired nside, nested params
266
+ healpix_skymap = generate_ultra_healpix_skymap(
267
+ ultra_l1c_psets=list(l1c_products),
268
+ output_map_structure=output_map_structure,
269
+ )
270
+
271
+ # Output formatting for HEALPIX tiling
272
+ if output_map_structure.tiling_type is ena_maps.SkyTilingType.HEALPIX:
273
+ map_dataset = healpix_skymap.to_dataset()
274
+ # Add attributes related to the map
275
+ map_attrs = {
276
+ "HEALPix_nside": output_map_structure.nside,
277
+ "HEALPix_nest": output_map_structure.nested,
278
+ "Data_version": data_version,
279
+ }
280
+
281
+ # TODO: Implement conversion to Rectangular map
282
+ elif output_map_structure.tiling_type is ena_maps.SkyTilingType.RECTANGULAR:
283
+ map_attrs = {
284
+ "Spacing_degrees": output_map_structure.spacing_deg,
285
+ "Data_version": data_version,
286
+ }
287
+ raise NotImplementedError
288
+
289
+ # Always add the following attributes to the map
290
+ map_attrs.update(
291
+ {
292
+ "Sky_tiling_type": output_map_structure.tiling_type.value,
293
+ "Spice_reference_frame": output_map_structure.spice_reference_frame,
294
+ }
295
+ )
296
+
297
+ # Add the defined attributes to the map's global attrs
298
+ map_dataset.attrs.update(map_attrs)
299
+ return [map_dataset]