imap-processing 0.11.0__py3-none-any.whl → 0.12.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.
- imap_processing/__init__.py +10 -11
- imap_processing/_version.py +2 -2
- imap_processing/ccsds/excel_to_xtce.py +65 -16
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -28
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +365 -42
- imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +0 -5
- imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +10 -11
- imap_processing/cdf/config/imap_hi_variable_attrs.yaml +17 -19
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +26 -13
- imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +106 -116
- imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +120 -145
- imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +14 -0
- imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +6 -9
- imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +1 -1
- imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +0 -12
- imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +1 -1
- imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +9 -21
- imap_processing/cdf/config/imap_mag_l1a_variable_attrs.yaml +361 -0
- imap_processing/cdf/config/imap_mag_l1b_variable_attrs.yaml +160 -0
- imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +160 -0
- imap_processing/cdf/config/imap_spacecraft_global_cdf_attrs.yaml +18 -0
- imap_processing/cdf/config/imap_spacecraft_variable_attrs.yaml +40 -0
- imap_processing/cdf/config/imap_swapi_global_cdf_attrs.yaml +1 -5
- imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +12 -4
- imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +16 -2
- imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +48 -52
- imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +71 -47
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +2 -14
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +51 -2
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +29 -14
- imap_processing/cdf/utils.py +13 -7
- imap_processing/cli.py +23 -8
- imap_processing/codice/codice_l1a.py +207 -85
- imap_processing/codice/constants.py +1322 -568
- imap_processing/codice/decompress.py +2 -6
- imap_processing/ena_maps/ena_maps.py +480 -116
- imap_processing/ena_maps/utils/coordinates.py +19 -0
- imap_processing/ena_maps/utils/map_utils.py +14 -17
- imap_processing/ena_maps/utils/spatial_utils.py +45 -47
- imap_processing/hi/l1a/hi_l1a.py +24 -18
- imap_processing/hi/l1a/histogram.py +0 -1
- imap_processing/hi/l1a/science_direct_event.py +6 -8
- imap_processing/hi/l1b/hi_l1b.py +31 -39
- imap_processing/hi/l1c/hi_l1c.py +405 -17
- imap_processing/hi/utils.py +58 -12
- imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt0-factors_20250219_v002.csv +205 -0
- imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt1-factors_20250219_v002.csv +205 -0
- imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt2-factors_20250219_v002.csv +205 -0
- imap_processing/hit/ancillary/imap_hit_l1b-to-l2-standard-dt3-factors_20250219_v002.csv +205 -0
- imap_processing/hit/ancillary/imap_hit_l1b-to-l2-summed-dt0-factors_20250219_v002.csv +68 -0
- imap_processing/hit/hit_utils.py +173 -1
- imap_processing/hit/l0/constants.py +20 -11
- imap_processing/hit/l0/decom_hit.py +18 -4
- imap_processing/hit/l1a/hit_l1a.py +45 -54
- imap_processing/hit/l1b/constants.py +317 -0
- imap_processing/hit/l1b/hit_l1b.py +367 -18
- imap_processing/hit/l2/constants.py +281 -0
- imap_processing/hit/l2/hit_l2.py +614 -0
- imap_processing/hit/packet_definitions/hit_packet_definitions.xml +1323 -71
- imap_processing/ialirt/l0/mag_l0_ialirt_data.py +155 -0
- imap_processing/ialirt/l0/parse_mag.py +246 -0
- imap_processing/ialirt/l0/process_swe.py +252 -0
- imap_processing/ialirt/packet_definitions/ialirt.xml +7 -3
- imap_processing/ialirt/packet_definitions/ialirt_mag.xml +115 -0
- imap_processing/ialirt/utils/grouping.py +114 -0
- imap_processing/ialirt/utils/time.py +29 -0
- imap_processing/idex/atomic_masses.csv +22 -0
- imap_processing/idex/decode.py +2 -2
- imap_processing/idex/idex_constants.py +25 -0
- imap_processing/idex/idex_l1a.py +6 -7
- imap_processing/idex/idex_l1b.py +4 -31
- imap_processing/idex/idex_l2a.py +789 -0
- imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +39 -33
- imap_processing/lo/l0/lo_science.py +6 -0
- imap_processing/lo/l1a/lo_l1a.py +0 -1
- imap_processing/lo/l1b/lo_l1b.py +177 -25
- imap_processing/mag/constants.py +8 -0
- imap_processing/mag/imap_mag_sdc-configuration_v001.yaml +6 -0
- imap_processing/mag/l0/decom_mag.py +10 -3
- imap_processing/mag/l1a/mag_l1a.py +22 -11
- imap_processing/mag/l1a/mag_l1a_data.py +28 -3
- imap_processing/mag/l1b/mag_l1b.py +190 -48
- imap_processing/mag/l1c/interpolation_methods.py +211 -0
- imap_processing/mag/l1c/mag_l1c.py +447 -9
- imap_processing/quality_flags.py +1 -0
- imap_processing/spacecraft/packet_definitions/scid_x252.xml +538 -0
- imap_processing/spacecraft/quaternions.py +123 -0
- imap_processing/spice/geometry.py +16 -19
- imap_processing/spice/repoint.py +120 -0
- imap_processing/swapi/l1/swapi_l1.py +4 -0
- imap_processing/swapi/l2/swapi_l2.py +0 -1
- imap_processing/swe/l1a/swe_l1a.py +47 -8
- imap_processing/swe/l1a/swe_science.py +5 -2
- imap_processing/swe/l1b/swe_l1b_science.py +103 -56
- imap_processing/swe/l2/swe_l2.py +60 -65
- imap_processing/swe/packet_definitions/swe_packet_definition.xml +1121 -1
- imap_processing/swe/utils/swe_constants.py +63 -0
- imap_processing/swe/utils/swe_utils.py +85 -28
- imap_processing/tests/ccsds/test_data/expected_output.xml +40 -1
- imap_processing/tests/ccsds/test_excel_to_xtce.py +23 -20
- imap_processing/tests/cdf/test_data/imap_instrument2_global_cdf_attrs.yaml +0 -2
- imap_processing/tests/codice/conftest.py +1 -1
- imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-counters-singles_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-ialirt_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-omni_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-pha_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-priorities_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_hi-sectored_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-aggregated_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-counters-singles_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-angular_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-priority_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-nsw-species_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-pha_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-angular_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-priority_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/data/validation/imap_codice_l1a_lo-sw-species_20241110193700_v0.0.0.cdf +0 -0
- imap_processing/tests/codice/test_codice_l1a.py +110 -46
- imap_processing/tests/codice/test_decompress.py +4 -4
- imap_processing/tests/conftest.py +166 -10
- imap_processing/tests/ena_maps/conftest.py +51 -0
- imap_processing/tests/ena_maps/test_ena_maps.py +638 -109
- imap_processing/tests/ena_maps/test_map_utils.py +66 -43
- imap_processing/tests/ena_maps/test_spatial_utils.py +16 -20
- imap_processing/tests/hi/data/l0/H45_diag_fee_20250208.bin +0 -0
- imap_processing/tests/hi/data/l0/H45_diag_fee_20250208_verify.csv +205 -0
- imap_processing/tests/hi/test_hi_l1b.py +12 -15
- imap_processing/tests/hi/test_hi_l1c.py +234 -6
- imap_processing/tests/hi/test_l1a.py +30 -0
- imap_processing/tests/hi/test_science_direct_event.py +1 -1
- imap_processing/tests/hi/test_utils.py +24 -2
- imap_processing/tests/hit/helpers/l1_validation.py +39 -39
- imap_processing/tests/hit/test_data/hskp_sample.ccsds +0 -0
- imap_processing/tests/hit/test_data/imap_hit_l0_raw_20100105_v001.pkts +0 -0
- imap_processing/tests/hit/test_decom_hit.py +4 -0
- imap_processing/tests/hit/test_hit_l1a.py +24 -28
- imap_processing/tests/hit/test_hit_l1b.py +304 -40
- imap_processing/tests/hit/test_hit_l2.py +454 -0
- imap_processing/tests/hit/test_hit_utils.py +112 -2
- imap_processing/tests/hit/validation_data/hskp_sample_eu_3_6_2025.csv +89 -0
- imap_processing/tests/hit/validation_data/hskp_sample_raw.csv +89 -88
- imap_processing/tests/ialirt/test_data/l0/461971383-404.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/461971384-405.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/461971385-406.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/461971386-407.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/461971387-408.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/461971388-409.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/461971389-410.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/461971390-411.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/461971391-412.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/sample_decoded_i-alirt_data.csv +383 -0
- imap_processing/tests/ialirt/unit/test_grouping.py +81 -0
- imap_processing/tests/ialirt/unit/test_parse_mag.py +168 -0
- imap_processing/tests/ialirt/unit/test_process_swe.py +208 -3
- imap_processing/tests/ialirt/unit/test_time.py +16 -0
- imap_processing/tests/idex/conftest.py +62 -6
- imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231218_v001.pkts +0 -0
- imap_processing/tests/idex/test_data/impact_14_tof_high_data.txt +4508 -4508
- imap_processing/tests/idex/test_idex_l1a.py +48 -4
- imap_processing/tests/idex/test_idex_l1b.py +3 -3
- imap_processing/tests/idex/test_idex_l2a.py +383 -0
- imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20241022_v002.cdf +0 -0
- imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20241022_v002.cdf +0 -0
- imap_processing/tests/lo/test_lo_l1b.py +148 -4
- imap_processing/tests/lo/test_lo_science.py +1 -0
- imap_processing/tests/mag/conftest.py +69 -0
- imap_processing/tests/mag/test_mag_decom.py +1 -1
- imap_processing/tests/mag/test_mag_l1a.py +38 -0
- imap_processing/tests/mag/test_mag_l1b.py +34 -53
- imap_processing/tests/mag/test_mag_l1c.py +251 -20
- imap_processing/tests/mag/test_mag_validation.py +109 -25
- imap_processing/tests/mag/validation/L1b/T009/MAGScience-normal-(2,2)-8s-20250204-16h39.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +16 -16
- imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +16 -16
- imap_processing/tests/mag/validation/L1b/T010/MAGScience-normal-(2,2)-8s-20250206-12h05.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T011/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +16 -16
- imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +16 -16
- imap_processing/tests/mag/validation/L1b/T012/MAGScience-normal-(2,2)-8s-20250204-16h08.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T012/data.bin +0 -0
- imap_processing/tests/mag/validation/L1b/T012/field_like_all_ranges.txt +19200 -0
- imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-cal.cdf +0 -0
- imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-in.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-magi-out.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T012/mag-l1a-l1b-t012-mago-out.csv +17 -0
- imap_processing/tests/mag/validation/imap_calibration_mag_20240229_v01.cdf +0 -0
- imap_processing/tests/spacecraft/__init__.py +0 -0
- imap_processing/tests/spacecraft/data/SSR_2024_190_20_08_12_0483851794_2_DA_apid0594_1packet.pkts +0 -0
- imap_processing/tests/spacecraft/test_quaternions.py +71 -0
- imap_processing/tests/spice/test_data/fake_repoint_data.csv +5 -0
- imap_processing/tests/spice/test_geometry.py +6 -9
- imap_processing/tests/spice/test_repoint.py +111 -0
- imap_processing/tests/swapi/test_swapi_l1.py +7 -3
- imap_processing/tests/swe/l0_data/2024051010_SWE_HK_packet.bin +0 -0
- imap_processing/tests/swe/l0_data/2024051011_SWE_CEM_RAW_packet.bin +0 -0
- imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_APP_HK_20240510_092742.csv +49 -0
- imap_processing/tests/swe/l0_validation_data/idle_export_eu.SWE_CEM_RAW_20240510_092742.csv +593 -0
- imap_processing/tests/swe/test_swe_l1a.py +18 -0
- imap_processing/tests/swe/test_swe_l1a_cem_raw.py +52 -0
- imap_processing/tests/swe/test_swe_l1a_hk.py +68 -0
- imap_processing/tests/swe/test_swe_l1b_science.py +23 -4
- imap_processing/tests/swe/test_swe_l2.py +112 -30
- imap_processing/tests/test_cli.py +2 -2
- imap_processing/tests/test_utils.py +138 -16
- imap_processing/tests/ultra/data/l0/FM45_UltraFM45_Functional_2024-01-22T0105_20240122T010548.CCSDS +0 -0
- imap_processing/tests/ultra/data/l0/ultra45_raw_sc_ultraimgrates_20220530_00.csv +164 -0
- imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3243 -3243
- imap_processing/tests/ultra/data/mock_data.py +341 -0
- imap_processing/tests/ultra/unit/conftest.py +69 -26
- imap_processing/tests/ultra/unit/test_badtimes.py +2 -0
- imap_processing/tests/ultra/unit/test_cullingmask.py +4 -0
- imap_processing/tests/ultra/unit/test_de.py +12 -4
- imap_processing/tests/ultra/unit/test_decom_apid_881.py +44 -0
- imap_processing/tests/ultra/unit/test_spacecraft_pset.py +78 -0
- imap_processing/tests/ultra/unit/test_ultra_l1a.py +28 -12
- imap_processing/tests/ultra/unit/test_ultra_l1b.py +34 -6
- imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +22 -26
- imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +86 -51
- imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +94 -52
- imap_processing/ultra/l0/decom_tools.py +6 -5
- imap_processing/ultra/l1a/ultra_l1a.py +28 -56
- imap_processing/ultra/l1b/de.py +72 -28
- imap_processing/ultra/l1b/extendedspin.py +12 -14
- imap_processing/ultra/l1b/ultra_l1b.py +34 -9
- imap_processing/ultra/l1b/ultra_l1b_culling.py +65 -29
- imap_processing/ultra/l1b/ultra_l1b_extended.py +64 -19
- imap_processing/ultra/l1c/spacecraft_pset.py +86 -0
- imap_processing/ultra/l1c/ultra_l1c.py +7 -4
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +112 -61
- imap_processing/ultra/lookup_tables/ultra_90_dps_exposure_compressed.cdf +0 -0
- imap_processing/ultra/utils/ultra_l1_utils.py +20 -2
- imap_processing/utils.py +68 -28
- {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/METADATA +8 -5
- {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/RECORD +250 -199
- imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +0 -237
- imap_processing/hi/l1a/housekeeping.py +0 -27
- imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-aggregated_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_hi-counters-singles_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_hi-omni_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_hi-sectored_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_hskp_20100101_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-aggregated_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_lo-counters-singles_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-angular_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-priority_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_lo-nsw-species_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-angular_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-priority_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1a_lo-sw-species_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-aggregated_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_hi-counters-singles_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_hi-omni_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_hi-sectored_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_hskp_20100101_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-aggregated_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_lo-counters-singles_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-angular_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-priority_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_lo-nsw-species_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-angular_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-priority_20240429_v001.cdf +0 -0
- imap_processing/tests/codice/data/imap_codice_l1b_lo-sw-species_20240429_v001.cdf +0 -0
- imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
- imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1251.pkts +0 -0
- imap_processing/tests/hit/PREFLIGHT_raw_record_2023_256_15_59_04_apid1252.pkts +0 -0
- imap_processing/tests/hit/validation_data/hskp_sample_eu.csv +0 -89
- imap_processing/tests/hit/validation_data/sci_sample_raw1.csv +0 -29
- imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231214_v001.pkts +0 -0
- imap_processing/tests/lo/test_cdfs/imap_lo_l1a_de_20100101_v001.cdf +0 -0
- imap_processing/tests/lo/test_cdfs/imap_lo_l1a_spin_20100101_v001.cdf +0 -0
- imap_processing/tests/ultra/test_data/mock_data.py +0 -161
- imap_processing/ultra/l1c/pset.py +0 -40
- /imap_processing/tests/ultra/{test_data → data}/l0/FM45_40P_Phi28p5_BeamCal_LinearScan_phi28.50_theta-0.00_20240207T102740.CCSDS +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l0/FM45_7P_Phi0.0_BeamCal_LinearScan_phi0.04_theta-0.01_20230821T121304.CCSDS +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l0/FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.CCSDS +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l0/Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.CCSDS +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_auxdata_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_enaphxtofhangimg_FM45_TV_Cycle6_Hot_Ops_Front212_20240124T063837.csv +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultraimgrates_Ultra45_EM_SwRI_Cal_Run7_ThetaScan_20220530T225054.csv +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l0/ultra45_raw_sc_ultrarawimgevent_FM45_7P_Phi00_BeamCal_LinearScan_phi004_theta-001_20230821T121304.csv +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E1.cdf +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E12.cdf +0 -0
- /imap_processing/tests/ultra/{test_data → data}/l1/dps_exposure_helio_45_E24.cdf +0 -0
- {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.11.0.dist-info → imap_processing-0.12.0.dist-info}/entry_points.txt +0 -0
|
@@ -7,13 +7,19 @@ import pathlib
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from enum import Enum
|
|
9
9
|
|
|
10
|
+
import astropy_healpix.healpy as hp
|
|
10
11
|
import numpy as np
|
|
11
12
|
import xarray as xr
|
|
12
13
|
from numpy.typing import NDArray
|
|
13
14
|
|
|
14
15
|
from imap_processing.cdf.utils import load_cdf
|
|
15
16
|
from imap_processing.ena_maps.utils import map_utils, spatial_utils
|
|
17
|
+
|
|
18
|
+
# The coordinate names can vary between L1C and L2 data (e.g. azimuth vs longitude),
|
|
19
|
+
# so we define an enum to handle the coordinate names.
|
|
20
|
+
from imap_processing.ena_maps.utils.coordinates import CoordNames
|
|
16
21
|
from imap_processing.spice import geometry
|
|
22
|
+
from imap_processing.spice.time import ttj2000ns_to_et
|
|
17
23
|
|
|
18
24
|
logger = logging.getLogger(__name__)
|
|
19
25
|
|
|
@@ -60,7 +66,7 @@ class IndexMatchMethod(Enum):
|
|
|
60
66
|
def match_coords_to_indices(
|
|
61
67
|
input_object: PointingSet | AbstractSkyMap,
|
|
62
68
|
output_object: PointingSet | AbstractSkyMap,
|
|
63
|
-
|
|
69
|
+
event_et: float | None = None,
|
|
64
70
|
) -> NDArray:
|
|
65
71
|
"""
|
|
66
72
|
Find the output indices corresponding to each input coord between 2 spatial objects.
|
|
@@ -92,10 +98,11 @@ def match_coords_to_indices(
|
|
|
92
98
|
The object containing a grid or tessellation of spatial pixels
|
|
93
99
|
into which the input spatial pixel centers will 'land', and be matched to
|
|
94
100
|
corresponding pixel 1D indices in the output frame.
|
|
95
|
-
|
|
101
|
+
event_et : float, optional
|
|
96
102
|
Event time at which to transform the input spatial object to the output frame.
|
|
97
103
|
This can be manually specified, e.g., for converting between Maps which do not
|
|
98
104
|
contain an epoch value.
|
|
105
|
+
If specified, must be in SPICE compatible ET.
|
|
99
106
|
The default value is None, in which case the event time of the PointingSet
|
|
100
107
|
object is used.
|
|
101
108
|
|
|
@@ -121,12 +128,14 @@ def match_coords_to_indices(
|
|
|
121
128
|
if isinstance(input_object, PointingSet) and isinstance(output_object, PointingSet):
|
|
122
129
|
raise ValueError("Cannot match indices between two PointingSet objects.")
|
|
123
130
|
|
|
124
|
-
# If
|
|
125
|
-
|
|
131
|
+
# If event_et is not specified, use epoch of the PointingSet, if present.
|
|
132
|
+
# The epoch will be in units of terrestrial time (TT) J2000 nanoseconds,
|
|
133
|
+
# which must be converted to ephemeris time (ET) for SPICE.
|
|
134
|
+
if event_et is None:
|
|
126
135
|
if isinstance(input_object, PointingSet):
|
|
127
|
-
|
|
136
|
+
event_et = ttj2000ns_to_et(input_object.data["epoch"].values)
|
|
128
137
|
elif isinstance(output_object, PointingSet):
|
|
129
|
-
|
|
138
|
+
event_et = ttj2000ns_to_et(output_object.data["epoch"].values)
|
|
130
139
|
else:
|
|
131
140
|
raise ValueError(
|
|
132
141
|
"Event time must be specified if both objects are SkyMaps."
|
|
@@ -137,11 +146,11 @@ def match_coords_to_indices(
|
|
|
137
146
|
|
|
138
147
|
# Transform the input pixel centers to the output frame
|
|
139
148
|
input_obj_az_el_output_frame = geometry.frame_transform_az_el(
|
|
140
|
-
et=
|
|
149
|
+
et=event_et,
|
|
141
150
|
az_el=input_obj_az_el_input_frame,
|
|
142
151
|
from_frame=input_object.spice_reference_frame,
|
|
143
152
|
to_frame=output_object.spice_reference_frame,
|
|
144
|
-
degrees=
|
|
153
|
+
degrees=True,
|
|
145
154
|
)
|
|
146
155
|
|
|
147
156
|
# The way indices are matched depends on the tiling type of the 2nd object
|
|
@@ -174,29 +183,17 @@ def match_coords_to_indices(
|
|
|
174
183
|
elif output_object.tiling_type is SkyTilingType.HEALPIX:
|
|
175
184
|
# To match to a Healpix tessellation, we need to use the healpy function ang2pix
|
|
176
185
|
# which directly returns the index on the output frame's Healpix tessellation.
|
|
177
|
-
"""
|
|
178
|
-
Leaving this as a placeholder for now, so we don't yet
|
|
179
|
-
need to add a healpy dependency. It will look something like the
|
|
180
|
-
following code, much simpler than the rectangular case:
|
|
181
|
-
|
|
182
|
-
```python
|
|
183
|
-
import healpy as hp
|
|
184
186
|
flat_indices_input_grid_output_frame = hp.ang2pix(
|
|
185
|
-
nside=
|
|
186
|
-
theta=
|
|
187
|
-
phi=
|
|
188
|
-
nest=
|
|
187
|
+
nside=output_object.nside,
|
|
188
|
+
theta=input_obj_az_el_output_frame[:, 0], # Lon in degrees
|
|
189
|
+
phi=input_obj_az_el_output_frame[:, 1], # Lat in degrees
|
|
190
|
+
nest=output_object.nested,
|
|
189
191
|
lonlat=True,
|
|
190
192
|
)
|
|
191
|
-
```
|
|
192
|
-
"""
|
|
193
|
-
raise NotImplementedError(
|
|
194
|
-
"Index matching for output tiling type Healpix is not yet implemented."
|
|
195
|
-
)
|
|
196
|
-
|
|
197
193
|
else:
|
|
198
194
|
raise ValueError(
|
|
199
195
|
"Tiling type of the output frame must be either RECTANGULAR or HEALPIX."
|
|
196
|
+
f"Received: {output_object.tiling_type}"
|
|
200
197
|
)
|
|
201
198
|
|
|
202
199
|
return flat_indices_input_grid_output_frame
|
|
@@ -207,6 +204,9 @@ class PointingSet(ABC):
|
|
|
207
204
|
"""
|
|
208
205
|
Abstract class to contain pointing set (PSET) data in the context of ENA sky maps.
|
|
209
206
|
|
|
207
|
+
Any spatial axes - (azimuth, elevation) for Rectangularly gridded tilings or
|
|
208
|
+
(pixel index) for Healpix - must be stored in the last axis/axes of each data array.
|
|
209
|
+
|
|
210
210
|
Parameters
|
|
211
211
|
----------
|
|
212
212
|
dataset : xr.Dataset
|
|
@@ -222,6 +222,49 @@ class PointingSet(ABC):
|
|
|
222
222
|
self.num_points = 0
|
|
223
223
|
self.az_el_points = np.zeros((self.num_points, 2))
|
|
224
224
|
self.data = xr.Dataset()
|
|
225
|
+
self.spatial_coords: tuple[str, ...] = ()
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def unwrapped_dims_dict(self) -> dict[str, tuple[str, ...]]:
|
|
229
|
+
"""
|
|
230
|
+
Get dimensions of each variable in the pointing set, with only 1 spatial dim.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
unwrapped_dims_dict : dict[str, tuple[str, ...]]
|
|
235
|
+
Dictionary of variable names and their dimensions, with only 1 spatial dim.
|
|
236
|
+
The generic pixel dimension is always included.
|
|
237
|
+
E.g.: {"counts": ("epoch", "energy_bin_center", "pixel")} .
|
|
238
|
+
"""
|
|
239
|
+
variable_dims = {}
|
|
240
|
+
for var_name in self.data.data_vars:
|
|
241
|
+
pset_dims = self.data[var_name].dims
|
|
242
|
+
non_spatial_dims = tuple(
|
|
243
|
+
dim for dim in pset_dims if dim not in self.spatial_coords
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
variable_dims[var_name] = (
|
|
247
|
+
*non_spatial_dims,
|
|
248
|
+
CoordNames.GENERIC_PIXEL.value,
|
|
249
|
+
)
|
|
250
|
+
return variable_dims
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def non_spatial_coords(self) -> dict[str, xr.DataArray]:
|
|
254
|
+
"""
|
|
255
|
+
Get the non-spatial coordinates of the pointing set.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
non_spatial_coords : dict[str, xr.DataArray]
|
|
260
|
+
Dictionary of coordinate names and their data arrays.
|
|
261
|
+
E.g.: {"epoch": [12345,], "energy": [100, 200, 300]} .
|
|
262
|
+
"""
|
|
263
|
+
non_spatial_coords = {}
|
|
264
|
+
for coord_name in self.data.coords:
|
|
265
|
+
if coord_name not in self.spatial_coords:
|
|
266
|
+
non_spatial_coords[coord_name] = self.data[coord_name]
|
|
267
|
+
return non_spatial_coords
|
|
225
268
|
|
|
226
269
|
def __repr__(self) -> str:
|
|
227
270
|
"""
|
|
@@ -233,34 +276,32 @@ class PointingSet(ABC):
|
|
|
233
276
|
String representation of the pointing set.
|
|
234
277
|
"""
|
|
235
278
|
return (
|
|
236
|
-
f"{self.__class__} PointingSet"
|
|
279
|
+
f"{self.__class__.__name__} PointingSet"
|
|
237
280
|
f"(spice_reference_frame={self.spice_reference_frame})"
|
|
238
281
|
)
|
|
239
282
|
|
|
240
283
|
|
|
241
|
-
class
|
|
284
|
+
class RectangularPointingSet(PointingSet):
|
|
242
285
|
"""
|
|
243
|
-
|
|
286
|
+
Pointing set object for rectangularly tiled data. Currently used in testing.
|
|
244
287
|
|
|
245
288
|
Parameters
|
|
246
289
|
----------
|
|
247
290
|
l1c_dataset : xr.Dataset | pathlib.Path | str
|
|
248
291
|
L1c xarray dataset containing the pointing set data or the path to the dataset.
|
|
249
|
-
Currently, the dataset is expected to be in a rectangular grid,
|
|
292
|
+
Currently, the dataset is expected to be tiled in a rectangular grid,
|
|
250
293
|
with data_vars indexed along the coordinates:
|
|
251
294
|
- 'epoch' : time value (1 value per PSET)
|
|
252
|
-
- '
|
|
253
|
-
- '
|
|
254
|
-
Some data_vars may additionally be indexed by energy bin;
|
|
255
|
-
however, only the spatial axes are used in this class.
|
|
295
|
+
- 'longitude' : (number of longitude/az bins in L1C)
|
|
296
|
+
- 'latitude' : (number of latitude/el bins in L1C)
|
|
256
297
|
spice_reference_frame : geometry.SpiceFrame
|
|
257
298
|
The reference Spice frame of the pointing set. Default is IMAP_DPS.
|
|
258
299
|
|
|
259
300
|
Raises
|
|
260
301
|
------
|
|
261
302
|
ValueError
|
|
262
|
-
If the
|
|
263
|
-
Or if the
|
|
303
|
+
If the longitude/az or latitude/el bin centers don't match the constructed grid.
|
|
304
|
+
Or if the longitude or latitude bin spacing is not uniform.
|
|
264
305
|
ValueError
|
|
265
306
|
If multiple epochs are found in the dataset.
|
|
266
307
|
"""
|
|
@@ -284,15 +325,16 @@ class UltraPointingSet(PointingSet):
|
|
|
284
325
|
if len(np.unique(self.epoch)) > 1:
|
|
285
326
|
raise ValueError("Multiple epochs found in the dataset.")
|
|
286
327
|
|
|
287
|
-
# The rest of the constructor handles the rectangular grid
|
|
288
|
-
# aspects of the Ultra PSET.
|
|
289
|
-
# NOTE: This may be changed to Healpix tessellation in the future
|
|
290
328
|
self.tiling_type = SkyTilingType.RECTANGULAR
|
|
329
|
+
self.spatial_coords = (
|
|
330
|
+
CoordNames.AZIMUTH_L1C.value,
|
|
331
|
+
CoordNames.ELEVATION_L1C.value,
|
|
332
|
+
)
|
|
291
333
|
|
|
292
334
|
# Ensure 1D axes grids are uniformly spaced,
|
|
293
335
|
# then set spacing based on data's azimuth bin spacing.
|
|
294
|
-
az_bin_delta = np.diff(self.data[
|
|
295
|
-
el_bin_delta = np.diff(self.data[
|
|
336
|
+
az_bin_delta = np.diff(self.data[CoordNames.AZIMUTH_L1C.value])
|
|
337
|
+
el_bin_delta = np.diff(self.data[CoordNames.ELEVATION_L1C.value])
|
|
296
338
|
if not np.allclose(az_bin_delta, az_bin_delta[0], atol=1e-10, rtol=0):
|
|
297
339
|
raise ValueError("Azimuth bin spacing is not uniform.")
|
|
298
340
|
if not np.allclose(el_bin_delta, el_bin_delta[0], atol=1e-10, rtol=0):
|
|
@@ -304,26 +346,26 @@ class UltraPointingSet(PointingSet):
|
|
|
304
346
|
)
|
|
305
347
|
self.spacing_deg = az_bin_delta[0]
|
|
306
348
|
|
|
307
|
-
# Build the azimuth and elevation grids with an AzElSkyGrid object
|
|
349
|
+
# Build the az/azimuth and el/elevation grids with an AzElSkyGrid object
|
|
308
350
|
# and check that the 1D axes match the dataset's az and el.
|
|
309
351
|
self.sky_grid = spatial_utils.AzElSkyGrid(
|
|
310
352
|
spacing_deg=self.spacing_deg,
|
|
311
353
|
)
|
|
312
354
|
|
|
313
355
|
for dim, constructed_bins in zip(
|
|
314
|
-
[
|
|
356
|
+
[CoordNames.AZIMUTH_L1C.value, CoordNames.ELEVATION_L1C.value],
|
|
315
357
|
[self.sky_grid.az_bin_midpoints, self.sky_grid.el_bin_midpoints],
|
|
316
358
|
):
|
|
317
359
|
if not np.allclose(
|
|
318
|
-
sorted(
|
|
319
|
-
self.data[
|
|
360
|
+
sorted(constructed_bins),
|
|
361
|
+
self.data[dim],
|
|
320
362
|
atol=1e-10,
|
|
321
363
|
rtol=0,
|
|
322
364
|
):
|
|
323
365
|
raise ValueError(
|
|
324
366
|
f"{dim} bin centers do not match."
|
|
325
|
-
f"Constructed: {
|
|
326
|
-
f"Dataset: {self.data[
|
|
367
|
+
f"Constructed: {constructed_bins}"
|
|
368
|
+
f"Dataset: {self.data[dim]}"
|
|
327
369
|
)
|
|
328
370
|
|
|
329
371
|
# Unwrap the az, el grids to series of points tiling the sky and combine them
|
|
@@ -344,6 +386,98 @@ class UltraPointingSet(PointingSet):
|
|
|
344
386
|
self.az_bin_edges = self.sky_grid.az_bin_edges
|
|
345
387
|
self.el_bin_edges = self.sky_grid.el_bin_edges
|
|
346
388
|
|
|
389
|
+
|
|
390
|
+
class UltraPointingSet(PointingSet):
|
|
391
|
+
"""
|
|
392
|
+
Pointing set object specifically for Healpix-tiled ULTRA data, nominally at Level1C.
|
|
393
|
+
|
|
394
|
+
Parameters
|
|
395
|
+
----------
|
|
396
|
+
l1c_dataset : xr.Dataset | pathlib.Path | str
|
|
397
|
+
L1c xarray dataset containing the pointing set data or the path to the dataset.
|
|
398
|
+
Currently, the dataset is expected to be tiled in a HEALPix tessellation,
|
|
399
|
+
with data_vars indexed along the coordinates:
|
|
400
|
+
- 'epoch' : time value (1 value per PSET, from the mean of the PSET)
|
|
401
|
+
- 'energy' : (number of energy bins in L1C)
|
|
402
|
+
- 'healpix_index' : HEALPix pixel index
|
|
403
|
+
Only the 'healpix_index' coordinate is used in this class for projection.
|
|
404
|
+
spice_reference_frame : geometry.SpiceFrame
|
|
405
|
+
The reference Spice frame of the pointing set. Default is IMAP_DPS.
|
|
406
|
+
|
|
407
|
+
Raises
|
|
408
|
+
------
|
|
409
|
+
ValueError
|
|
410
|
+
If the longitude/az or latitude/el bin centers don't match the constructed grid.
|
|
411
|
+
Or if the longitude or latitude bin spacing is not uniform.
|
|
412
|
+
ValueError
|
|
413
|
+
If multiple epochs are found in the dataset.
|
|
414
|
+
"""
|
|
415
|
+
|
|
416
|
+
def __init__(
|
|
417
|
+
self,
|
|
418
|
+
l1c_dataset: xr.Dataset | pathlib.Path | str,
|
|
419
|
+
spice_reference_frame: geometry.SpiceFrame = geometry.SpiceFrame.IMAP_DPS,
|
|
420
|
+
):
|
|
421
|
+
# Store the reference frame of the pointing set
|
|
422
|
+
self.spice_reference_frame = spice_reference_frame
|
|
423
|
+
|
|
424
|
+
# Read in the data and store the xarray dataset as data attr
|
|
425
|
+
if isinstance(l1c_dataset, (str, pathlib.Path)):
|
|
426
|
+
self.data = load_cdf(pathlib.Path(l1c_dataset))
|
|
427
|
+
elif isinstance(l1c_dataset, xr.Dataset):
|
|
428
|
+
self.data = l1c_dataset
|
|
429
|
+
|
|
430
|
+
# A PSET must have a single epoch
|
|
431
|
+
self.epoch = self.data["epoch"].values
|
|
432
|
+
if len(np.unique(self.epoch)) > 1:
|
|
433
|
+
raise ValueError("Multiple epochs found in the dataset.")
|
|
434
|
+
|
|
435
|
+
# Set the tiling type and number of points
|
|
436
|
+
self.tiling_type = SkyTilingType.HEALPIX
|
|
437
|
+
self.spatial_coords = (CoordNames.HEALPIX_INDEX.value,)
|
|
438
|
+
self.num_points = self.data[CoordNames.HEALPIX_INDEX.value].size
|
|
439
|
+
self.nside = hp.npix_to_nside(self.num_points)
|
|
440
|
+
|
|
441
|
+
# Determine if the HEALPix tessellation is nested, default is False
|
|
442
|
+
self.nested = bool(
|
|
443
|
+
self.data[CoordNames.HEALPIX_INDEX.value].attrs.get("nested", False)
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Get the azimuth and elevation coordinates of the healpix pixel centers (deg)
|
|
447
|
+
self.azimuth_pixel_center, self.elevation_pixel_center = hp.pix2ang(
|
|
448
|
+
nside=self.nside,
|
|
449
|
+
ipix=np.arange(self.num_points),
|
|
450
|
+
nest=self.nested,
|
|
451
|
+
lonlat=True,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# Verify that the azimuth and elevation of the healpix pixel centers
|
|
455
|
+
# match the data's azimuth and elevation bin centers.
|
|
456
|
+
# NOTE: They can have different names in the L1C dataset
|
|
457
|
+
# (e.g. "longitude"/"latitude" vs "azimuth"/"elevation").
|
|
458
|
+
for dim, constructed_bins in zip(
|
|
459
|
+
[CoordNames.AZIMUTH_L1C.value, CoordNames.ELEVATION_L1C.value],
|
|
460
|
+
[self.azimuth_pixel_center, self.elevation_pixel_center],
|
|
461
|
+
):
|
|
462
|
+
if not np.allclose(
|
|
463
|
+
self.data[dim],
|
|
464
|
+
constructed_bins,
|
|
465
|
+
atol=1e-10,
|
|
466
|
+
rtol=0,
|
|
467
|
+
):
|
|
468
|
+
raise ValueError(
|
|
469
|
+
f"{dim} pixel centers do not match the data's {dim} bin centers."
|
|
470
|
+
f"Constructed: {constructed_bins}"
|
|
471
|
+
f"Dataset: {self.data[dim]}"
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
# The coordinates of the healpix pixel centers are stored as a 2D array
|
|
475
|
+
# of shape (num_points, 2) where column 0 is the lon/az
|
|
476
|
+
# and column 1 is the lat/el.
|
|
477
|
+
self.az_el_points = np.column_stack(
|
|
478
|
+
(self.azimuth_pixel_center, self.elevation_pixel_center)
|
|
479
|
+
)
|
|
480
|
+
|
|
347
481
|
def __repr__(self) -> str:
|
|
348
482
|
"""
|
|
349
483
|
Return a string representation of the UltraPointingSet.
|
|
@@ -362,65 +496,87 @@ class UltraPointingSet(PointingSet):
|
|
|
362
496
|
|
|
363
497
|
# Define the Map classes
|
|
364
498
|
class AbstractSkyMap(ABC):
|
|
365
|
-
"""
|
|
499
|
+
"""
|
|
500
|
+
Abstract base class to contain map data in the context of ENA sky maps.
|
|
501
|
+
|
|
502
|
+
Data values are stored internally in an xarray Dataset, in the .data_1d attribute.
|
|
503
|
+
where the final (-1) axis is the only spatial dimension.
|
|
504
|
+
If the map is rectangular, this axis is the raveled 2D grid.
|
|
505
|
+
If the map is Healpix, this axis is the 1D array of Healpix pixel indices.
|
|
506
|
+
|
|
507
|
+
The data can be also accessed via the to_dataset method, which rewraps the data to
|
|
508
|
+
a 2D grid shape if the map is rectangular and formats the data as an xarray
|
|
509
|
+
Dataset with the correct dims and coords.
|
|
510
|
+
"""
|
|
366
511
|
|
|
367
512
|
@abstractmethod
|
|
368
513
|
def __init__(self) -> None:
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
514
|
+
self.tiling_type: SkyTilingType
|
|
515
|
+
self.sky_grid: spatial_utils.AzElSkyGrid
|
|
516
|
+
self.num_points: int
|
|
517
|
+
self.non_spatial_coords: dict[str, xr.DataArray | NDArray]
|
|
518
|
+
self.spatial_coords: dict[str, xr.DataArray | NDArray]
|
|
519
|
+
self.binning_grid_shape: tuple[int, ...]
|
|
520
|
+
self.data_1d: xr.Dataset
|
|
521
|
+
|
|
522
|
+
def to_dataset(self) -> xr.Dataset:
|
|
372
523
|
"""
|
|
373
|
-
|
|
524
|
+
Get the SkyMap data as a formatted xarray Dataset.
|
|
374
525
|
|
|
375
526
|
Returns
|
|
376
527
|
-------
|
|
377
|
-
|
|
378
|
-
|
|
528
|
+
xr.Dataset
|
|
529
|
+
The SkyMap data as a formatted xarray Dataset with dims and coords.
|
|
530
|
+
If the SkyMap is empty, an empty xarray Dataset is returned.
|
|
531
|
+
If the SkyMap is Rectangular, the data is rewrapped to a 2D grid of
|
|
532
|
+
lon/lat (AKA az/el) coordinates.
|
|
533
|
+
If the SkyMap is Healpix, the data is unchanged from the data_1d, but
|
|
534
|
+
the pixel coordinate is renamed to CoordNames.HEALPIX_INDEX.value.
|
|
379
535
|
"""
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
NOTE: Internally, the map is stored as a 1D array of pixels.
|
|
388
|
-
|
|
389
|
-
Parameters
|
|
390
|
-
----------
|
|
391
|
-
spacing_deg : float
|
|
392
|
-
The spacing of the rectangular grid in degrees.
|
|
393
|
-
spice_frame : geometry.SpiceFrame
|
|
394
|
-
The reference Spice frame of the map.
|
|
395
|
-
"""
|
|
396
|
-
|
|
397
|
-
def __init__(
|
|
398
|
-
self,
|
|
399
|
-
spacing_deg: float,
|
|
400
|
-
spice_frame: geometry.SpiceFrame,
|
|
401
|
-
):
|
|
402
|
-
# Define the core properties of the map:
|
|
403
|
-
self.tiling_type = SkyTilingType.RECTANGULAR # Type of tiling of the sky
|
|
404
|
-
self.spacing_deg = spacing_deg
|
|
405
|
-
self.spice_reference_frame = spice_frame
|
|
406
|
-
self.sky_grid = spatial_utils.AzElSkyGrid(
|
|
407
|
-
spacing_deg=self.spacing_deg,
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
# Solid angles of each pixel in the map grid in units of steradians
|
|
411
|
-
self.solid_angle_grid = spatial_utils.build_solid_angle_map(
|
|
412
|
-
spacing_deg=self.spacing_deg,
|
|
413
|
-
)
|
|
414
|
-
|
|
415
|
-
# Unwrap the az, el, solid angle grids to series of points tiling the sky
|
|
416
|
-
az_points = self.sky_grid.az_grid.ravel()
|
|
417
|
-
el_points = self.sky_grid.el_grid.ravel()
|
|
418
|
-
self.az_el_points = np.column_stack((az_points, el_points))
|
|
419
|
-
self.solid_angle_points = self.solid_angle_grid.ravel()
|
|
420
|
-
self.num_points = self.az_el_points.shape[0]
|
|
536
|
+
if len(self.data_1d.data_vars) == 0:
|
|
537
|
+
# If the map is empty, return an empty xarray Dataset,
|
|
538
|
+
# with the unaltered spatial coords of the map
|
|
539
|
+
return xr.Dataset(
|
|
540
|
+
{},
|
|
541
|
+
coords={**self.spatial_coords},
|
|
542
|
+
)
|
|
421
543
|
|
|
422
|
-
|
|
423
|
-
|
|
544
|
+
if self.tiling_type is SkyTilingType.HEALPIX:
|
|
545
|
+
# return the data_1d as is, but with the pixel coordinate
|
|
546
|
+
# renamed to CoordNames.HEALPIX_INDEX.value
|
|
547
|
+
return self.data_1d.rename(
|
|
548
|
+
{CoordNames.GENERIC_PIXEL.value: CoordNames.HEALPIX_INDEX.value}
|
|
549
|
+
)
|
|
550
|
+
elif self.tiling_type is SkyTilingType.RECTANGULAR:
|
|
551
|
+
# Rewrap each data array in the data_1d to the original 2D grid shape
|
|
552
|
+
rewrapped_data = {}
|
|
553
|
+
for key in self.data_1d.data_vars:
|
|
554
|
+
# drop pixel dim from the end, and add the spatial coords as dims
|
|
555
|
+
rewrapped_dims = [
|
|
556
|
+
dim
|
|
557
|
+
for dim in self.data_1d[key].dims
|
|
558
|
+
if dim != CoordNames.GENERIC_PIXEL.value
|
|
559
|
+
]
|
|
560
|
+
rewrapped_dims.extend(self.spatial_coords.keys())
|
|
561
|
+
rewrapped_data[key] = xr.DataArray(
|
|
562
|
+
spatial_utils.rewrap_even_spaced_az_el_grid(
|
|
563
|
+
self.data_1d[key].values,
|
|
564
|
+
self.binning_grid_shape,
|
|
565
|
+
),
|
|
566
|
+
dims=rewrapped_dims,
|
|
567
|
+
)
|
|
568
|
+
# Add the output coordinates to the rewrapped data, excluding the pixel
|
|
569
|
+
self.non_spatial_coords.update(
|
|
570
|
+
{
|
|
571
|
+
key: self.data_1d[key].coords[key]
|
|
572
|
+
for key in self.data_1d[key].coords
|
|
573
|
+
if key != CoordNames.GENERIC_PIXEL.value
|
|
574
|
+
}
|
|
575
|
+
)
|
|
576
|
+
return xr.Dataset(
|
|
577
|
+
rewrapped_data,
|
|
578
|
+
coords={**self.non_spatial_coords, **self.spatial_coords},
|
|
579
|
+
)
|
|
424
580
|
|
|
425
581
|
def project_pset_values_to_map(
|
|
426
582
|
self,
|
|
@@ -440,7 +596,7 @@ class RectangularSkyMap(AbstractSkyMap):
|
|
|
440
596
|
pointing_set : PointingSet
|
|
441
597
|
The pointing set containing the values to project to the map.
|
|
442
598
|
value_keys : list[tuple[str, IndexMatchMethod]] | None
|
|
443
|
-
The keys of the values to project to the map.
|
|
599
|
+
The keys of the values in the PointingSet to project to the map.
|
|
444
600
|
Ex.: ["counts", "flux"]
|
|
445
601
|
data_vars named each key must be present, and of the same dimensionality in
|
|
446
602
|
each pointing set which is to be projected to the map.
|
|
@@ -456,45 +612,185 @@ class RectangularSkyMap(AbstractSkyMap):
|
|
|
456
612
|
"""
|
|
457
613
|
if value_keys is None:
|
|
458
614
|
value_keys = list(pointing_set.data.data_vars.keys())
|
|
459
|
-
|
|
460
615
|
for value_key in value_keys:
|
|
461
616
|
if value_key not in pointing_set.data.data_vars:
|
|
462
617
|
raise ValueError(f"Value key {value_key} not found in pointing set.")
|
|
463
618
|
|
|
464
|
-
# Determine the indices of the sky map grid that correspond to
|
|
465
|
-
# each pixel in the pointing set.
|
|
466
619
|
if index_match_method is IndexMatchMethod.PUSH:
|
|
620
|
+
# Determine the indices of the sky map grid that correspond to
|
|
621
|
+
# each pixel in the pointing set.
|
|
467
622
|
matched_indices_push = match_coords_to_indices(
|
|
468
623
|
input_object=pointing_set,
|
|
469
624
|
output_object=self,
|
|
470
625
|
)
|
|
626
|
+
elif index_match_method is IndexMatchMethod.PULL:
|
|
627
|
+
# Determine the indices of the pointing set grid that correspond to
|
|
628
|
+
# each pixel in the sky map.
|
|
629
|
+
matched_indices_pull = match_coords_to_indices(
|
|
630
|
+
input_object=self,
|
|
631
|
+
output_object=pointing_set,
|
|
632
|
+
)
|
|
633
|
+
else:
|
|
634
|
+
raise NotImplementedError(
|
|
635
|
+
"Only PUSH and PULL index matching methods are supported."
|
|
636
|
+
)
|
|
471
637
|
|
|
472
638
|
for value_key in value_keys:
|
|
639
|
+
pset_values = pointing_set.data[value_key]
|
|
640
|
+
|
|
473
641
|
# If multiple spatial axes present
|
|
474
642
|
# (i.e (az, el) for rectangular coordinate PSET),
|
|
475
643
|
# flatten them in the values array to match the raveled indices
|
|
476
|
-
|
|
477
|
-
|
|
644
|
+
non_spatial_axes_shape = tuple(
|
|
645
|
+
size
|
|
646
|
+
for key, size in pset_values.sizes.items()
|
|
647
|
+
if key not in pointing_set.spatial_coords
|
|
648
|
+
)
|
|
649
|
+
raveled_pset_data = pset_values.data.reshape(
|
|
650
|
+
*non_spatial_axes_shape,
|
|
651
|
+
pointing_set.num_points,
|
|
478
652
|
)
|
|
479
|
-
|
|
653
|
+
|
|
654
|
+
if value_key not in self.data_1d.data_vars:
|
|
480
655
|
# Initialize the map data array if it doesn't exist (values start at 0)
|
|
481
|
-
output_shape = (
|
|
482
|
-
self.
|
|
656
|
+
output_shape = (*raveled_pset_data.shape[:-1], self.num_points)
|
|
657
|
+
self.data_1d[value_key] = xr.DataArray(
|
|
658
|
+
np.zeros(output_shape),
|
|
659
|
+
dims=pointing_set.unwrapped_dims_dict[value_key],
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
# Make coordinates for the map data array if they don't exist
|
|
663
|
+
self.data_1d.coords.update(
|
|
664
|
+
{
|
|
665
|
+
dim: pointing_set.data[dim]
|
|
666
|
+
for dim in self.data_1d[value_key].dims
|
|
667
|
+
if dim not in self.data_1d.coords
|
|
668
|
+
}
|
|
669
|
+
)
|
|
483
670
|
|
|
484
671
|
if index_match_method is IndexMatchMethod.PUSH:
|
|
672
|
+
# Bin the values at the matched indices. There may be multiple
|
|
673
|
+
# pointing set pixels that correspond to the same sky map pixel.
|
|
485
674
|
pointing_projected_values = map_utils.bin_single_array_at_indices(
|
|
486
675
|
value_array=raveled_pset_data,
|
|
487
|
-
projection_grid_shape=
|
|
488
|
-
len(self.sky_grid.az_bin_midpoints),
|
|
489
|
-
len(self.sky_grid.el_bin_midpoints),
|
|
490
|
-
),
|
|
676
|
+
projection_grid_shape=self.binning_grid_shape,
|
|
491
677
|
projection_indices=matched_indices_push,
|
|
492
678
|
)
|
|
679
|
+
elif index_match_method is IndexMatchMethod.PULL:
|
|
680
|
+
# We know that there will only be one value per sky map pixel,
|
|
681
|
+
# so we can use the matched indices directly
|
|
682
|
+
pointing_projected_values = raveled_pset_data[..., matched_indices_pull]
|
|
493
683
|
else:
|
|
494
684
|
raise NotImplementedError(
|
|
495
|
-
"
|
|
685
|
+
"Only PUSH and PULL index matching methods are supported."
|
|
496
686
|
)
|
|
497
|
-
|
|
687
|
+
|
|
688
|
+
self.data_1d[value_key] += pointing_projected_values
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
class RectangularSkyMap(AbstractSkyMap):
|
|
692
|
+
"""
|
|
693
|
+
Map which tiles the sky with a 2D rectangular grid of azimuth/elevation pixels.
|
|
694
|
+
|
|
695
|
+
Parameters
|
|
696
|
+
----------
|
|
697
|
+
spacing_deg : float
|
|
698
|
+
The spacing of the rectangular grid in degrees.
|
|
699
|
+
spice_frame : geometry.SpiceFrame
|
|
700
|
+
The reference Spice frame of the map.
|
|
701
|
+
|
|
702
|
+
Notes
|
|
703
|
+
-----
|
|
704
|
+
Internally, the map is stored as a 1D array of pixels, and all data arrays
|
|
705
|
+
are stored with the final (-1) axis as the only spatial axis, representing the
|
|
706
|
+
pixel index in the 1D array (See Figs 1-2, which demonstrate the 1D pixel index
|
|
707
|
+
corresponding to the 2D grid of coordinates).
|
|
708
|
+
|
|
709
|
+
^ |15, 75|45, 75|75, 75|105, 75|...|255, 75|285, 75|315, 75|345, 75|
|
|
710
|
+
| |15, 45|45, 45|75, 45|105, 45|...|255, 45|285, 45|315, 45|345, 45|
|
|
711
|
+
| |15, 15|45, 15|75, 15|105, 15|...|255, 15|285, 15|315, 15|345, 15|
|
|
712
|
+
| |15, -15|45, -15|75, -15|105, -15|...|255, -15|285, -15|315, -15|345, -15|
|
|
713
|
+
| |15, -45|45, -45|75, -45|105, -45|...|255, -45|285, -45|315, -45|345, -45|
|
|
714
|
+
| |15, -75|45, -75|75, -75|105, -75|...|255, -75|285, -75|315, -75|345, -75|
|
|
715
|
+
|
|
|
716
|
+
---------------------------------------------------------------> Azimuth (degrees)
|
|
717
|
+
Elevation (degrees)
|
|
718
|
+
|
|
719
|
+
Fig. 1: Example of a rectangular grid of pixels in azimuth and elevation coordinates
|
|
720
|
+
in degrees, with a spacing of 30 degrees. There will be 12 azimuth bins and 6
|
|
721
|
+
elevation bins in this example, resulting in 72 pixels in the map.
|
|
722
|
+
|
|
723
|
+
A multidimentional value (e.g. counts, with energy levels at each pixel)
|
|
724
|
+
will be stored as a 2D array with the first axis as the energy dimension and the
|
|
725
|
+
second axis as the pixel index.
|
|
726
|
+
|
|
727
|
+
^ |5|11|17|23|29|35|41|47|53|59|65|71|
|
|
728
|
+
| |4|10|16|22|28|34|40|46|52|58|64|70|
|
|
729
|
+
| |3|9 |15|21|27|33|39|45|51|57|63|69|
|
|
730
|
+
| |2|8 |14|20|26|32|38|44|50|56|62|68|
|
|
731
|
+
| |1|7 |13|19|25|31|37|43|49|55|61|67|
|
|
732
|
+
| |0|6 |12|18|24|30|36|42|48|54|60|66|
|
|
733
|
+
---------------------------------------> Azimuth
|
|
734
|
+
Elevation
|
|
735
|
+
|
|
736
|
+
Fig. 2: The 1D indices of the pixels in Fig. 1.
|
|
737
|
+
Note that the indices are raveled from the 2D grid of (az, el) such that as one
|
|
738
|
+
increases in pixel index, elevation increments first, then azimuth.
|
|
739
|
+
"""
|
|
740
|
+
|
|
741
|
+
def __init__(
|
|
742
|
+
self,
|
|
743
|
+
spacing_deg: float,
|
|
744
|
+
spice_frame: geometry.SpiceFrame,
|
|
745
|
+
):
|
|
746
|
+
# Define the core properties of the map:
|
|
747
|
+
self.tiling_type = SkyTilingType.RECTANGULAR # Type of tiling of the sky
|
|
748
|
+
|
|
749
|
+
# The reference Spice frame of the map, in which angles are defined
|
|
750
|
+
self.spice_reference_frame = spice_frame
|
|
751
|
+
|
|
752
|
+
# Angular spacing of the map grid (degrees) defines the number, size of pixels.
|
|
753
|
+
self.spacing_deg = spacing_deg
|
|
754
|
+
self.sky_grid = spatial_utils.AzElSkyGrid(
|
|
755
|
+
spacing_deg=self.spacing_deg,
|
|
756
|
+
)
|
|
757
|
+
# The shape of the map (num_az_bins, num_el_bins) is used to bin the data
|
|
758
|
+
self.binning_grid_shape = self.sky_grid.grid_shape
|
|
759
|
+
|
|
760
|
+
self.non_spatial_coords = {}
|
|
761
|
+
self.spatial_coords = {
|
|
762
|
+
CoordNames.AZIMUTH_L1C.value: xr.DataArray(
|
|
763
|
+
self.sky_grid.az_bin_midpoints,
|
|
764
|
+
dims=[CoordNames.AZIMUTH_L1C.value],
|
|
765
|
+
attrs={"units": "degrees"},
|
|
766
|
+
),
|
|
767
|
+
CoordNames.ELEVATION_L1C.value: xr.DataArray(
|
|
768
|
+
self.sky_grid.el_bin_midpoints,
|
|
769
|
+
dims=[CoordNames.ELEVATION_L1C.value],
|
|
770
|
+
attrs={"units": "degrees"},
|
|
771
|
+
),
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
# Unwrap the az, el grids to 1D array of points tiling the sky
|
|
775
|
+
az_points = self.sky_grid.az_grid.ravel()
|
|
776
|
+
el_points = self.sky_grid.el_grid.ravel()
|
|
777
|
+
|
|
778
|
+
# Stack so axis 0 is different pixels, and axis 1 is (az, el) of the pixel
|
|
779
|
+
self.az_el_points = np.column_stack((az_points, el_points))
|
|
780
|
+
self.num_points = self.az_el_points.shape[0]
|
|
781
|
+
|
|
782
|
+
# Calculate solid angles of each pixel in the map grid in units of steradians
|
|
783
|
+
self.solid_angle_grid = spatial_utils.build_solid_angle_map(
|
|
784
|
+
spacing_deg=self.spacing_deg,
|
|
785
|
+
)
|
|
786
|
+
self.solid_angle_points = self.solid_angle_grid.ravel()
|
|
787
|
+
|
|
788
|
+
# Initialize xarray Dataset to store map data projected from pointing sets
|
|
789
|
+
self.data_1d: xr.Dataset = xr.Dataset(
|
|
790
|
+
coords={
|
|
791
|
+
CoordNames.GENERIC_PIXEL.value: np.arange(self.num_points),
|
|
792
|
+
}
|
|
793
|
+
)
|
|
498
794
|
|
|
499
795
|
def __repr__(self) -> str:
|
|
500
796
|
"""
|
|
@@ -506,14 +802,82 @@ class RectangularSkyMap(AbstractSkyMap):
|
|
|
506
802
|
String representation of the RectangularSkyMap.
|
|
507
803
|
"""
|
|
508
804
|
return (
|
|
509
|
-
"
|
|
805
|
+
f"{self.__class__.__name__}\n\t(reference_frame="
|
|
510
806
|
f"{self.spice_reference_frame.name} ({self.spice_reference_frame.value}), "
|
|
511
807
|
f"spacing_deg={self.spacing_deg}, num_points={self.num_points})"
|
|
512
808
|
)
|
|
513
809
|
|
|
514
810
|
|
|
515
|
-
|
|
516
|
-
|
|
811
|
+
class HealpixSkyMap(AbstractSkyMap):
|
|
812
|
+
"""
|
|
813
|
+
Map which tiles the sky with a Healpix tessellation of equal-area pixels.
|
|
814
|
+
|
|
815
|
+
Parameters
|
|
816
|
+
----------
|
|
817
|
+
nside : int
|
|
818
|
+
The nside parameter of the Healpix tessellation.
|
|
819
|
+
spice_frame : geometry.SpiceFrame
|
|
820
|
+
The reference Spice frame of the map.
|
|
821
|
+
nested : bool, optional
|
|
822
|
+
Whether the Healpix tessellation is nested. Default is False.
|
|
823
|
+
"""
|
|
824
|
+
|
|
825
|
+
def __init__(
|
|
826
|
+
self, nside: int, spice_frame: geometry.SpiceFrame, nested: bool = False
|
|
827
|
+
):
|
|
828
|
+
# Define the core properties of the map:
|
|
829
|
+
self.tiling_type = SkyTilingType.HEALPIX
|
|
830
|
+
self.spice_reference_frame = spice_frame
|
|
831
|
+
|
|
832
|
+
# Tile the sky with a Healpix tessellation. Defined by nside, nested parameters.
|
|
833
|
+
self.nside = nside
|
|
834
|
+
self.nested = nested
|
|
835
|
+
|
|
836
|
+
# Calculate how many pixels cover the sky and the approximate resolution (deg)
|
|
837
|
+
self.num_points = hp.nside2npix(nside)
|
|
838
|
+
self.approx_resolution = np.rad2deg(hp.nside2resol(nside, arcmin=False))
|
|
839
|
+
# Define binning_grid_shape for consistency with RectangularSkyMap
|
|
840
|
+
self.binning_grid_shape = (self.num_points,)
|
|
841
|
+
self.spatial_coords = {
|
|
842
|
+
CoordNames.HEALPIX_INDEX.value: xr.DataArray(
|
|
843
|
+
np.arange(self.num_points),
|
|
844
|
+
dims=[CoordNames.HEALPIX_INDEX.value],
|
|
845
|
+
)
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
# The centers of each pixel in the Healpix tessellation in azimuth (az) and
|
|
849
|
+
# elevation (el) coordinates (degrees) within the map's Spice frame.
|
|
850
|
+
pixel_az, pixel_el = hp.pix2ang(
|
|
851
|
+
nside=nside, ipix=np.arange(self.num_points), nest=nested, lonlat=True
|
|
852
|
+
)
|
|
853
|
+
# Stack so axis 0 is different pixels, and axis 1 is (az, el) of the pixel
|
|
854
|
+
self.az_el_points = np.column_stack((pixel_az, pixel_el))
|
|
855
|
+
|
|
856
|
+
# Tracks Per-Pixel Solid Angle in steradians.
|
|
857
|
+
self.solid_angle = hp.nside2pixarea(nside, degrees=False)
|
|
858
|
+
|
|
859
|
+
# Solid angle is equal at all pixels, but define
|
|
860
|
+
# solid_angle_points to be consistent with RectangularSkyMap
|
|
861
|
+
self.solid_angle_points = np.full(self.num_points, self.solid_angle)
|
|
862
|
+
|
|
863
|
+
# Initialize xarray Dataset to store map data projected from pointing sets
|
|
864
|
+
self.data_1d: xr.Dataset = xr.Dataset(
|
|
865
|
+
coords={
|
|
866
|
+
CoordNames.GENERIC_PIXEL.value: np.arange(self.num_points),
|
|
867
|
+
}
|
|
868
|
+
)
|
|
517
869
|
|
|
518
|
-
|
|
519
|
-
|
|
870
|
+
def __repr__(self) -> str:
|
|
871
|
+
"""
|
|
872
|
+
Return a string representation of the HealpixSkyMap.
|
|
873
|
+
|
|
874
|
+
Returns
|
|
875
|
+
-------
|
|
876
|
+
str
|
|
877
|
+
String representation of the HealpixSkyMap.
|
|
878
|
+
"""
|
|
879
|
+
return (
|
|
880
|
+
f"{self.__class__.__name__}\n\t(reference_frame="
|
|
881
|
+
f"{self.spice_reference_frame.name} ({self.spice_reference_frame.value}), "
|
|
882
|
+
f"nside={self.nside}, num_points={self.num_points})"
|
|
883
|
+
)
|