imap-processing 0.11.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.
- imap_processing/__init__.py +11 -11
- imap_processing/_version.py +2 -2
- imap_processing/ccsds/ccsds_data.py +1 -2
- imap_processing/ccsds/excel_to_xtce.py +66 -18
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +24 -40
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +934 -42
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1846 -128
- 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 +27 -14
- 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 +25 -9
- imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +6 -4
- imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +3 -3
- 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 +23 -20
- 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_swapi_variable_attrs.yaml +22 -0
- 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 +64 -52
- imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +71 -47
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +180 -19
- imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +5045 -41
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +80 -17
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +32 -57
- imap_processing/cdf/utils.py +52 -38
- imap_processing/cli.py +477 -233
- imap_processing/codice/codice_l1a.py +466 -131
- imap_processing/codice/codice_l1b.py +51 -152
- imap_processing/codice/constants.py +1360 -569
- imap_processing/codice/decompress.py +2 -6
- imap_processing/ena_maps/ena_maps.py +1103 -146
- 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 +55 -52
- imap_processing/glows/l1a/glows_l1a.py +28 -99
- imap_processing/glows/l1a/glows_l1a_data.py +2 -2
- imap_processing/glows/l1b/glows_l1b.py +1 -4
- imap_processing/glows/l1b/glows_l1b_data.py +1 -3
- imap_processing/glows/l2/glows_l2.py +2 -5
- imap_processing/hi/l1a/hi_l1a.py +54 -29
- 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 +111 -82
- imap_processing/hi/l1c/hi_l1c.py +416 -32
- imap_processing/hi/utils.py +58 -12
- imap_processing/hit/ancillary/imap_hit_l1b-to-l2-sector-dt0-factors_20250219_v002.csv +81 -0
- 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 +235 -5
- imap_processing/hit/l0/constants.py +20 -11
- imap_processing/hit/l0/decom_hit.py +21 -5
- imap_processing/hit/l1a/hit_l1a.py +71 -75
- imap_processing/hit/l1b/constants.py +321 -0
- imap_processing/hit/l1b/hit_l1b.py +377 -67
- imap_processing/hit/l2/constants.py +318 -0
- imap_processing/hit/l2/hit_l2.py +723 -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 +374 -0
- imap_processing/ialirt/l0/process_swapi.py +69 -0
- imap_processing/ialirt/l0/process_swe.py +548 -0
- imap_processing/ialirt/packet_definitions/ialirt.xml +216 -208
- imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +1 -1
- imap_processing/ialirt/packet_definitions/ialirt_codicelo.xml +1 -1
- imap_processing/ialirt/packet_definitions/ialirt_mag.xml +115 -0
- imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +14 -14
- 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 +33 -0
- imap_processing/idex/idex_l0.py +22 -8
- imap_processing/idex/idex_l1a.py +81 -51
- imap_processing/idex/idex_l1b.py +13 -39
- imap_processing/idex/idex_l2a.py +823 -0
- imap_processing/idex/idex_l2b.py +120 -0
- imap_processing/idex/idex_variable_unpacking_and_eu_conversion.csv +11 -11
- imap_processing/idex/packet_definitions/idex_housekeeping_packet_definition.xml +9130 -0
- imap_processing/lo/l0/lo_science.py +7 -2
- imap_processing/lo/l1a/lo_l1a.py +1 -5
- imap_processing/lo/l1b/lo_l1b.py +702 -29
- imap_processing/lo/l1b/tof_conversions.py +11 -0
- imap_processing/lo/l1c/lo_l1c.py +1 -4
- imap_processing/mag/constants.py +51 -0
- imap_processing/mag/imap_mag_sdc_configuration_v001.py +8 -0
- imap_processing/mag/l0/decom_mag.py +10 -3
- imap_processing/mag/l1a/mag_l1a.py +23 -19
- imap_processing/mag/l1a/mag_l1a_data.py +35 -10
- imap_processing/mag/l1b/mag_l1b.py +259 -50
- imap_processing/mag/l1c/interpolation_methods.py +388 -0
- imap_processing/mag/l1c/mag_l1c.py +621 -17
- imap_processing/mag/l2/mag_l2.py +140 -0
- imap_processing/mag/l2/mag_l2_data.py +288 -0
- imap_processing/quality_flags.py +1 -0
- imap_processing/spacecraft/packet_definitions/scid_x252.xml +538 -0
- imap_processing/spacecraft/quaternions.py +121 -0
- imap_processing/spice/geometry.py +19 -22
- imap_processing/spice/kernels.py +0 -276
- imap_processing/spice/pointing_frame.py +257 -0
- imap_processing/spice/repoint.py +149 -0
- imap_processing/spice/spin.py +38 -33
- imap_processing/spice/time.py +24 -0
- imap_processing/swapi/l1/swapi_l1.py +20 -12
- imap_processing/swapi/l2/swapi_l2.py +116 -5
- imap_processing/swapi/swapi_utils.py +32 -0
- imap_processing/swe/l1a/swe_l1a.py +44 -12
- imap_processing/swe/l1a/swe_science.py +13 -13
- imap_processing/swe/l1b/swe_l1b.py +898 -23
- imap_processing/swe/l2/swe_l2.py +75 -136
- imap_processing/swe/packet_definitions/swe_packet_definition.xml +1121 -1
- imap_processing/swe/utils/swe_constants.py +64 -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 +24 -21
- imap_processing/tests/cdf/test_data/imap_instrument2_global_cdf_attrs.yaml +0 -2
- imap_processing/tests/cdf/test_utils.py +14 -16
- imap_processing/tests/codice/conftest.py +44 -33
- 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 +126 -53
- imap_processing/tests/codice/test_codice_l1b.py +6 -7
- imap_processing/tests/codice/test_decompress.py +4 -4
- imap_processing/tests/conftest.py +239 -27
- imap_processing/tests/ena_maps/conftest.py +51 -0
- imap_processing/tests/ena_maps/test_ena_maps.py +1068 -110
- imap_processing/tests/ena_maps/test_map_utils.py +66 -43
- imap_processing/tests/ena_maps/test_spatial_utils.py +17 -21
- imap_processing/tests/glows/conftest.py +10 -14
- imap_processing/tests/glows/test_glows_decom.py +4 -4
- imap_processing/tests/glows/test_glows_l1a_cdf.py +6 -27
- imap_processing/tests/glows/test_glows_l1a_data.py +6 -8
- imap_processing/tests/glows/test_glows_l1b.py +11 -11
- imap_processing/tests/glows/test_glows_l1b_data.py +5 -5
- imap_processing/tests/glows/test_glows_l2.py +2 -8
- imap_processing/tests/hi/conftest.py +1 -1
- 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 +22 -27
- imap_processing/tests/hi/test_hi_l1c.py +249 -18
- imap_processing/tests/hi/test_l1a.py +35 -7
- imap_processing/tests/hi/test_science_direct_event.py +3 -3
- imap_processing/tests/hi/test_utils.py +24 -2
- imap_processing/tests/hit/helpers/l1_validation.py +74 -73
- 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 +5 -1
- imap_processing/tests/hit/test_hit_l1a.py +32 -36
- imap_processing/tests/hit/test_hit_l1b.py +300 -81
- imap_processing/tests/hit/test_hit_l2.py +716 -0
- imap_processing/tests/hit/test_hit_utils.py +184 -7
- imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -62
- 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/hit/validation_data/sci_sample_raw.csv +1 -1
- imap_processing/tests/ialirt/data/l0/461971383-404.bin +0 -0
- imap_processing/tests/ialirt/data/l0/461971384-405.bin +0 -0
- imap_processing/tests/ialirt/data/l0/461971385-406.bin +0 -0
- imap_processing/tests/ialirt/data/l0/461971386-407.bin +0 -0
- imap_processing/tests/ialirt/data/l0/461971387-408.bin +0 -0
- imap_processing/tests/ialirt/data/l0/461971388-409.bin +0 -0
- imap_processing/tests/ialirt/data/l0/461971389-410.bin +0 -0
- imap_processing/tests/ialirt/data/l0/461971390-411.bin +0 -0
- imap_processing/tests/ialirt/data/l0/461971391-412.bin +0 -0
- imap_processing/tests/ialirt/data/l0/sample_decoded_i-alirt_data.csv +383 -0
- imap_processing/tests/ialirt/unit/test_decom_ialirt.py +16 -81
- imap_processing/tests/ialirt/unit/test_grouping.py +81 -0
- imap_processing/tests/ialirt/unit/test_parse_mag.py +223 -0
- imap_processing/tests/ialirt/unit/test_process_codicehi.py +3 -3
- imap_processing/tests/ialirt/unit/test_process_codicelo.py +3 -10
- imap_processing/tests/ialirt/unit/test_process_ephemeris.py +4 -4
- imap_processing/tests/ialirt/unit/test_process_hit.py +3 -3
- imap_processing/tests/ialirt/unit/test_process_swapi.py +24 -16
- imap_processing/tests/ialirt/unit/test_process_swe.py +319 -6
- imap_processing/tests/ialirt/unit/test_time.py +16 -0
- imap_processing/tests/idex/conftest.py +127 -6
- imap_processing/tests/idex/test_data/imap_idex_l0_raw_20231218_v001.pkts +0 -0
- imap_processing/tests/idex/test_data/imap_idex_l0_raw_20241206_v001.pkts +0 -0
- imap_processing/tests/idex/test_data/imap_idex_l0_raw_20250108_v001.pkts +0 -0
- imap_processing/tests/idex/test_data/impact_14_tof_high_data.txt +4508 -4508
- imap_processing/tests/idex/test_idex_l0.py +33 -11
- imap_processing/tests/idex/test_idex_l1a.py +92 -21
- imap_processing/tests/idex/test_idex_l1b.py +106 -27
- imap_processing/tests/idex/test_idex_l2a.py +399 -0
- imap_processing/tests/idex/test_idex_l2b.py +93 -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_l1a.py +3 -3
- imap_processing/tests/lo/test_lo_l1b.py +515 -6
- imap_processing/tests/lo/test_lo_l1c.py +1 -1
- imap_processing/tests/lo/test_lo_science.py +7 -7
- imap_processing/tests/lo/test_star_sensor.py +1 -1
- imap_processing/tests/mag/conftest.py +120 -2
- imap_processing/tests/mag/test_mag_decom.py +5 -4
- imap_processing/tests/mag/test_mag_l1a.py +51 -7
- imap_processing/tests/mag/test_mag_l1b.py +40 -59
- imap_processing/tests/mag/test_mag_l1c.py +354 -19
- imap_processing/tests/mag/test_mag_l2.py +130 -0
- imap_processing/tests/mag/test_mag_validation.py +247 -26
- 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/L1c/T013/mag-l1b-l1c-t013-magi-normal-in.csv +1217 -0
- imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-magi-normal-out.csv +1857 -0
- imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-in.csv +1217 -0
- imap_processing/tests/mag/validation/L1c/T013/mag-l1b-l1c-t013-mago-normal-out.csv +1857 -0
- imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-in.csv +1217 -0
- imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-magi-normal-out.csv +1793 -0
- imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-in.csv +1217 -0
- imap_processing/tests/mag/validation/L1c/T014/mag-l1b-l1c-t014-mago-normal-out.csv +1793 -0
- imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-burst-in.csv +2561 -0
- imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-in.csv +961 -0
- imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-magi-normal-out.csv +1539 -0
- imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-in.csv +1921 -0
- imap_processing/tests/mag/validation/L1c/T015/mag-l1b-l1c-t015-mago-normal-out.csv +2499 -0
- imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-in.csv +865 -0
- imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-magi-normal-out.csv +1196 -0
- imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-in.csv +1729 -0
- imap_processing/tests/mag/validation/L1c/T016/mag-l1b-l1c-t016-mago-normal-out.csv +3053 -0
- imap_processing/tests/mag/validation/L2/imap_mag_l1b_norm-mago_20251017_v002.cdf +0 -0
- imap_processing/tests/mag/validation/calibration/imap_mag_l1b-calibration_20240229_v001.cdf +0 -0
- imap_processing/tests/mag/validation/calibration/imap_mag_l2-calibration-matrices_20251017_v004.cdf +0 -0
- imap_processing/tests/mag/validation/calibration/imap_mag_l2-offsets-norm_20251017_20251017_v001.cdf +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_data/fake_spin_data.csv +11 -11
- imap_processing/tests/spice/test_geometry.py +9 -12
- imap_processing/tests/spice/test_kernels.py +1 -200
- imap_processing/tests/spice/test_pointing_frame.py +185 -0
- imap_processing/tests/spice/test_repoint.py +121 -0
- imap_processing/tests/spice/test_spin.py +50 -9
- imap_processing/tests/spice/test_time.py +14 -0
- imap_processing/tests/swapi/lut/imap_swapi_esa-unit-conversion_20250211_v000.csv +73 -0
- imap_processing/tests/swapi/lut/imap_swapi_lut-notes_20250211_v000.csv +1025 -0
- imap_processing/tests/swapi/test_swapi_l1.py +13 -11
- imap_processing/tests/swapi/test_swapi_l2.py +180 -8
- 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/lut/checker-board-indices.csv +24 -0
- imap_processing/tests/swe/lut/imap_swe_esa-lut_20250301_v000.csv +385 -0
- imap_processing/tests/swe/lut/imap_swe_l1b-in-flight-cal_20240510_20260716_v000.csv +3 -0
- imap_processing/tests/swe/test_swe_l1a.py +20 -2
- 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_l1a_science.py +3 -3
- imap_processing/tests/swe/test_swe_l1b.py +162 -24
- imap_processing/tests/swe/test_swe_l2.py +153 -91
- imap_processing/tests/test_cli.py +171 -88
- imap_processing/tests/test_utils.py +140 -17
- 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 +369 -0
- imap_processing/tests/ultra/unit/conftest.py +115 -89
- imap_processing/tests/ultra/unit/test_badtimes.py +4 -4
- imap_processing/tests/ultra/unit/test_cullingmask.py +8 -6
- imap_processing/tests/ultra/unit/test_de.py +14 -13
- imap_processing/tests/ultra/unit/test_decom_apid_880.py +27 -76
- imap_processing/tests/ultra/unit/test_decom_apid_881.py +54 -11
- imap_processing/tests/ultra/unit/test_decom_apid_883.py +12 -10
- imap_processing/tests/ultra/unit/test_decom_apid_896.py +202 -55
- imap_processing/tests/ultra/unit/test_lookup_utils.py +23 -1
- imap_processing/tests/ultra/unit/test_spacecraft_pset.py +77 -0
- imap_processing/tests/ultra/unit/test_ultra_l1a.py +98 -305
- imap_processing/tests/ultra/unit/test_ultra_l1b.py +60 -14
- imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +2 -2
- imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +26 -27
- imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +239 -70
- imap_processing/tests/ultra/unit/test_ultra_l1c.py +5 -5
- imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +114 -83
- imap_processing/tests/ultra/unit/test_ultra_l2.py +230 -0
- imap_processing/ultra/constants.py +1 -1
- imap_processing/ultra/l0/decom_tools.py +27 -39
- imap_processing/ultra/l0/decom_ultra.py +168 -204
- imap_processing/ultra/l0/ultra_utils.py +152 -136
- imap_processing/ultra/l1a/ultra_l1a.py +55 -271
- imap_processing/ultra/l1b/badtimes.py +1 -4
- imap_processing/ultra/l1b/cullingmask.py +2 -6
- imap_processing/ultra/l1b/de.py +116 -57
- imap_processing/ultra/l1b/extendedspin.py +20 -18
- imap_processing/ultra/l1b/lookup_utils.py +72 -9
- imap_processing/ultra/l1b/ultra_l1b.py +36 -16
- imap_processing/ultra/l1b/ultra_l1b_culling.py +66 -30
- imap_processing/ultra/l1b/ultra_l1b_extended.py +297 -94
- imap_processing/ultra/l1c/histogram.py +2 -6
- imap_processing/ultra/l1c/spacecraft_pset.py +84 -0
- imap_processing/ultra/l1c/ultra_l1c.py +8 -9
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +206 -108
- imap_processing/ultra/l2/ultra_l2.py +299 -0
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +526 -0
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +526 -0
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +526 -0
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +526 -0
- imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -2
- imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +2 -0
- imap_processing/ultra/packet_definitions/README.md +38 -0
- imap_processing/ultra/packet_definitions/ULTRA_SCI_COMBINED.xml +15302 -482
- imap_processing/ultra/utils/ultra_l1_utils.py +31 -12
- imap_processing/utils.py +69 -29
- {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/METADATA +10 -6
- imap_processing-0.13.0.dist-info/RECORD +578 -0
- imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +0 -237
- imap_processing/hi/l1a/housekeeping.py +0 -27
- imap_processing/hi/l1b/hi_eng_unit_convert_table.csv +0 -154
- imap_processing/swe/l1b/swe_esa_lookup_table.csv +0 -1441
- imap_processing/swe/l1b/swe_l1b_science.py +0 -652
- 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/swe/test_swe_l1b_science.py +0 -84
- imap_processing/tests/ultra/test_data/mock_data.py +0 -161
- imap_processing/ultra/l1c/pset.py +0 -40
- imap_processing/ultra/lookup_tables/dps_sensitivity45.cdf +0 -0
- imap_processing-0.11.0.dist-info/RECORD +0 -488
- /imap_processing/idex/packet_definitions/{idex_packet_definition.xml → idex_science_packet_definition.xml} +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/IALiRT Raw Packet Telemetry.txt +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/apid01152.tlm +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/eu_SWP_IAL_20240826_152033.csv +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/hi_fsw_view_1_ccsds.bin +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.ccsds +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/hit_ialirt_sample.csv +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
- /imap_processing/tests/ialirt/{test_data → data}/l0/imap_codice_l1a_lo-ialirt_20241110193700_v0.0.0.cdf +0 -0
- /imap_processing/{mag/l1b → tests/spacecraft}/__init__.py +0 -0
- /imap_processing/{swe/l1b/engineering_unit_convert_table.csv → tests/swe/lut/imap_swe_eu-conversion_20240510_v000.csv} +0 -0
- /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.13.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.11.0.dist-info → imap_processing-0.13.0.dist-info}/entry_points.txt +0 -0
|
@@ -2,125 +2,200 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
import tempfile
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
from pathlib import Path
|
|
5
9
|
from unittest import mock
|
|
6
10
|
|
|
11
|
+
import astropy_healpix.healpy as hp
|
|
7
12
|
import numpy as np
|
|
8
13
|
import pytest
|
|
9
14
|
import xarray as xr
|
|
10
15
|
|
|
16
|
+
from imap_processing.cdf.utils import load_cdf, write_cdf
|
|
11
17
|
from imap_processing.ena_maps import ena_maps
|
|
18
|
+
from imap_processing.ena_maps.utils import spatial_utils
|
|
19
|
+
from imap_processing.ena_maps.utils.coordinates import CoordNames
|
|
12
20
|
from imap_processing.spice import geometry
|
|
13
|
-
from imap_processing.tests.ultra.test_data.mock_data import mock_l1c_pset_product
|
|
14
21
|
|
|
15
22
|
|
|
16
|
-
@pytest.fixture()
|
|
17
|
-
def
|
|
18
|
-
"""
|
|
19
|
-
|
|
23
|
+
@pytest.fixture(autouse=True, scope="module")
|
|
24
|
+
def setup_all_pset_products(ultra_l1c_pset_datasets, rectangular_l1c_pset_datasets):
|
|
25
|
+
"""
|
|
26
|
+
Setup fixture data once for all tests.
|
|
27
|
+
|
|
28
|
+
This is relatively computationally intensive for the high resolution PSETs,
|
|
29
|
+
so we use a module-level fixture to avoid repeating the setup code. However,
|
|
30
|
+
some tests need to modify the PSETs, so we use a function-level fixture to
|
|
31
|
+
make a deepcopy of the PSETs for each test function.
|
|
32
|
+
"""
|
|
33
|
+
hp_ultra_nside = ultra_l1c_pset_datasets["nside"]
|
|
34
|
+
hp_ultra_l1c_pset_products = ultra_l1c_pset_datasets["products"]
|
|
35
|
+
rect_spacing = rectangular_l1c_pset_datasets["spacing"]
|
|
36
|
+
rect_rectangular_l1c_pset_products = rectangular_l1c_pset_datasets["products"]
|
|
20
37
|
return {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
stripe_center_lon=mid_longitude,
|
|
26
|
-
timestr=f"2025-09-{i + 1:02d}T12:00:00",
|
|
27
|
-
head=("45" if (i % 2 == 0) else "90"),
|
|
28
|
-
)
|
|
29
|
-
for i, mid_longitude in enumerate(
|
|
30
|
-
np.arange(
|
|
31
|
-
0,
|
|
32
|
-
360,
|
|
33
|
-
45,
|
|
34
|
-
)
|
|
35
|
-
)
|
|
36
|
-
],
|
|
38
|
+
"hp_ultra_nside": hp_ultra_nside,
|
|
39
|
+
"hp_ultra_l1c_pset_products": hp_ultra_l1c_pset_products,
|
|
40
|
+
"rect_spacing": rect_spacing,
|
|
41
|
+
"rect_rectangular_l1c_pset_products": rect_rectangular_l1c_pset_products,
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
|
|
40
45
|
class TestUltraPointingSet:
|
|
41
46
|
@pytest.fixture(autouse=True)
|
|
42
|
-
def _setup_ultra_l1c_pset_products(self,
|
|
47
|
+
def _setup_ultra_l1c_pset_products(self, setup_all_pset_products):
|
|
43
48
|
"""Setup fixture data as class attributes"""
|
|
44
|
-
self.
|
|
45
|
-
self.l1c_pset_products =
|
|
49
|
+
self.nside = setup_all_pset_products["hp_ultra_nside"]
|
|
50
|
+
self.l1c_pset_products = deepcopy(
|
|
51
|
+
setup_all_pset_products["hp_ultra_l1c_pset_products"]
|
|
52
|
+
)
|
|
46
53
|
|
|
47
54
|
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
48
55
|
def test_instantiate(self):
|
|
49
56
|
"""Test instantiation of UltraPointingSet"""
|
|
50
57
|
ultra_psets = [
|
|
51
58
|
ena_maps.UltraPointingSet(
|
|
59
|
+
l1c_product,
|
|
52
60
|
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
53
|
-
l1c_dataset=l1c_product,
|
|
54
61
|
)
|
|
55
62
|
for l1c_product in self.l1c_pset_products
|
|
56
63
|
]
|
|
57
64
|
|
|
58
65
|
for ultra_pset in ultra_psets:
|
|
59
|
-
# Check tiling is
|
|
60
|
-
assert ultra_pset.tiling_type
|
|
66
|
+
# Check tiling is HEALPix
|
|
67
|
+
assert ultra_pset.tiling_type is ena_maps.SkyTilingType.HEALPIX
|
|
61
68
|
|
|
62
69
|
# Check that the reference frame is correctly set
|
|
63
|
-
assert ultra_pset.spice_reference_frame
|
|
70
|
+
assert ultra_pset.spice_reference_frame is geometry.SpiceFrame.IMAP_DPS
|
|
64
71
|
|
|
65
72
|
# Check the number of points is (360/0.5) * (180/0.5)
|
|
66
73
|
np.testing.assert_equal(
|
|
67
74
|
ultra_pset.num_points,
|
|
68
|
-
|
|
75
|
+
hp.nside2npix(self.nside),
|
|
69
76
|
)
|
|
70
77
|
|
|
71
78
|
# Check the repr exists
|
|
72
79
|
assert "UltraPointingSet" in repr(ultra_pset)
|
|
73
80
|
|
|
81
|
+
# Checks for the property methods:
|
|
82
|
+
# Check that the unwrapped_dims_dict is as expected
|
|
83
|
+
assert ultra_pset.unwrapped_dims_dict["counts"] == (
|
|
84
|
+
"epoch",
|
|
85
|
+
"energy_bin_geometric_mean",
|
|
86
|
+
"pixel",
|
|
87
|
+
)
|
|
88
|
+
# Check the non_spatial_coords are as expected
|
|
89
|
+
assert tuple(ultra_pset.non_spatial_coords.keys()) == (
|
|
90
|
+
"epoch",
|
|
91
|
+
"energy_bin_geometric_mean",
|
|
92
|
+
)
|
|
93
|
+
|
|
74
94
|
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
75
|
-
def
|
|
76
|
-
|
|
95
|
+
def test_init_cdf(
|
|
96
|
+
self,
|
|
97
|
+
):
|
|
98
|
+
ultra_pset = self.l1c_pset_products[0]
|
|
77
99
|
|
|
78
|
-
|
|
79
|
-
uneven_az_dataset = xr.Dataset()
|
|
80
|
-
uneven_az_dataset["epoch"] = 1
|
|
81
|
-
uneven_az_dataset["azimuth_bin_center"] = np.array([0, 5, 15, 20, 30])
|
|
82
|
-
uneven_az_dataset["elevation_bin_center"] = np.arange(5)
|
|
100
|
+
cdf_filepath = write_cdf(ultra_pset, istp=False)
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
ena_maps.UltraPointingSet(
|
|
86
|
-
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
87
|
-
l1c_dataset=uneven_az_dataset,
|
|
88
|
-
)
|
|
102
|
+
ultra_pset_from_dataset = ena_maps.UltraPointingSet(ultra_pset)
|
|
89
103
|
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
ultra_pset_from_str = ena_maps.UltraPointingSet(cdf_filepath)
|
|
105
|
+
ultra_pset_from_path = ena_maps.UltraPointingSet(Path(cdf_filepath))
|
|
92
106
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
np.testing.assert_allclose(
|
|
108
|
+
ultra_pset_from_dataset.data["counts"].values,
|
|
109
|
+
ultra_pset_from_str.data["counts"].values,
|
|
110
|
+
rtol=1e-6,
|
|
111
|
+
)
|
|
98
112
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
113
|
+
np.testing.assert_allclose(
|
|
114
|
+
ultra_pset_from_dataset.data["counts"].values,
|
|
115
|
+
ultra_pset_from_path.data["counts"].values,
|
|
116
|
+
rtol=1e-6,
|
|
117
|
+
)
|
|
102
118
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
119
|
+
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
120
|
+
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
121
|
+
def test_different_spacing_raises_error(self):
|
|
122
|
+
"""Test that different spaced az/el from the L1C dataset raises ValueError"""
|
|
123
|
+
|
|
124
|
+
ultra_pset_ds = self.l1c_pset_products[0]
|
|
125
|
+
# Modify the dataset to have different spacing
|
|
126
|
+
ultra_pset_ds[CoordNames.ELEVATION_L1C.value].values = np.arange(
|
|
127
|
+
ultra_pset_ds[CoordNames.ELEVATION_L1C.value].size
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
with pytest.raises(ValueError, match="do not match"):
|
|
106
131
|
ena_maps.UltraPointingSet(
|
|
132
|
+
ultra_pset_ds,
|
|
107
133
|
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
108
|
-
l1c_dataset=uneven_az_dataset,
|
|
109
134
|
)
|
|
110
135
|
|
|
111
136
|
|
|
137
|
+
@pytest.fixture(scope="module")
|
|
138
|
+
def hi_pset_cdf_path(imap_tests_path):
|
|
139
|
+
return imap_tests_path / "hi/data/l1/imap_hi_l1c_45sensor-pset_20250415_v999.cdf"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@pytest.mark.external_test_data
|
|
143
|
+
class TestHiPointingSet:
|
|
144
|
+
"""Test suite for HiPointingSet class."""
|
|
145
|
+
|
|
146
|
+
def test_init(self, hi_pset_cdf_path):
|
|
147
|
+
"""Test coverage for __init__ method."""
|
|
148
|
+
pset_ds = load_cdf(hi_pset_cdf_path)
|
|
149
|
+
hi_pset = ena_maps.HiPointingSet(pset_ds)
|
|
150
|
+
assert isinstance(hi_pset, ena_maps.HiPointingSet)
|
|
151
|
+
assert hi_pset.spice_reference_frame == geometry.SpiceFrame.ECLIPJ2000
|
|
152
|
+
assert hi_pset.num_points == 3600
|
|
153
|
+
np.testing.assert_array_equal(hi_pset.az_el_points.shape, (3600, 2))
|
|
154
|
+
|
|
155
|
+
def test_from_cdf(self, hi_pset_cdf_path):
|
|
156
|
+
"""Test coverage for from_cdf method."""
|
|
157
|
+
hi_pset = ena_maps.HiPointingSet(hi_pset_cdf_path)
|
|
158
|
+
assert isinstance(hi_pset, ena_maps.HiPointingSet)
|
|
159
|
+
|
|
160
|
+
def test_plays_nice_with_rectangular_sky_map(self, hi_pset_cdf_path):
|
|
161
|
+
"""Test that HiPointingSet works with RectangularSkyMap"""
|
|
162
|
+
hi_pset = ena_maps.HiPointingSet(hi_pset_cdf_path)
|
|
163
|
+
rect_map = ena_maps.RectangularSkyMap(
|
|
164
|
+
spacing_deg=2, spice_frame=geometry.SpiceFrame.ECLIPJ2000
|
|
165
|
+
)
|
|
166
|
+
rect_map.project_pset_values_to_map(hi_pset, ["counts", "exposure_times"])
|
|
167
|
+
assert rect_map.data_1d["counts"].max() > 0
|
|
168
|
+
|
|
169
|
+
|
|
112
170
|
class TestRectangularSkyMap:
|
|
113
171
|
@pytest.fixture(autouse=True)
|
|
114
|
-
def _setup_ultra_l1c_pset_products(self,
|
|
172
|
+
def _setup_ultra_l1c_pset_products(self, setup_all_pset_products):
|
|
115
173
|
"""Setup fixture data as class attributes"""
|
|
116
|
-
self.
|
|
117
|
-
self.
|
|
174
|
+
self.ultra_l1c_nside = setup_all_pset_products["hp_ultra_nside"]
|
|
175
|
+
self.ultra_l1c_pset_products = deepcopy(
|
|
176
|
+
setup_all_pset_products["hp_ultra_l1c_pset_products"]
|
|
177
|
+
)
|
|
118
178
|
self.ultra_psets = [
|
|
119
179
|
ena_maps.UltraPointingSet(
|
|
180
|
+
l1c_product,
|
|
120
181
|
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
121
|
-
l1c_dataset=l1c_product,
|
|
122
182
|
)
|
|
123
|
-
for l1c_product in self.
|
|
183
|
+
for l1c_product in self.ultra_l1c_pset_products
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
@pytest.fixture(autouse=True)
|
|
187
|
+
def _setup_rectangular_l1c_pset_products(self, setup_all_pset_products):
|
|
188
|
+
"""Setup fixture data as class attributes"""
|
|
189
|
+
self.rectangular_l1c_spacing_deg = setup_all_pset_products["rect_spacing"]
|
|
190
|
+
self.rectangular_l1c_pset_products = deepcopy(
|
|
191
|
+
setup_all_pset_products["rect_rectangular_l1c_pset_products"]
|
|
192
|
+
)
|
|
193
|
+
self.rectangular_psets = [
|
|
194
|
+
ena_maps.RectangularPointingSet(
|
|
195
|
+
l1c_product,
|
|
196
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
197
|
+
)
|
|
198
|
+
for l1c_product in self.rectangular_l1c_pset_products
|
|
124
199
|
]
|
|
125
200
|
|
|
126
201
|
def test_instantiate(self):
|
|
@@ -130,8 +205,9 @@ class TestRectangularSkyMap:
|
|
|
130
205
|
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
131
206
|
)
|
|
132
207
|
|
|
133
|
-
# Check that the map is empty
|
|
134
|
-
assert rm.
|
|
208
|
+
# Check that the map data is an empty xarray Dataset
|
|
209
|
+
assert isinstance(rm.data_1d, xr.Dataset)
|
|
210
|
+
assert rm.data_1d.data_vars == {}
|
|
135
211
|
|
|
136
212
|
# Check that the reference frame is correctly set
|
|
137
213
|
assert rm.spice_reference_frame == geometry.SpiceFrame.ECLIPJ2000
|
|
@@ -142,11 +218,80 @@ class TestRectangularSkyMap:
|
|
|
142
218
|
# Check the repr exists
|
|
143
219
|
assert "RectangularSkyMap" in repr(rm)
|
|
144
220
|
|
|
221
|
+
np.testing.assert_array_equal(
|
|
222
|
+
rm.binning_grid_shape, (360 / rm.spacing_deg, 180 / rm.spacing_deg)
|
|
223
|
+
)
|
|
224
|
+
|
|
145
225
|
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
146
226
|
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
147
|
-
def
|
|
227
|
+
def test_project_healpix_pset_values_to_map_push_method(
|
|
228
|
+
self, mock_frame_transform_az_el
|
|
229
|
+
):
|
|
230
|
+
"""
|
|
231
|
+
Test projection of Healpix tiled PSET values to RectMap w "push" index matching.
|
|
232
|
+
|
|
233
|
+
If frame_transform_az_el is mocked to return the az and el unchanged,
|
|
234
|
+
then the map should have the same total counts in each energy bin
|
|
235
|
+
as the PSETs, summed.
|
|
236
|
+
"""
|
|
237
|
+
index_matching_method = ena_maps.IndexMatchMethod.PUSH
|
|
238
|
+
|
|
239
|
+
# Mock frame_transform to return the az and el unchanged
|
|
240
|
+
mock_frame_transform_az_el.side_effect = (
|
|
241
|
+
lambda et, az_el, from_frame, to_frame, degrees: az_el
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
rectangular_map = ena_maps.RectangularSkyMap(
|
|
245
|
+
spacing_deg=2,
|
|
246
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Project each PSET's values to the map (push method)
|
|
250
|
+
for ultra_pset in self.ultra_psets:
|
|
251
|
+
rectangular_map.project_pset_values_to_map(
|
|
252
|
+
ultra_pset,
|
|
253
|
+
value_keys=["counts", "exposure_factor"],
|
|
254
|
+
index_match_method=index_matching_method,
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Check that the map has been updated
|
|
258
|
+
assert "counts" in rectangular_map.data_1d.data_vars
|
|
259
|
+
|
|
260
|
+
# Check that the map has the same values as the PSETs, summed
|
|
261
|
+
simple_summed_pset_counts_by_energy = np.zeros(
|
|
262
|
+
shape=(
|
|
263
|
+
self.ultra_l1c_pset_products[0]["counts"].sizes[
|
|
264
|
+
CoordNames.ENERGY_ULTRA.value
|
|
265
|
+
],
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
for pset in self.ultra_l1c_pset_products:
|
|
269
|
+
simple_summed_pset_counts_by_energy += pset["counts"].sum(
|
|
270
|
+
dim=[
|
|
271
|
+
d for d in pset["counts"].dims if d != CoordNames.ENERGY_ULTRA.value
|
|
272
|
+
]
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
rmap_counts_per_energy_bin = rectangular_map.data_1d["counts"].sum(
|
|
276
|
+
dim=[
|
|
277
|
+
d
|
|
278
|
+
for d in rectangular_map.data_1d["counts"].dims
|
|
279
|
+
if d != CoordNames.ENERGY_ULTRA.value
|
|
280
|
+
]
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
np.testing.assert_array_equal(
|
|
284
|
+
rmap_counts_per_energy_bin,
|
|
285
|
+
simple_summed_pset_counts_by_energy,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
@pytest.mark.usefixtures("_setup_rectangular_l1c_pset_products")
|
|
289
|
+
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
290
|
+
def test_project_rect_pset_values_to_map_push_method(
|
|
291
|
+
self, mock_frame_transform_az_el
|
|
292
|
+
):
|
|
148
293
|
"""
|
|
149
|
-
Test projection of PSET values to Rect
|
|
294
|
+
Test projection of Rect PSET values to Rect Map w "push" index matching method.
|
|
150
295
|
|
|
151
296
|
If frame_transform_az_el is mocked to return the az and el unchanged, and the
|
|
152
297
|
map has the same spacing as the PSETs, then the map should have
|
|
@@ -154,7 +299,7 @@ class TestRectangularSkyMap:
|
|
|
154
299
|
"""
|
|
155
300
|
index_matching_method = ena_maps.IndexMatchMethod.PUSH
|
|
156
301
|
|
|
157
|
-
pset_spacing_deg = self.
|
|
302
|
+
pset_spacing_deg = self.rectangular_psets[0].sky_grid.spacing_deg
|
|
158
303
|
|
|
159
304
|
# Mock frame_transform to return the az and el unchanged
|
|
160
305
|
mock_frame_transform_az_el.side_effect = (
|
|
@@ -166,59 +311,640 @@ class TestRectangularSkyMap:
|
|
|
166
311
|
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
167
312
|
)
|
|
168
313
|
|
|
169
|
-
# Project each PSET's values to the map
|
|
170
|
-
for
|
|
314
|
+
# Project each PSET's values to the map (push method)
|
|
315
|
+
for rectangular_pset in self.rectangular_psets:
|
|
171
316
|
rectangular_map.project_pset_values_to_map(
|
|
172
|
-
|
|
173
|
-
value_keys=["counts", "
|
|
317
|
+
rectangular_pset,
|
|
318
|
+
value_keys=["counts", "exposure_factor"],
|
|
174
319
|
index_match_method=index_matching_method,
|
|
175
320
|
)
|
|
176
321
|
|
|
177
322
|
# Check that the map has been updated
|
|
178
|
-
assert rectangular_map.
|
|
323
|
+
assert "counts" in rectangular_map.data_1d.data_vars
|
|
179
324
|
|
|
180
325
|
# Check that the map has the same values as the PSETs, summed
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
326
|
+
simple_summed_pset_counts_by_energy = np.zeros(
|
|
327
|
+
shape=(
|
|
328
|
+
self.rectangular_l1c_pset_products[0]["counts"].sizes[
|
|
329
|
+
CoordNames.ENERGY_ULTRA.value
|
|
330
|
+
],
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
for pset in self.rectangular_l1c_pset_products:
|
|
334
|
+
simple_summed_pset_counts_by_energy += pset["counts"].sum(
|
|
335
|
+
dim=[
|
|
336
|
+
d for d in pset["counts"].dims if d != CoordNames.ENERGY_ULTRA.value
|
|
337
|
+
]
|
|
338
|
+
)
|
|
184
339
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
340
|
+
rmap_counts_per_energy_bin = rectangular_map.data_1d["counts"].sum(
|
|
341
|
+
dim=[
|
|
342
|
+
d
|
|
343
|
+
for d in rectangular_map.data_1d["counts"].dims
|
|
344
|
+
if d != CoordNames.ENERGY_ULTRA.value
|
|
345
|
+
]
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
np.testing.assert_array_equal(
|
|
349
|
+
rmap_counts_per_energy_bin,
|
|
350
|
+
simple_summed_pset_counts_by_energy,
|
|
188
351
|
)
|
|
189
352
|
|
|
190
353
|
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
354
|
+
def test_project_pset_values_to_map_errors(self):
|
|
355
|
+
index_matching_method = ena_maps.IndexMatchMethod.PUSH
|
|
356
|
+
rectangular_map = ena_maps.RectangularSkyMap(
|
|
357
|
+
spacing_deg=1,
|
|
358
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# An error should be raised if a key is not found in the PSET
|
|
362
|
+
with pytest.raises(ValueError, match="Value key invalid not found"):
|
|
363
|
+
rectangular_map.project_pset_values_to_map(
|
|
364
|
+
self.ultra_psets[0],
|
|
365
|
+
value_keys=["invalid"],
|
|
366
|
+
index_match_method=index_matching_method,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
@pytest.mark.usefixtures("_setup_rectangular_l1c_pset_products")
|
|
191
370
|
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
192
|
-
def
|
|
193
|
-
|
|
371
|
+
def test_project_rect_pset_values_to_map_pull_method(
|
|
372
|
+
self, mock_frame_transform_az_el
|
|
373
|
+
):
|
|
374
|
+
"""
|
|
375
|
+
Test projection Rect PSET to Rect. Map with "pull" index matching method.
|
|
376
|
+
|
|
377
|
+
NOTE: Pull index matching is only expected to be done with Rectangularly tiled
|
|
378
|
+
PointingSet objects.
|
|
379
|
+
"""
|
|
194
380
|
|
|
195
381
|
index_matching_method = ena_maps.IndexMatchMethod.PULL
|
|
382
|
+
skymap_spacing = 10
|
|
383
|
+
|
|
384
|
+
# Mock frame_transform to return the az and el unchanged
|
|
385
|
+
mock_frame_transform_az_el.side_effect = (
|
|
386
|
+
lambda et, az_el, from_frame, to_frame, degrees: az_el
|
|
387
|
+
)
|
|
196
388
|
rectangular_map = ena_maps.RectangularSkyMap(
|
|
197
|
-
spacing_deg=
|
|
389
|
+
spacing_deg=skymap_spacing,
|
|
198
390
|
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
199
391
|
)
|
|
200
392
|
|
|
201
|
-
|
|
393
|
+
# Each map pixel will add the value of a single PSET pixel, so we'll start at 0
|
|
394
|
+
# and add 0, 1, 2, 3, ... to the map
|
|
395
|
+
expected_value_every_pixel = 0
|
|
396
|
+
|
|
397
|
+
# Another way to test this is that (if the PSET pixels are
|
|
398
|
+
# smaller than the SkyMap pixels) the sum of the counts in all PSETs should
|
|
399
|
+
# be (PSET_spacing / SkyMap_spacing)^2 times the sum of the counts in the SkyMap
|
|
400
|
+
total_pset_counts = np.zeros_like(
|
|
401
|
+
self.rectangular_l1c_pset_products[0]["counts"].values
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Project each PSET's values to the map (pull method)
|
|
405
|
+
for pset_num, rectangular_pset in enumerate(self.rectangular_psets):
|
|
406
|
+
# Set the counts to be 0 in the first PSET, 1 in the second, etc.
|
|
407
|
+
rectangular_pset.data["counts"].values = np.full_like(
|
|
408
|
+
rectangular_pset.data["counts"].values, pset_num
|
|
409
|
+
)
|
|
410
|
+
|
|
202
411
|
rectangular_map.project_pset_values_to_map(
|
|
203
|
-
|
|
204
|
-
value_keys=["counts", "
|
|
412
|
+
rectangular_pset,
|
|
413
|
+
value_keys=["counts", "exposure_factor"],
|
|
205
414
|
index_match_method=index_matching_method,
|
|
206
415
|
)
|
|
416
|
+
expected_value_every_pixel += pset_num
|
|
417
|
+
|
|
418
|
+
total_pset_counts += rectangular_pset.data["counts"].values
|
|
419
|
+
|
|
420
|
+
# Check that the map has been updated
|
|
421
|
+
assert "counts" in rectangular_map.data_1d
|
|
422
|
+
|
|
423
|
+
np.testing.assert_allclose(
|
|
424
|
+
rectangular_map.data_1d["counts"],
|
|
425
|
+
expected_value_every_pixel,
|
|
426
|
+
)
|
|
427
|
+
downsample_ratio = skymap_spacing / self.rectangular_l1c_spacing_deg
|
|
428
|
+
np.testing.assert_allclose(
|
|
429
|
+
rectangular_map.data_1d["counts"].sum(),
|
|
430
|
+
total_pset_counts.sum() / (downsample_ratio**2),
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Convert to xarray Dataset and check the data is as expected
|
|
434
|
+
# This is a method, which could be tested separately, but that would be
|
|
435
|
+
# innefficient, as it would require all the same, computationally intensive
|
|
436
|
+
# operations to be repeated as this test
|
|
437
|
+
rect_map_ds = rectangular_map.to_dataset()
|
|
438
|
+
assert "counts" in rect_map_ds.data_vars
|
|
439
|
+
assert rect_map_ds["counts"].shape == (
|
|
440
|
+
1,
|
|
441
|
+
rectangular_pset.data["counts"].sizes[CoordNames.ENERGY_ULTRA.value],
|
|
442
|
+
360 / skymap_spacing,
|
|
443
|
+
180 / skymap_spacing,
|
|
444
|
+
)
|
|
445
|
+
assert rect_map_ds["counts"].dims == (
|
|
446
|
+
CoordNames.TIME.value,
|
|
447
|
+
CoordNames.ENERGY_ULTRA.value,
|
|
448
|
+
CoordNames.AZIMUTH_L2.value,
|
|
449
|
+
CoordNames.ELEVATION_L2.value,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Check that the data is as expected
|
|
453
|
+
np.testing.assert_array_equal(
|
|
454
|
+
rect_map_ds["counts"].values,
|
|
455
|
+
spatial_utils.rewrap_even_spaced_az_el_grid(
|
|
456
|
+
rectangular_map.data_1d["counts"].values,
|
|
457
|
+
rectangular_map.binning_grid_shape,
|
|
458
|
+
),
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
class TestHealpixSkyMap:
|
|
463
|
+
@pytest.fixture(autouse=True)
|
|
464
|
+
def _setup_ultra_l1c_pset_products(self, setup_all_pset_products):
|
|
465
|
+
"""Setup fixture data as class attributes"""
|
|
466
|
+
self.ultra_l1c_nside = setup_all_pset_products["hp_ultra_nside"]
|
|
467
|
+
self.ultra_l1c_pset_products = deepcopy(
|
|
468
|
+
setup_all_pset_products["hp_ultra_l1c_pset_products"]
|
|
469
|
+
)
|
|
470
|
+
self.ultra_psets = [
|
|
471
|
+
ena_maps.UltraPointingSet(
|
|
472
|
+
l1c_product,
|
|
473
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
474
|
+
)
|
|
475
|
+
for l1c_product in self.ultra_l1c_pset_products
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
@pytest.fixture(autouse=True)
|
|
479
|
+
def _setup_rectangular_l1c_pset_products(self, setup_all_pset_products):
|
|
480
|
+
"""Setup fixture data as class attributes"""
|
|
481
|
+
self.rectangular_l1c_spacing_deg = setup_all_pset_products["rect_spacing"]
|
|
482
|
+
self.rectangular_l1c_pset_products = deepcopy(
|
|
483
|
+
setup_all_pset_products["rect_rectangular_l1c_pset_products"]
|
|
484
|
+
)
|
|
485
|
+
self.rectangular_psets = [
|
|
486
|
+
ena_maps.RectangularPointingSet(
|
|
487
|
+
l1c_product,
|
|
488
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
489
|
+
)
|
|
490
|
+
for l1c_product in self.rectangular_l1c_pset_products
|
|
491
|
+
]
|
|
492
|
+
|
|
493
|
+
@pytest.mark.parametrize(
|
|
494
|
+
"nside",
|
|
495
|
+
[8, 16, 32],
|
|
496
|
+
)
|
|
497
|
+
@pytest.mark.parametrize("nested", [True, False], ids=["nested", "ring"])
|
|
498
|
+
def test_instantiate(self, nside, nested):
|
|
499
|
+
"""Test instantiation of HealpixSkyMap"""
|
|
500
|
+
hp_map = ena_maps.HealpixSkyMap(
|
|
501
|
+
nside=nside,
|
|
502
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
503
|
+
nested=nested,
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# Check that the map data is an empty xarray Dataset
|
|
507
|
+
assert isinstance(hp_map.data_1d, xr.Dataset)
|
|
508
|
+
assert hp_map.data_1d.data_vars == {}
|
|
509
|
+
|
|
510
|
+
# Check that the reference frame is correctly set
|
|
511
|
+
assert hp_map.spice_reference_frame is geometry.SpiceFrame.ECLIPJ2000
|
|
512
|
+
# Check that the nside and nested properties are set correctly
|
|
513
|
+
np.testing.assert_equal(hp_map.nside, nside)
|
|
514
|
+
np.testing.assert_equal(hp_map.nested, nested)
|
|
515
|
+
# Check the number of points is 12 * nside^2
|
|
516
|
+
np.testing.assert_equal(hp_map.num_points, 12 * nside**2)
|
|
517
|
+
# There will be az, el values for each pixel
|
|
518
|
+
assert hp_map.az_el_points.shape == (hp_map.num_points, 2)
|
|
519
|
+
# The az must be in the range [0, 360) degrees
|
|
520
|
+
# and el in the range [-90, 90)
|
|
521
|
+
assert np.all(hp_map.az_el_points[:, 0] >= 0)
|
|
522
|
+
assert np.all(hp_map.az_el_points[:, 0] < 360)
|
|
523
|
+
assert np.all(hp_map.az_el_points[:, 1] >= -90)
|
|
524
|
+
assert np.all(hp_map.az_el_points[:, 1] < 90)
|
|
525
|
+
|
|
526
|
+
# Check that the binning grid shape is just a tuple of num_points
|
|
527
|
+
np.testing.assert_equal(hp_map.binning_grid_shape, (hp_map.num_points,))
|
|
528
|
+
|
|
529
|
+
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
530
|
+
@pytest.mark.parametrize(
|
|
531
|
+
"nside,degree_tolerance",
|
|
532
|
+
[
|
|
533
|
+
(8, 6),
|
|
534
|
+
(16, 3),
|
|
535
|
+
(32, 2),
|
|
536
|
+
],
|
|
537
|
+
)
|
|
538
|
+
@pytest.mark.parametrize("nested", [True, False], ids=["nested", "ring"])
|
|
539
|
+
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
540
|
+
def test_project_healpix_pset_values_to_map_push_method(
|
|
541
|
+
self, mock_frame_transform_az_el, nside, degree_tolerance, nested
|
|
542
|
+
):
|
|
543
|
+
"""
|
|
544
|
+
Test that PointingSet which contains bright spot pushes to correct spot in map.
|
|
545
|
+
|
|
546
|
+
Parameterized over nside (of the map, not the PSET), nested.
|
|
547
|
+
The tolerance for lower nsides must be higher because the
|
|
548
|
+
Healpix pixels are larger.
|
|
549
|
+
"""
|
|
550
|
+
|
|
551
|
+
# Mock frame_transform to return the az and el unchanged
|
|
552
|
+
mock_frame_transform_az_el.side_effect = (
|
|
553
|
+
lambda et, az_el, from_frame, to_frame, degrees: az_el
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
index_matching_method = ena_maps.IndexMatchMethod.PUSH
|
|
557
|
+
|
|
558
|
+
# Create a PointingSet with a bright spot
|
|
559
|
+
mock_pset_input_frame = ena_maps.UltraPointingSet(
|
|
560
|
+
self.ultra_l1c_pset_products[0],
|
|
561
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
562
|
+
)
|
|
563
|
+
mock_pset_input_frame.data["counts"].values = np.zeros_like(
|
|
564
|
+
mock_pset_input_frame.data["counts"].values
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
input_bright_pixel_number = hp.ang2pix(
|
|
568
|
+
nside=mock_pset_input_frame.nside,
|
|
569
|
+
theta=180,
|
|
570
|
+
phi=0,
|
|
571
|
+
nest=mock_pset_input_frame.nested,
|
|
572
|
+
lonlat=True,
|
|
573
|
+
)
|
|
574
|
+
input_bright_pixel_az_el_deg = mock_pset_input_frame.az_el_points[
|
|
575
|
+
input_bright_pixel_number
|
|
576
|
+
]
|
|
577
|
+
mock_pset_input_frame.data["counts"].values[
|
|
578
|
+
:,
|
|
579
|
+
:,
|
|
580
|
+
input_bright_pixel_number,
|
|
581
|
+
] = 1
|
|
582
|
+
|
|
583
|
+
# Create a Healpix map
|
|
584
|
+
hp_map = ena_maps.HealpixSkyMap(
|
|
585
|
+
nside=nside,
|
|
586
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
587
|
+
nested=nested,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
# Project the PointingSet to the Healpix map
|
|
591
|
+
hp_map.project_pset_values_to_map(
|
|
592
|
+
mock_pset_input_frame,
|
|
593
|
+
value_keys=[
|
|
594
|
+
"counts",
|
|
595
|
+
],
|
|
596
|
+
index_match_method=index_matching_method,
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
# Check that the map has been updated
|
|
600
|
+
assert "counts" in hp_map.data_1d.data_vars
|
|
601
|
+
|
|
602
|
+
# Find the maximum value in the spatial pixel dimension of the healpix map
|
|
603
|
+
bright_hp_pixel_index = hp_map.data_1d["counts"][0, :].values.argmax()
|
|
604
|
+
bright_hp_pixel_az_el = hp_map.az_el_points[bright_hp_pixel_index]
|
|
605
|
+
|
|
606
|
+
np.testing.assert_allclose(
|
|
607
|
+
bright_hp_pixel_az_el,
|
|
608
|
+
input_bright_pixel_az_el_deg,
|
|
609
|
+
atol=degree_tolerance,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
@pytest.mark.usefixtures("_setup_rectangular_l1c_pset_products")
|
|
613
|
+
@pytest.mark.parametrize(
|
|
614
|
+
"nside,degree_tolerance",
|
|
615
|
+
[
|
|
616
|
+
(8, 6),
|
|
617
|
+
(16, 3),
|
|
618
|
+
(32, 2),
|
|
619
|
+
],
|
|
620
|
+
)
|
|
621
|
+
@pytest.mark.parametrize("nested", [True, False], ids=["nested", "ring"])
|
|
622
|
+
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
623
|
+
def test_project_rect_pset_values_to_map_push_method(
|
|
624
|
+
self, mock_frame_transform_az_el, nside, degree_tolerance, nested
|
|
625
|
+
):
|
|
626
|
+
"""
|
|
627
|
+
Test that PointingSet which contains bright spot pushes to correct spot in map.
|
|
628
|
+
|
|
629
|
+
Parameterized over nside, nested. The tolerance for lower nsides must be higher
|
|
630
|
+
because the Healpix pixels are larger.
|
|
631
|
+
"""
|
|
632
|
+
|
|
633
|
+
# Mock frame_transform to return the az and el unchanged
|
|
634
|
+
mock_frame_transform_az_el.side_effect = (
|
|
635
|
+
lambda et, az_el, from_frame, to_frame, degrees: az_el
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
index_matching_method = ena_maps.IndexMatchMethod.PUSH
|
|
639
|
+
|
|
640
|
+
# Create a PointingSet with a bright spot
|
|
641
|
+
mock_pset_input_frame = ena_maps.RectangularPointingSet(
|
|
642
|
+
self.rectangular_l1c_pset_products[0],
|
|
643
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
644
|
+
)
|
|
645
|
+
mock_pset_input_frame.data["counts"].values = np.zeros_like(
|
|
646
|
+
mock_pset_input_frame.data["counts"].values
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
input_bright_pixel_az_el_deg = (110, 55)
|
|
650
|
+
mock_pset_input_frame.data["counts"].values[
|
|
651
|
+
:,
|
|
652
|
+
:,
|
|
653
|
+
int(
|
|
654
|
+
input_bright_pixel_az_el_deg[0]
|
|
655
|
+
// mock_pset_input_frame.sky_grid.spacing_deg
|
|
656
|
+
),
|
|
657
|
+
int(
|
|
658
|
+
(90 + input_bright_pixel_az_el_deg[1])
|
|
659
|
+
// mock_pset_input_frame.sky_grid.spacing_deg
|
|
660
|
+
),
|
|
661
|
+
] = 1
|
|
662
|
+
|
|
663
|
+
# Create a Healpix map
|
|
664
|
+
hp_map = ena_maps.HealpixSkyMap(
|
|
665
|
+
nside=nside,
|
|
666
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
667
|
+
nested=nested,
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
# Project the PointingSet to the Healpix map
|
|
671
|
+
hp_map.project_pset_values_to_map(
|
|
672
|
+
mock_pset_input_frame,
|
|
673
|
+
value_keys=[
|
|
674
|
+
"counts",
|
|
675
|
+
],
|
|
676
|
+
index_match_method=index_matching_method,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# Check that the map has been updated
|
|
680
|
+
assert "counts" in hp_map.data_1d.data_vars
|
|
681
|
+
|
|
682
|
+
# Find the maximum value in the spatial pixel dimension of the healpix map
|
|
683
|
+
bright_hp_pixel_index = hp_map.data_1d["counts"][0, 0].argmax(dim="pixel")
|
|
684
|
+
bright_hp_pixel_az_el = hp_map.az_el_points[bright_hp_pixel_index]
|
|
685
|
+
|
|
686
|
+
np.testing.assert_allclose(
|
|
687
|
+
bright_hp_pixel_az_el,
|
|
688
|
+
input_bright_pixel_az_el_deg,
|
|
689
|
+
atol=degree_tolerance,
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
# Convert to xarray Dataset and check the data is as expected
|
|
693
|
+
hp_map_ds = hp_map.to_dataset()
|
|
694
|
+
assert "counts" in hp_map_ds.data_vars
|
|
695
|
+
assert hp_map_ds["counts"].shape == (
|
|
696
|
+
1,
|
|
697
|
+
mock_pset_input_frame.data["counts"].sizes[CoordNames.ENERGY_ULTRA.value],
|
|
698
|
+
hp_map.num_points,
|
|
699
|
+
)
|
|
700
|
+
assert hp_map_ds["counts"].dims == (
|
|
701
|
+
CoordNames.TIME.value,
|
|
702
|
+
CoordNames.ENERGY_ULTRA.value,
|
|
703
|
+
CoordNames.HEALPIX_INDEX.value,
|
|
704
|
+
)
|
|
705
|
+
np.testing.assert_array_equal(
|
|
706
|
+
hp_map_ds["counts"].values,
|
|
707
|
+
hp_map.data_1d["counts"].values,
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
@mock.patch("astropy_healpix.healpy.ang2pix")
|
|
711
|
+
def test_calculate_rect_pixel_value_from_healpix_map_n_subdivisions(
|
|
712
|
+
self,
|
|
713
|
+
mock_ang2pix,
|
|
714
|
+
):
|
|
715
|
+
"""Test getting rectangular pixel values from HealpixSkyMap via subdivision."""
|
|
716
|
+
|
|
717
|
+
# Mock ang2pix to return fixed values based on a dict
|
|
718
|
+
pixel_dict = {
|
|
719
|
+
# 0 subdiv - just 1 pixel
|
|
720
|
+
(180, 0): 0,
|
|
721
|
+
# 1 subdiv - all subpix have same solid angle because centered on equator
|
|
722
|
+
(179, -1): 1,
|
|
723
|
+
(179, 1): 2,
|
|
724
|
+
(181, -1): 3,
|
|
725
|
+
(181, 1): 4,
|
|
726
|
+
# 2 subdiv - 'Inner' subpix have larger solid angle than 'outer' subpix
|
|
727
|
+
(178.5, -1.5): 5,
|
|
728
|
+
(178.5, -0.5): 6,
|
|
729
|
+
(178.5, 0.5): 7,
|
|
730
|
+
(178.5, 1.5): 8,
|
|
731
|
+
(179.5, -1.5): 9,
|
|
732
|
+
(179.5, -0.5): 10,
|
|
733
|
+
(179.5, 0.5): 11,
|
|
734
|
+
(179.5, 1.5): 12,
|
|
735
|
+
(180.5, -1.5): 12,
|
|
736
|
+
(180.5, -0.5): 14,
|
|
737
|
+
(180.5, 0.5): 15,
|
|
738
|
+
(180.5, 1.5): 16,
|
|
739
|
+
(181.5, -1.5): 17,
|
|
740
|
+
(181.5, -0.5): 18,
|
|
741
|
+
(181.5, 0.5): 19,
|
|
742
|
+
(181.5, 1.5): 20,
|
|
743
|
+
}
|
|
744
|
+
expected_mean_0_subdivisions = 0
|
|
745
|
+
expected_mean_1_subdivisions = 2.5
|
|
746
|
+
expected_mean_2_subdivisions = 12.5
|
|
747
|
+
|
|
748
|
+
def mock_ang2pix_fn(nside, theta, phi, nest=True, lonlat=False):
|
|
749
|
+
vals = []
|
|
750
|
+
for pix_num in range(len(theta)):
|
|
751
|
+
key = (theta[pix_num], phi[pix_num])
|
|
752
|
+
vals.append(pixel_dict.get(key, 0))
|
|
753
|
+
return np.array(vals)
|
|
754
|
+
|
|
755
|
+
hp_map = ena_maps.HealpixSkyMap(
|
|
756
|
+
nside=16,
|
|
757
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
758
|
+
nested=True,
|
|
759
|
+
)
|
|
760
|
+
hp_map.data_1d["counts"] = xr.DataArray(
|
|
761
|
+
data=[
|
|
762
|
+
np.arange(hp_map.num_points),
|
|
763
|
+
],
|
|
764
|
+
dims=["epoch", "pixel"],
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
for num_subdiv, (expected_value, atol) in enumerate(
|
|
768
|
+
[
|
|
769
|
+
# The first subdivs have all the same solid angle
|
|
770
|
+
(expected_mean_0_subdivisions, 1e-9),
|
|
771
|
+
(expected_mean_1_subdivisions, 1e-9),
|
|
772
|
+
# Slight difference from not taking into account asym solid angle
|
|
773
|
+
(expected_mean_2_subdivisions, 0.1),
|
|
774
|
+
]
|
|
775
|
+
):
|
|
776
|
+
mock_ang2pix.reset_mock()
|
|
777
|
+
mock_ang2pix.side_effect = mock_ang2pix_fn
|
|
778
|
+
mean_value = (
|
|
779
|
+
hp_map.calculate_rect_pixel_value_from_healpix_map_n_subdivisions(
|
|
780
|
+
rect_pix_center_lon_lat=(180, 0),
|
|
781
|
+
rect_pix_spacing_deg=4,
|
|
782
|
+
value_array=hp_map.data_1d["counts"],
|
|
783
|
+
num_subdivisions=num_subdiv,
|
|
784
|
+
)
|
|
785
|
+
)
|
|
786
|
+
np.testing.assert_allclose(
|
|
787
|
+
mean_value,
|
|
788
|
+
expected_value,
|
|
789
|
+
atol=atol,
|
|
790
|
+
err_msg=f"Failed for num_subdivisions: {num_subdiv}",
|
|
791
|
+
)
|
|
792
|
+
hp_map.calculate_rect_pixel_value_from_healpix_map_n_subdivisions(
|
|
793
|
+
rect_pix_center_lon_lat=(180, 0),
|
|
794
|
+
rect_pix_spacing_deg=2,
|
|
795
|
+
value_array=hp_map.data_1d["counts"],
|
|
796
|
+
num_subdivisions=0,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
@mock.patch(
|
|
800
|
+
"imap_processing.ena_maps.ena_maps.HealpixSkyMap.calculate_rect_pixel_value_from_healpix_map_n_subdivisions"
|
|
801
|
+
)
|
|
802
|
+
def test_get_rect_pixel_value_recursive_subdivs(
|
|
803
|
+
self,
|
|
804
|
+
mock_calculate_rect_pixel_value_from_healpix_map_n_subdivisions,
|
|
805
|
+
):
|
|
806
|
+
"""Test that the recursive subdivision works as expected with different rtol."""
|
|
807
|
+
|
|
808
|
+
# Mock the function to return a fixed value for a number of subdivisions
|
|
809
|
+
value_by_subdivisions = {
|
|
810
|
+
0: 100.0,
|
|
811
|
+
1: 110.0, # 10/110 = 0.09090909 change
|
|
812
|
+
2: 105.0, # 5/105 = 0.04761905 change
|
|
813
|
+
3: 107.0, # 2/107 = 0.01869159 change
|
|
814
|
+
4: 107.5, # 0.5/107.5 = 0.00465116 change
|
|
815
|
+
5: 107.51, # 0.01/107.51 = 0.00009301 change
|
|
816
|
+
6: 107.5099, # 0.0001/107.5099 = 0.00000093 change
|
|
817
|
+
7: 120, # Big change - but will stop because of MAX SUBDIVS
|
|
818
|
+
}
|
|
819
|
+
required_rtols = [
|
|
820
|
+
0.1,
|
|
821
|
+
0.05,
|
|
822
|
+
0.02,
|
|
823
|
+
0.005,
|
|
824
|
+
0.0001,
|
|
825
|
+
0.000001,
|
|
826
|
+
1e-12,
|
|
827
|
+
]
|
|
828
|
+
|
|
829
|
+
mock_calculate_rect_pixel_value_from_healpix_map_n_subdivisions.side_effect = (
|
|
830
|
+
lambda *args, **kwargs: np.array(
|
|
831
|
+
[
|
|
832
|
+
value_by_subdivisions[kwargs["num_subdivisions"]],
|
|
833
|
+
]
|
|
834
|
+
)
|
|
835
|
+
)
|
|
836
|
+
hp_map = ena_maps.HealpixSkyMap(
|
|
837
|
+
nside=16,
|
|
838
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
# Test the recursive subdivision by setting different tolerances to get the
|
|
842
|
+
# expected number of subdivisions and resultant mean value.
|
|
843
|
+
for expected_subdiv_level in range(1, len(required_rtols)):
|
|
844
|
+
mean, depth = hp_map.get_rect_pixel_value_recursive_subdivs(
|
|
845
|
+
rect_pix_center_lon_lat=(180, 0),
|
|
846
|
+
rect_pix_spacing_deg=4,
|
|
847
|
+
value_array=[],
|
|
848
|
+
rtol=required_rtols[expected_subdiv_level - 1],
|
|
849
|
+
max_subdivision_depth=7,
|
|
850
|
+
)
|
|
851
|
+
assert depth == expected_subdiv_level
|
|
852
|
+
np.testing.assert_equal(
|
|
853
|
+
mean,
|
|
854
|
+
value_by_subdivisions[expected_subdiv_level],
|
|
855
|
+
err_msg=f"Failed for expected_subdiv_level: {expected_subdiv_level}",
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
def test_to_rectangular_skymap(
|
|
859
|
+
self,
|
|
860
|
+
):
|
|
861
|
+
hp_map = ena_maps.HealpixSkyMap(
|
|
862
|
+
nside=64,
|
|
863
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
hp_map.data_1d["counts"] = xr.DataArray(
|
|
867
|
+
data=np.fromfunction(
|
|
868
|
+
lambda time, energy, pixel: 1000 + pixel * (10 * (energy + 1)),
|
|
869
|
+
shape=(1, 10, hp_map.num_points),
|
|
870
|
+
dtype=np.float32,
|
|
871
|
+
),
|
|
872
|
+
dims=["epoch", "energy", "pixel"],
|
|
873
|
+
)
|
|
874
|
+
hp_map.data_1d["exposure_factor"] = xr.DataArray(
|
|
875
|
+
data=np.ones((10, hp_map.num_points)),
|
|
876
|
+
dims=["energy", "pixel"],
|
|
877
|
+
)
|
|
878
|
+
hp_map.data_1d["observation_date"] = xr.DataArray(
|
|
879
|
+
data=np.ones(hp_map.num_points),
|
|
880
|
+
dims=["pixel"],
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
rect_map, subdiv_depth_dict = hp_map.to_rectangular_skymap(
|
|
884
|
+
rect_spacing_deg=2,
|
|
885
|
+
value_keys=["counts", "exposure_factor", "observation_date"],
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
for value_key, subdiv_depth in subdiv_depth_dict.items():
|
|
889
|
+
# subdiv depth should always be between 1 and
|
|
890
|
+
# ena_maps.MAX_SUBDIV_RECURSION_DEPTH
|
|
891
|
+
np.testing.assert_array_less(
|
|
892
|
+
0,
|
|
893
|
+
subdiv_depth,
|
|
894
|
+
err_msg=f"subdiv <1 for: {value_key}",
|
|
895
|
+
)
|
|
896
|
+
np.testing.assert_array_less(
|
|
897
|
+
subdiv_depth,
|
|
898
|
+
ena_maps.MAX_SUBDIV_RECURSION_DEPTH + 1,
|
|
899
|
+
err_msg=f"subdiv >MAX for: {value_key}",
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
# The min and max values of the rect and healpix maps should be close
|
|
903
|
+
# The min will have a larger relative tolerance because the variation
|
|
904
|
+
# in the test data is larger in comparison to the min value than to the max
|
|
905
|
+
np.testing.assert_allclose(
|
|
906
|
+
rect_map.data_1d[value_key].min(),
|
|
907
|
+
hp_map.data_1d[value_key].min(),
|
|
908
|
+
rtol=5e-2,
|
|
909
|
+
err_msg=f"Min values of {value_key} do not match",
|
|
910
|
+
)
|
|
911
|
+
np.testing.assert_allclose(
|
|
912
|
+
rect_map.data_1d[value_key].max(),
|
|
913
|
+
hp_map.data_1d[value_key].max(),
|
|
914
|
+
rtol=1e-3,
|
|
915
|
+
err_msg=f"Max values of {value_key} do not match",
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
# The dims of the rect map should be the same as the healpix map,
|
|
919
|
+
# except for the final pixel dimension
|
|
920
|
+
assert (
|
|
921
|
+
rect_map.data_1d[value_key].dims[:-1]
|
|
922
|
+
== hp_map.data_1d[value_key].dims[:-1]
|
|
923
|
+
)
|
|
207
924
|
|
|
208
925
|
|
|
209
926
|
class TestIndexMatching:
|
|
210
927
|
@pytest.fixture(autouse=True)
|
|
211
|
-
def
|
|
928
|
+
def _setup_rectangular_l1c_pset_products(self, setup_all_pset_products):
|
|
212
929
|
"""Setup fixture data as class attributes"""
|
|
213
|
-
self.
|
|
214
|
-
self.
|
|
930
|
+
self.rectangular_l1c_spacing_deg = setup_all_pset_products["rect_spacing"]
|
|
931
|
+
self.rectangular_l1c_pset_products = deepcopy(
|
|
932
|
+
setup_all_pset_products["rect_rectangular_l1c_pset_products"]
|
|
933
|
+
)
|
|
934
|
+
self.rectangular_psets = [
|
|
935
|
+
ena_maps.RectangularPointingSet(
|
|
936
|
+
l1c_product,
|
|
937
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
938
|
+
)
|
|
939
|
+
for l1c_product in self.rectangular_l1c_pset_products
|
|
940
|
+
]
|
|
215
941
|
|
|
216
942
|
@pytest.mark.parametrize(
|
|
217
943
|
"map_spacing_deg",
|
|
218
944
|
[0.5, 1, 10],
|
|
219
945
|
)
|
|
220
946
|
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
221
|
-
def
|
|
947
|
+
def test_match_coords_to_indices_rect_pset_to_rect_map(
|
|
222
948
|
self, mock_frame_transform_az_el, map_spacing_deg
|
|
223
949
|
):
|
|
224
950
|
# Mock frame_transform to return the az and el unchanged
|
|
@@ -227,8 +953,8 @@ class TestIndexMatching:
|
|
|
227
953
|
)
|
|
228
954
|
|
|
229
955
|
# Mock a PSET, overriding the az/el points
|
|
230
|
-
mock_pset_input_frame = ena_maps.
|
|
231
|
-
|
|
956
|
+
mock_pset_input_frame = ena_maps.RectangularPointingSet(
|
|
957
|
+
self.rectangular_l1c_pset_products[0],
|
|
232
958
|
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
233
959
|
)
|
|
234
960
|
manual_az_el_coords = np.array(
|
|
@@ -246,7 +972,7 @@ class TestIndexMatching:
|
|
|
246
972
|
[359.999999, 89.99999],
|
|
247
973
|
]
|
|
248
974
|
)
|
|
249
|
-
mock_pset_input_frame.az_el_points =
|
|
975
|
+
mock_pset_input_frame.az_el_points = manual_az_el_coords
|
|
250
976
|
|
|
251
977
|
# Manually calculate the resulting 1D pixel indices for each az/el pair
|
|
252
978
|
# (num of pixels in an az row spanning 180 deg of elevation) * (current az row)
|
|
@@ -259,15 +985,15 @@ class TestIndexMatching:
|
|
|
259
985
|
]
|
|
260
986
|
)
|
|
261
987
|
|
|
262
|
-
#
|
|
263
|
-
|
|
988
|
+
# Create the rectangular map and check the output values
|
|
989
|
+
rect_map = ena_maps.RectangularSkyMap(
|
|
264
990
|
spacing_deg=map_spacing_deg,
|
|
265
991
|
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
266
992
|
)
|
|
267
993
|
flat_indices_input_grid_output_frame = ena_maps.match_coords_to_indices(
|
|
268
|
-
mock_pset_input_frame,
|
|
994
|
+
mock_pset_input_frame, rect_map
|
|
269
995
|
)
|
|
270
|
-
assert
|
|
996
|
+
assert rect_map.num_points == 360 * 180 / map_spacing_deg**2
|
|
271
997
|
assert len(flat_indices_input_grid_output_frame) == len(manual_az_el_coords)
|
|
272
998
|
np.testing.assert_equal(
|
|
273
999
|
flat_indices_input_grid_output_frame, expected_output_pixel
|
|
@@ -275,35 +1001,267 @@ class TestIndexMatching:
|
|
|
275
1001
|
|
|
276
1002
|
# Check that the map's az/el points at the matched indices
|
|
277
1003
|
# are the same as the input az/el points to within the spacing of the map
|
|
278
|
-
matched_map_az_el =
|
|
279
|
-
flat_indices_input_grid_output_frame
|
|
280
|
-
]
|
|
1004
|
+
matched_map_az_el = rect_map.az_el_points[flat_indices_input_grid_output_frame]
|
|
281
1005
|
np.testing.assert_allclose(
|
|
282
1006
|
matched_map_az_el[:, 0],
|
|
283
1007
|
mock_pset_input_frame.az_el_points[:, 0],
|
|
284
|
-
atol=
|
|
1008
|
+
atol=map_spacing_deg,
|
|
285
1009
|
)
|
|
286
1010
|
|
|
287
|
-
|
|
1011
|
+
@pytest.mark.parametrize(
|
|
1012
|
+
"nside,degree_tolerance",
|
|
1013
|
+
[
|
|
1014
|
+
(8, 12),
|
|
1015
|
+
(16, 6),
|
|
1016
|
+
(32, 3),
|
|
1017
|
+
],
|
|
1018
|
+
ids=["nside8", "nside16", "nside32"],
|
|
1019
|
+
)
|
|
1020
|
+
@pytest.mark.parametrize("nested", [True, False], ids=["nested", "ring"])
|
|
1021
|
+
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
1022
|
+
def test_match_coords_to_indices_rect_pset_to_healpix_map(
|
|
1023
|
+
self, mock_frame_transform_az_el, nside, degree_tolerance, nested
|
|
1024
|
+
):
|
|
1025
|
+
# Mock frame_transform to return the az and el unchanged
|
|
1026
|
+
mock_frame_transform_az_el.side_effect = (
|
|
1027
|
+
lambda et, az_el, from_frame, to_frame, degrees: az_el
|
|
1028
|
+
)
|
|
1029
|
+
hp_map = ena_maps.HealpixSkyMap(
|
|
1030
|
+
nside=nside, spice_frame=geometry.SpiceFrame.ECLIPJ2000, nested=nested
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
# Make a PointingSet
|
|
1034
|
+
mock_pset_input_frame = ena_maps.RectangularPointingSet(
|
|
1035
|
+
self.rectangular_l1c_pset_products[0],
|
|
1036
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
# Match the PSET to the Healpix map
|
|
1040
|
+
healpix_indices_of_rect_pixels = ena_maps.match_coords_to_indices(
|
|
1041
|
+
mock_pset_input_frame, hp_map
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
# Check that the map's az/el points at the matched indices
|
|
1045
|
+
# are the same as the input az/el points to within degree_tolerance,
|
|
1046
|
+
# but we must ignore the polar regions and azimuthal wrap-around regions
|
|
1047
|
+
rect_equatorial_elevations_mask = (
|
|
1048
|
+
np.abs(mock_pset_input_frame.az_el_points[:, 1]) < 60
|
|
1049
|
+
)
|
|
1050
|
+
rect_az_non_wraparound_mask = (
|
|
1051
|
+
mock_pset_input_frame.az_el_points[:, 0] < 340
|
|
1052
|
+
) & (mock_pset_input_frame.az_el_points[:, 0] > 20)
|
|
1053
|
+
rect_good_az_el_mask = (
|
|
1054
|
+
rect_equatorial_elevations_mask & rect_az_non_wraparound_mask
|
|
1055
|
+
)
|
|
1056
|
+
matched_map_az_el = np.column_stack(
|
|
1057
|
+
hp.pix2ang(
|
|
1058
|
+
nside=nside,
|
|
1059
|
+
ipix=healpix_indices_of_rect_pixels,
|
|
1060
|
+
nest=nested,
|
|
1061
|
+
lonlat=True,
|
|
1062
|
+
)
|
|
1063
|
+
)
|
|
1064
|
+
np.testing.assert_allclose(
|
|
1065
|
+
matched_map_az_el[rect_good_az_el_mask, 0],
|
|
1066
|
+
mock_pset_input_frame.az_el_points[rect_good_az_el_mask, 0],
|
|
1067
|
+
atol=degree_tolerance,
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
def test_match_coords_to_indices_pset_to_invalid_map(
|
|
288
1071
|
self,
|
|
289
1072
|
):
|
|
290
|
-
mock_pset_input_frame = ena_maps.
|
|
291
|
-
|
|
1073
|
+
mock_pset_input_frame = ena_maps.RectangularPointingSet(
|
|
1074
|
+
self.rectangular_l1c_pset_products[0],
|
|
292
1075
|
spice_reference_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
293
1076
|
)
|
|
294
|
-
|
|
295
1077
|
# Until implemented, just change the tiling on a RectangularSkyMap
|
|
296
|
-
|
|
1078
|
+
mock_invalid_map = ena_maps.RectangularSkyMap(
|
|
297
1079
|
spacing_deg=2,
|
|
298
1080
|
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
299
1081
|
)
|
|
300
|
-
|
|
1082
|
+
mock_invalid_map.tiling_type = "INVALID"
|
|
301
1083
|
|
|
302
|
-
# Should raise
|
|
303
|
-
with pytest.raises(NotImplementedError):
|
|
304
|
-
ena_maps.match_coords_to_indices(mock_pset_input_frame, mock_hp_map)
|
|
305
|
-
|
|
306
|
-
mock_other_map = mock_hp_map
|
|
307
|
-
mock_other_map.tiling_type = "INVALID"
|
|
1084
|
+
# Should raise ValueError if the tiling type is invalid
|
|
308
1085
|
with pytest.raises(ValueError, match="Tiling type of the output frame"):
|
|
309
|
-
ena_maps.match_coords_to_indices(mock_pset_input_frame,
|
|
1086
|
+
ena_maps.match_coords_to_indices(mock_pset_input_frame, mock_invalid_map)
|
|
1087
|
+
|
|
1088
|
+
def test_match_coords_to_indices_pset_to_pset_error(self):
|
|
1089
|
+
mock_pset_input_frame = ena_maps.RectangularPointingSet(
|
|
1090
|
+
self.rectangular_l1c_pset_products[0],
|
|
1091
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
1092
|
+
)
|
|
1093
|
+
mock_pset_output_frame = ena_maps.RectangularPointingSet(
|
|
1094
|
+
self.rectangular_l1c_pset_products[1],
|
|
1095
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
1096
|
+
)
|
|
1097
|
+
with pytest.raises(
|
|
1098
|
+
ValueError, match="Cannot match indices between two PointingSet objects"
|
|
1099
|
+
):
|
|
1100
|
+
ena_maps.match_coords_to_indices(
|
|
1101
|
+
mock_pset_input_frame, mock_pset_output_frame
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
def test_match_coords_to_indices_map_to_map_no_et_error(self):
|
|
1105
|
+
mock_rect_map_1 = ena_maps.RectangularSkyMap(
|
|
1106
|
+
spacing_deg=2,
|
|
1107
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
1108
|
+
)
|
|
1109
|
+
mock_rect_map_2 = ena_maps.RectangularSkyMap(
|
|
1110
|
+
spacing_deg=4,
|
|
1111
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
1112
|
+
)
|
|
1113
|
+
with pytest.raises(
|
|
1114
|
+
ValueError,
|
|
1115
|
+
match="Event time must be specified if both objects are SkyMaps.",
|
|
1116
|
+
):
|
|
1117
|
+
ena_maps.match_coords_to_indices(mock_rect_map_1, mock_rect_map_2)
|
|
1118
|
+
|
|
1119
|
+
# No error if event time is specified
|
|
1120
|
+
_ = ena_maps.match_coords_to_indices(
|
|
1121
|
+
mock_rect_map_1, mock_rect_map_2, event_et=0
|
|
1122
|
+
)
|
|
1123
|
+
|
|
1124
|
+
|
|
1125
|
+
class TestAbstractSkyMap:
|
|
1126
|
+
@pytest.mark.parametrize(
|
|
1127
|
+
"skymap_props_dict",
|
|
1128
|
+
[
|
|
1129
|
+
pytest.param(
|
|
1130
|
+
# HealpixSkyMap properties
|
|
1131
|
+
{
|
|
1132
|
+
"sky_tiling_type": "HEALPIX",
|
|
1133
|
+
"nside": 32,
|
|
1134
|
+
"nested": True,
|
|
1135
|
+
"spice_reference_frame": geometry.SpiceFrame.ECLIPJ2000.name,
|
|
1136
|
+
"values_to_push_project": ["foo", "bar"],
|
|
1137
|
+
},
|
|
1138
|
+
id="healpix-skymap",
|
|
1139
|
+
),
|
|
1140
|
+
pytest.param(
|
|
1141
|
+
{
|
|
1142
|
+
"sky_tiling_type": "RECTANGULAR",
|
|
1143
|
+
"spacing_deg": 2,
|
|
1144
|
+
"spice_reference_frame": geometry.SpiceFrame.ECLIPJ2000.name,
|
|
1145
|
+
"values_to_pull_project": ["potato", "po-tah-to"],
|
|
1146
|
+
},
|
|
1147
|
+
id="rectangular-skymap",
|
|
1148
|
+
),
|
|
1149
|
+
],
|
|
1150
|
+
)
|
|
1151
|
+
def test_to_dict_and_from_dict(self, skymap_props_dict):
|
|
1152
|
+
"""Test serialization to and from dictionary"""
|
|
1153
|
+
# Make a SkyMap from the original properties dict
|
|
1154
|
+
skymap_from_dict = ena_maps.AbstractSkyMap.from_dict(skymap_props_dict)
|
|
1155
|
+
|
|
1156
|
+
# Use the SkyMap to create a new properties dict
|
|
1157
|
+
dict_from_skymap = skymap_from_dict.to_dict()
|
|
1158
|
+
|
|
1159
|
+
assert (
|
|
1160
|
+
skymap_from_dict.spice_reference_frame
|
|
1161
|
+
== geometry.SpiceFrame[skymap_props_dict["spice_reference_frame"]]
|
|
1162
|
+
)
|
|
1163
|
+
|
|
1164
|
+
if skymap_props_dict["sky_tiling_type"] == "HEALPIX":
|
|
1165
|
+
assert isinstance(skymap_from_dict, ena_maps.HealpixSkyMap), (
|
|
1166
|
+
"from_dict should return a HealpixSkyMap object"
|
|
1167
|
+
)
|
|
1168
|
+
assert skymap_from_dict.nside == skymap_props_dict["nside"]
|
|
1169
|
+
assert skymap_from_dict.nested == skymap_props_dict["nested"]
|
|
1170
|
+
assert (
|
|
1171
|
+
skymap_from_dict.values_to_push_project
|
|
1172
|
+
== skymap_props_dict["values_to_push_project"]
|
|
1173
|
+
)
|
|
1174
|
+
assert skymap_from_dict.values_to_pull_project == []
|
|
1175
|
+
|
|
1176
|
+
elif skymap_props_dict["sky_tiling_type"] == "RECTANGULAR":
|
|
1177
|
+
assert isinstance(skymap_from_dict, ena_maps.RectangularSkyMap), (
|
|
1178
|
+
"from_dict should return a RectangularSkyMap object"
|
|
1179
|
+
)
|
|
1180
|
+
assert skymap_from_dict.spacing_deg == skymap_props_dict["spacing_deg"]
|
|
1181
|
+
assert skymap_from_dict.values_to_push_project == []
|
|
1182
|
+
assert (
|
|
1183
|
+
skymap_from_dict.values_to_pull_project
|
|
1184
|
+
== skymap_props_dict["values_to_pull_project"]
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
for key in [
|
|
1188
|
+
"sky_tiling_type",
|
|
1189
|
+
"spice_reference_frame",
|
|
1190
|
+
"nside",
|
|
1191
|
+
"nested",
|
|
1192
|
+
"spacing_deg",
|
|
1193
|
+
]:
|
|
1194
|
+
if key in skymap_props_dict:
|
|
1195
|
+
assert dict_from_skymap[key] == skymap_props_dict[key]
|
|
1196
|
+
|
|
1197
|
+
# Check that the dict from the SkyMap matches the original dict ONLY after
|
|
1198
|
+
# adding automatically added "values_to_push_project"/"values_to_pull_project"
|
|
1199
|
+
# key to the original dict
|
|
1200
|
+
assert dict_from_skymap != skymap_props_dict
|
|
1201
|
+
|
|
1202
|
+
# In the dicts passed in above, the HEALPIX one is missing the pull key
|
|
1203
|
+
# and the RECTANGULAR one is missing the push key
|
|
1204
|
+
if skymap_props_dict["sky_tiling_type"] == "HEALPIX":
|
|
1205
|
+
skymap_props_dict["values_to_pull_project"] = []
|
|
1206
|
+
elif skymap_props_dict["sky_tiling_type"] == "RECTANGULAR":
|
|
1207
|
+
skymap_props_dict["values_to_push_project"] = []
|
|
1208
|
+
assert dict_from_skymap == skymap_props_dict
|
|
1209
|
+
|
|
1210
|
+
# Change a value in the new dict and check that it is not equal to the original
|
|
1211
|
+
dict_from_skymap["spice_reference_frame"] = "SPACE!"
|
|
1212
|
+
assert (
|
|
1213
|
+
dict_from_skymap["spice_reference_frame"]
|
|
1214
|
+
!= skymap_props_dict["spice_reference_frame"]
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1217
|
+
def test_to_json_and_from_json(self):
|
|
1218
|
+
"""Test serialization to and from JSON"""
|
|
1219
|
+
# Make a SkyMap from the original properties dict
|
|
1220
|
+
skymap_props_dict = {
|
|
1221
|
+
"sky_tiling_type": "HEALPIX",
|
|
1222
|
+
"nside": 32,
|
|
1223
|
+
"nested": True,
|
|
1224
|
+
"spice_reference_frame": geometry.SpiceFrame.ECLIPJ2000.name,
|
|
1225
|
+
"values_to_push_project": ["foo", "bar"],
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
# Write a temporary json file with the properties dict
|
|
1229
|
+
|
|
1230
|
+
with tempfile.NamedTemporaryFile(
|
|
1231
|
+
delete=False, suffix=".json", mode="w"
|
|
1232
|
+
) as temp_file:
|
|
1233
|
+
json.dump(skymap_props_dict, temp_file)
|
|
1234
|
+
temp_file_path_input = temp_file.name
|
|
1235
|
+
|
|
1236
|
+
# Read the json file and create a new SkyMap from it
|
|
1237
|
+
skymap_from_json = ena_maps.AbstractSkyMap.from_json(temp_file_path_input)
|
|
1238
|
+
|
|
1239
|
+
# Create json output from the SkyMap at a separate temporary file path
|
|
1240
|
+
temp_file_path_output = tempfile.NamedTemporaryFile(
|
|
1241
|
+
delete=False, suffix=".json", mode="w"
|
|
1242
|
+
).name
|
|
1243
|
+
skymap_from_json.to_json(json_path=temp_file_path_output)
|
|
1244
|
+
|
|
1245
|
+
assert skymap_from_json.spice_reference_frame == geometry.SpiceFrame.ECLIPJ2000
|
|
1246
|
+
assert skymap_from_json.tiling_type is ena_maps.SkyTilingType.HEALPIX
|
|
1247
|
+
assert skymap_from_json.nside == 32
|
|
1248
|
+
assert skymap_from_json.nested is True
|
|
1249
|
+
assert skymap_from_json.values_to_push_project == ["foo", "bar"]
|
|
1250
|
+
assert skymap_from_json.values_to_pull_project == []
|
|
1251
|
+
|
|
1252
|
+
# Expect there to be a AttributeError when accessing a non-existent key
|
|
1253
|
+
with pytest.raises(AttributeError):
|
|
1254
|
+
_ = skymap_from_json.spacing_deg
|
|
1255
|
+
|
|
1256
|
+
# Check that the json output is the same as the original input ONLY
|
|
1257
|
+
# after adding automatically added
|
|
1258
|
+
# "values_to_push_project"/"values_to_pull_project" key to the original dict
|
|
1259
|
+
with open(temp_file_path_input) as f:
|
|
1260
|
+
original_json = json.load(f)
|
|
1261
|
+
with open(temp_file_path_output) as f:
|
|
1262
|
+
output_json = json.load(f)
|
|
1263
|
+
# The output json will have added an empty list for values_to_pull_project
|
|
1264
|
+
assert original_json != output_json
|
|
1265
|
+
# add the values_to_pull_project key to the original json
|
|
1266
|
+
original_json["values_to_pull_project"] = []
|
|
1267
|
+
assert original_json == output_json
|