imap-processing 0.9.0__py3-none-any.whl → 0.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of imap-processing might be problematic. Click here for more details.
- imap_processing/_version.py +2 -2
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +749 -442
- imap_processing/cdf/config/imap_glows_global_cdf_attrs.yaml +7 -0
- imap_processing/cdf/config/imap_glows_l1a_variable_attrs.yaml +8 -2
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +0 -1
- imap_processing/cdf/config/imap_glows_l2_variable_attrs.yaml +358 -0
- imap_processing/cdf/config/imap_hi_variable_attrs.yaml +59 -25
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +22 -0
- imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +32 -8
- imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +94 -5
- imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +65 -37
- imap_processing/cdf/config/imap_swapi_variable_attrs.yaml +16 -1
- imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +7 -0
- imap_processing/cdf/config/imap_swe_l1a_variable_attrs.yaml +14 -14
- imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +25 -24
- imap_processing/cdf/config/imap_swe_l2_variable_attrs.yaml +238 -0
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +100 -92
- imap_processing/cdf/utils.py +2 -2
- imap_processing/cli.py +45 -9
- imap_processing/codice/codice_l1a.py +104 -58
- imap_processing/codice/constants.py +111 -155
- imap_processing/codice/data/esa_sweep_values.csv +256 -256
- imap_processing/codice/data/lo_stepping_values.csv +128 -128
- imap_processing/ena_maps/ena_maps.py +519 -0
- imap_processing/ena_maps/utils/map_utils.py +145 -0
- imap_processing/ena_maps/utils/spatial_utils.py +226 -0
- imap_processing/glows/__init__.py +3 -0
- imap_processing/glows/ancillary/imap_glows_pipeline_settings_v001.json +52 -0
- imap_processing/glows/l1a/glows_l1a.py +72 -14
- imap_processing/glows/l1b/glows_l1b.py +2 -1
- imap_processing/glows/l1b/glows_l1b_data.py +25 -1
- imap_processing/glows/l2/glows_l2.py +324 -0
- imap_processing/glows/l2/glows_l2_data.py +156 -51
- imap_processing/hi/l1a/science_direct_event.py +57 -51
- imap_processing/hi/l1b/hi_l1b.py +43 -28
- imap_processing/hi/l1c/hi_l1c.py +225 -42
- imap_processing/hi/utils.py +20 -3
- imap_processing/hit/l0/constants.py +2 -2
- imap_processing/hit/l0/decom_hit.py +1 -1
- imap_processing/hit/l1a/hit_l1a.py +94 -13
- imap_processing/hit/l1b/hit_l1b.py +158 -9
- imap_processing/ialirt/l0/process_codicehi.py +156 -0
- imap_processing/ialirt/l0/process_codicelo.py +5 -2
- imap_processing/ialirt/packet_definitions/ialirt.xml +28 -20
- imap_processing/ialirt/packet_definitions/ialirt_codicehi.xml +241 -0
- imap_processing/ialirt/packet_definitions/ialirt_swapi.xml +170 -0
- imap_processing/ialirt/packet_definitions/ialirt_swe.xml +258 -0
- imap_processing/ialirt/process_ephemeris.py +72 -40
- imap_processing/idex/decode.py +241 -0
- imap_processing/idex/idex_l1a.py +143 -81
- imap_processing/idex/idex_l1b.py +244 -10
- imap_processing/lo/l0/lo_science.py +61 -0
- imap_processing/lo/l1a/lo_l1a.py +98 -10
- imap_processing/lo/l1b/lo_l1b.py +2 -2
- imap_processing/lo/l1c/lo_l1c.py +2 -2
- imap_processing/lo/packet_definitions/lo_xtce.xml +1082 -9178
- imap_processing/mag/l0/decom_mag.py +2 -2
- imap_processing/mag/l1a/mag_l1a.py +7 -7
- imap_processing/mag/l1a/mag_l1a_data.py +62 -30
- imap_processing/mag/l1b/mag_l1b.py +11 -6
- imap_processing/quality_flags.py +18 -3
- imap_processing/spice/geometry.py +149 -177
- imap_processing/spice/kernels.py +26 -26
- imap_processing/spice/spin.py +233 -0
- imap_processing/spice/time.py +96 -31
- imap_processing/swapi/l1/swapi_l1.py +60 -31
- imap_processing/swapi/packet_definitions/swapi_packet_definition.xml +363 -384
- imap_processing/swe/l1a/swe_l1a.py +8 -3
- imap_processing/swe/l1a/swe_science.py +24 -24
- imap_processing/swe/l1b/swe_l1b.py +2 -1
- imap_processing/swe/l1b/swe_l1b_science.py +181 -122
- imap_processing/swe/l2/swe_l2.py +337 -70
- imap_processing/swe/utils/swe_utils.py +28 -0
- imap_processing/tests/cdf/test_utils.py +2 -2
- imap_processing/tests/codice/conftest.py +20 -17
- imap_processing/tests/codice/data/validation/imap_codice_l1a_hskp_20241110193622_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-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-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_l0.py +55 -121
- imap_processing/tests/codice/test_codice_l1a.py +147 -59
- imap_processing/tests/conftest.py +81 -22
- imap_processing/tests/ena_maps/test_ena_maps.py +309 -0
- imap_processing/tests/ena_maps/test_map_utils.py +286 -0
- imap_processing/tests/ena_maps/test_spatial_utils.py +161 -0
- imap_processing/tests/glows/conftest.py +7 -1
- imap_processing/tests/glows/test_glows_l1a_cdf.py +3 -7
- imap_processing/tests/glows/test_glows_l1a_data.py +34 -6
- imap_processing/tests/glows/test_glows_l1b_data.py +29 -17
- imap_processing/tests/glows/test_glows_l2.py +101 -0
- imap_processing/tests/hi/conftest.py +3 -3
- imap_processing/tests/hi/data/l1/imap_hi_l1b_45sensor-de_20250415_v999.cdf +0 -0
- imap_processing/tests/hi/data/l1/imap_his_pset-calibration-prod-config_20240101_v001.csv +31 -0
- imap_processing/tests/hi/test_hi_l1b.py +14 -9
- imap_processing/tests/hi/test_hi_l1c.py +136 -36
- imap_processing/tests/hi/test_l1a.py +0 -2
- imap_processing/tests/hi/test_science_direct_event.py +18 -14
- imap_processing/tests/hi/test_utils.py +16 -11
- imap_processing/tests/hit/helpers/__init__.py +0 -0
- imap_processing/tests/hit/helpers/l1_validation.py +405 -0
- imap_processing/tests/hit/test_data/sci_sample.ccsds +0 -0
- imap_processing/tests/hit/test_decom_hit.py +8 -10
- imap_processing/tests/hit/test_hit_l1a.py +117 -180
- imap_processing/tests/hit/test_hit_l1b.py +149 -55
- imap_processing/tests/hit/validation_data/hit_l1b_standard_sample2_nsrl_v4_3decimals.csv +62 -0
- imap_processing/tests/hit/validation_data/sci_sample_raw.csv +62 -0
- imap_processing/tests/ialirt/test_data/l0/20240827095047_SWE_IALIRT_packet.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/BinLog CCSDS_FRAG_TLM_20240826_152323Z_IALIRT_data_for_SDC.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/eu_SWP_IAL_20240826_152033.csv +644 -0
- imap_processing/tests/ialirt/test_data/l0/hi_fsw_view_1_ccsds.bin +0 -0
- imap_processing/tests/ialirt/test_data/l0/idle_export_eu.SWE_IALIRT_20240827_093852.csv +914 -0
- imap_processing/tests/ialirt/test_data/l0/imap_codice_l1a_hi-ialirt_20240523200000_v0.0.0.cdf +0 -0
- imap_processing/tests/ialirt/unit/test_process_codicehi.py +106 -0
- imap_processing/tests/ialirt/unit/test_process_ephemeris.py +33 -5
- imap_processing/tests/ialirt/unit/test_process_swapi.py +85 -0
- imap_processing/tests/ialirt/unit/test_process_swe.py +106 -0
- imap_processing/tests/idex/conftest.py +29 -1
- imap_processing/tests/idex/test_data/compressed_2023_102_14_24_55.pkts +0 -0
- imap_processing/tests/idex/test_data/non_compressed_2023_102_14_22_26.pkts +0 -0
- imap_processing/tests/idex/test_idex_l0.py +6 -3
- imap_processing/tests/idex/test_idex_l1a.py +151 -1
- imap_processing/tests/idex/test_idex_l1b.py +124 -2
- imap_processing/tests/lo/test_lo_l1a.py +62 -2
- imap_processing/tests/lo/test_lo_science.py +85 -0
- imap_processing/tests/lo/validation_data/Instrument_FM1_T104_R129_20240803_ILO_SPIN_EU.csv +2 -0
- imap_processing/tests/mag/conftest.py +16 -0
- imap_processing/tests/mag/test_mag_decom.py +6 -4
- imap_processing/tests/mag/test_mag_l1a.py +36 -7
- imap_processing/tests/mag/test_mag_l1b.py +55 -4
- imap_processing/tests/mag/test_mag_validation.py +148 -0
- imap_processing/tests/mag/validation/L1a/T001/all_p_ones.txt +19200 -0
- imap_processing/tests/mag/validation/L1a/T001/mag-l0-l1a-t001-in.bin +0 -0
- imap_processing/tests/mag/validation/L1a/T001/mag-l0-l1a-t001-out.csv +17 -0
- imap_processing/tests/mag/validation/L1a/T002/all_n_ones.txt +19200 -0
- imap_processing/tests/mag/validation/L1a/T002/mag-l0-l1a-t002-in.bin +0 -0
- imap_processing/tests/mag/validation/L1a/T002/mag-l0-l1a-t002-out.csv +17 -0
- imap_processing/tests/mag/validation/L1a/T003/field_like.txt +19200 -0
- imap_processing/tests/mag/validation/L1a/T003/mag-l0-l1a-t003-in.bin +0 -0
- imap_processing/tests/mag/validation/L1a/T003/mag-l0-l1a-t003-out.csv +17 -0
- imap_processing/tests/mag/validation/L1a/T004/field_like.txt +19200 -0
- imap_processing/tests/mag/validation/L1a/T004/mag-l0-l1a-t004-in.bin +0 -0
- imap_processing/tests/mag/validation/L1a/T004/mag-l0-l1a-t004-out.csv +17 -0
- imap_processing/tests/mag/validation/L1a/T005/field_like_range_change.txt +19200 -0
- imap_processing/tests/mag/validation/L1a/T005/mag-l0-l1a-t005-in.bin +0 -0
- imap_processing/tests/mag/validation/L1a/T005/mag-l0-l1a-t005-out.csv +17 -0
- imap_processing/tests/mag/validation/L1a/T006/hdr_field.txt +19200 -0
- imap_processing/tests/mag/validation/L1a/T006/mag-l0-l1a-t006-in.bin +0 -0
- imap_processing/tests/mag/validation/L1a/T006/mag-l0-l1a-t006-out.csv +17 -0
- imap_processing/tests/mag/validation/L1a/T007/hdr_field_and_range_change.txt +19200 -0
- imap_processing/tests/mag/validation/L1a/T007/mag-l0-l1a-t007-in.bin +0 -0
- imap_processing/tests/mag/validation/L1a/T007/mag-l0-l1a-t007-out.csv +17 -0
- imap_processing/tests/mag/validation/L1a/T008/field_like_range_change.txt +19200 -0
- imap_processing/tests/mag/validation/L1a/T008/mag-l0-l1a-t008-in.bin +0 -0
- imap_processing/tests/mag/validation/L1a/T008/mag-l0-l1a-t008-out.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T009/data.bin +0 -0
- imap_processing/tests/mag/validation/L1b/T009/field_like_all_ranges.txt +19200 -0
- imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-in.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-magi-out.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T009/mag-l1a-l1b-t009-mago-out.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T010/data.bin +0 -0
- imap_processing/tests/mag/validation/L1b/T010/field_like_all_ranges.txt +19200 -0
- imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-in.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-magi-out.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T010/mag-l1a-l1b-t010-mago-out.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T011/data.bin +0 -0
- imap_processing/tests/mag/validation/L1b/T011/field_like_all_ranges.txt +19200 -0
- imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-in.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-magi-out.csv +17 -0
- imap_processing/tests/mag/validation/L1b/T011/mag-l1a-l1b-t011-mago-out.csv +17 -0
- imap_processing/tests/spice/test_geometry.py +128 -133
- imap_processing/tests/spice/test_kernels.py +37 -37
- imap_processing/tests/spice/test_spin.py +184 -0
- imap_processing/tests/spice/test_time.py +43 -20
- imap_processing/tests/swapi/test_swapi_l1.py +11 -10
- imap_processing/tests/swapi/test_swapi_l2.py +13 -3
- imap_processing/tests/swe/test_swe_l1a.py +1 -1
- imap_processing/tests/swe/test_swe_l1b.py +20 -3
- imap_processing/tests/swe/test_swe_l1b_science.py +54 -35
- imap_processing/tests/swe/test_swe_l2.py +148 -5
- imap_processing/tests/test_cli.py +39 -7
- imap_processing/tests/test_quality_flags.py +19 -19
- imap_processing/tests/test_utils.py +3 -2
- imap_processing/tests/ultra/test_data/l0/ultra45_raw_sc_ultrarawimg_withFSWcalcs_FM45_40P_Phi28p5_BeamCal_LinearScan_phi2850_theta-000_20240207T102740.csv +3314 -3314
- imap_processing/tests/ultra/test_data/mock_data.py +161 -0
- imap_processing/tests/ultra/unit/conftest.py +73 -0
- imap_processing/tests/ultra/unit/test_badtimes.py +58 -0
- imap_processing/tests/ultra/unit/test_cullingmask.py +87 -0
- imap_processing/tests/ultra/unit/test_de.py +61 -60
- imap_processing/tests/ultra/unit/test_ultra_l1a.py +3 -3
- imap_processing/tests/ultra/unit/test_ultra_l1b.py +51 -77
- imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +5 -5
- imap_processing/tests/ultra/unit/test_ultra_l1b_culling.py +114 -0
- imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +86 -26
- imap_processing/tests/ultra/unit/test_ultra_l1c.py +1 -1
- imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +3 -3
- imap_processing/ultra/constants.py +11 -1
- imap_processing/ultra/l1a/ultra_l1a.py +2 -2
- imap_processing/ultra/l1b/badtimes.py +22 -5
- imap_processing/ultra/l1b/cullingmask.py +31 -5
- imap_processing/ultra/l1b/de.py +32 -37
- imap_processing/ultra/l1b/extendedspin.py +44 -20
- imap_processing/ultra/l1b/ultra_l1b.py +21 -22
- imap_processing/ultra/l1b/ultra_l1b_culling.py +190 -0
- imap_processing/ultra/l1b/ultra_l1b_extended.py +81 -30
- imap_processing/ultra/l1c/histogram.py +6 -2
- imap_processing/ultra/l1c/pset.py +6 -2
- imap_processing/ultra/l1c/ultra_l1c.py +2 -3
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +4 -3
- imap_processing/ultra/utils/ultra_l1_utils.py +70 -14
- imap_processing/utils.py +2 -2
- {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/METADATA +7 -2
- {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/RECORD +235 -152
- imap_processing/tests/codice/data/eu_unit_lookup_table.csv +0 -101
- imap_processing/tests/codice/data/idle_export_eu.COD_NHK_20230822_122700 2.csv +0 -100
- imap_processing/tests/codice/data/idle_export_raw.COD_NHK_20230822_122700.csv +0 -100
- imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
- imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
- imap_processing/tests/hit/test_data/sci_sample1.ccsds +0 -0
- imap_processing/tests/ultra/unit/test_spatial_utils.py +0 -125
- imap_processing/ultra/utils/spatial_utils.py +0 -221
- /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_APP_NHK.bin +0 -0
- /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_APP_NHK.csv +0 -0
- /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_SCI_CNT.bin +0 -0
- /imap_processing/tests/hi/{test_data → data}/l0/20231030_H45_SCI_DE.bin +0 -0
- /imap_processing/tests/hi/{test_data → data}/l0/H90_NHK_20241104.bin +0 -0
- /imap_processing/tests/hi/{test_data → data}/l0/H90_sci_cnt_20241104.bin +0 -0
- /imap_processing/tests/hi/{test_data → data}/l0/H90_sci_de_20241104.bin +0 -0
- /imap_processing/tests/hi/{test_data → data}/l0/README.txt +0 -0
- /imap_processing/tests/idex/{imap_idex_l0_raw_20231214_v001.pkts → test_data/imap_idex_l0_raw_20231214_v001.pkts} +0 -0
- /imap_processing/tests/idex/{impact_14_tof_high_data.txt → test_data/impact_14_tof_high_data.txt} +0 -0
- /imap_processing/tests/mag/{imap_mag_l1a_norm-magi_20251017_v001.cdf → validation/imap_mag_l1a_norm-magi_20251017_v001.cdf} +0 -0
- /imap_processing/tests/mag/{mag_l0_test_data.pkts → validation/mag_l0_test_data.pkts} +0 -0
- /imap_processing/tests/mag/{mag_l0_test_output.csv → validation/mag_l0_test_output.csv} +0 -0
- /imap_processing/tests/mag/{mag_l1_test_data.pkts → validation/mag_l1_test_data.pkts} +0 -0
- /imap_processing/tests/mag/{mag_l1a_test_output.csv → validation/mag_l1a_test_output.csv} +0 -0
- {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.9.0.dist-info → imap_processing-0.11.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Test classes and methods in ena_maps.py."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from unittest import mock
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pytest
|
|
9
|
+
import xarray as xr
|
|
10
|
+
|
|
11
|
+
from imap_processing.ena_maps import ena_maps
|
|
12
|
+
from imap_processing.spice import geometry
|
|
13
|
+
from imap_processing.tests.ultra.test_data.mock_data import mock_l1c_pset_product
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture()
|
|
17
|
+
def l1c_pset_products():
|
|
18
|
+
"""Make fake L1C Ultra PSET products for testing"""
|
|
19
|
+
l1c_spatial_bin_spacing_deg = 10
|
|
20
|
+
return {
|
|
21
|
+
"spacing": l1c_spatial_bin_spacing_deg,
|
|
22
|
+
"products": [
|
|
23
|
+
mock_l1c_pset_product(
|
|
24
|
+
spacing_deg=l1c_spatial_bin_spacing_deg,
|
|
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
|
+
],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestUltraPointingSet:
|
|
41
|
+
@pytest.fixture(autouse=True)
|
|
42
|
+
def _setup_ultra_l1c_pset_products(self, l1c_pset_products):
|
|
43
|
+
"""Setup fixture data as class attributes"""
|
|
44
|
+
self.l1c_spatial_bin_spacing_deg = l1c_pset_products["spacing"]
|
|
45
|
+
self.l1c_pset_products = l1c_pset_products["products"]
|
|
46
|
+
|
|
47
|
+
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
48
|
+
def test_instantiate(self):
|
|
49
|
+
"""Test instantiation of UltraPointingSet"""
|
|
50
|
+
ultra_psets = [
|
|
51
|
+
ena_maps.UltraPointingSet(
|
|
52
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
53
|
+
l1c_dataset=l1c_product,
|
|
54
|
+
)
|
|
55
|
+
for l1c_product in self.l1c_pset_products
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
for ultra_pset in ultra_psets:
|
|
59
|
+
# Check tiling is rectangular
|
|
60
|
+
assert ultra_pset.tiling_type == ena_maps.SkyTilingType.RECTANGULAR
|
|
61
|
+
|
|
62
|
+
# Check that the reference frame is correctly set
|
|
63
|
+
assert ultra_pset.spice_reference_frame == geometry.SpiceFrame.IMAP_DPS
|
|
64
|
+
|
|
65
|
+
# Check the number of points is (360/0.5) * (180/0.5)
|
|
66
|
+
np.testing.assert_equal(
|
|
67
|
+
ultra_pset.num_points,
|
|
68
|
+
int(360 * 180 / (self.l1c_spatial_bin_spacing_deg**2)),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Check the repr exists
|
|
72
|
+
assert "UltraPointingSet" in repr(ultra_pset)
|
|
73
|
+
|
|
74
|
+
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
75
|
+
def test_uneven_spacing_raises_error(self):
|
|
76
|
+
"""Test that uneven spacing in az/el raises ValueError"""
|
|
77
|
+
|
|
78
|
+
# Create dataset with uneven az spacing
|
|
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)
|
|
83
|
+
|
|
84
|
+
with pytest.raises(ValueError, match="Azimuth bin spacing is not uniform"):
|
|
85
|
+
ena_maps.UltraPointingSet(
|
|
86
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
87
|
+
l1c_dataset=uneven_az_dataset,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
uneven_az_dataset["azimuth_bin_center"] = np.arange(5)
|
|
91
|
+
uneven_az_dataset["elevation_bin_center"] = np.array([0, 5, 15, 20, 30])
|
|
92
|
+
|
|
93
|
+
with pytest.raises(ValueError, match="Elevation bin spacing is not uniform"):
|
|
94
|
+
ena_maps.UltraPointingSet(
|
|
95
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
96
|
+
l1c_dataset=uneven_az_dataset,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Even but not the same spacing between az and el
|
|
100
|
+
uneven_az_dataset["azimuth_bin_center"] = np.arange(5)
|
|
101
|
+
uneven_az_dataset["elevation_bin_center"] = np.arange(5) * 2
|
|
102
|
+
|
|
103
|
+
with pytest.raises(
|
|
104
|
+
ValueError, match="Azimuth and elevation bin spacing do not match:"
|
|
105
|
+
):
|
|
106
|
+
ena_maps.UltraPointingSet(
|
|
107
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
108
|
+
l1c_dataset=uneven_az_dataset,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestRectangularSkyMap:
|
|
113
|
+
@pytest.fixture(autouse=True)
|
|
114
|
+
def _setup_ultra_l1c_pset_products(self, l1c_pset_products):
|
|
115
|
+
"""Setup fixture data as class attributes"""
|
|
116
|
+
self.l1c_spatial_bin_spacing_deg = l1c_pset_products["spacing"]
|
|
117
|
+
self.l1c_pset_products = l1c_pset_products["products"]
|
|
118
|
+
self.ultra_psets = [
|
|
119
|
+
ena_maps.UltraPointingSet(
|
|
120
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
121
|
+
l1c_dataset=l1c_product,
|
|
122
|
+
)
|
|
123
|
+
for l1c_product in self.l1c_pset_products
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
def test_instantiate(self):
|
|
127
|
+
"""Test instantiation of RectangularSkyMap"""
|
|
128
|
+
rm = ena_maps.RectangularSkyMap(
|
|
129
|
+
spacing_deg=2,
|
|
130
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Check that the map is empty
|
|
134
|
+
assert rm.data_dict == {}
|
|
135
|
+
|
|
136
|
+
# Check that the reference frame is correctly set
|
|
137
|
+
assert rm.spice_reference_frame == geometry.SpiceFrame.ECLIPJ2000
|
|
138
|
+
|
|
139
|
+
# Check the number of points is (360/2) * (180/2)
|
|
140
|
+
np.testing.assert_equal(rm.num_points, int(360 * 180 / 4))
|
|
141
|
+
|
|
142
|
+
# Check the repr exists
|
|
143
|
+
assert "RectangularSkyMap" in repr(rm)
|
|
144
|
+
|
|
145
|
+
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
146
|
+
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
147
|
+
def test_project_pset_values_to_map_push_method(self, mock_frame_transform_az_el):
|
|
148
|
+
"""
|
|
149
|
+
Test projection of PSET values to Rect. Map w "push" index matching method.
|
|
150
|
+
|
|
151
|
+
If frame_transform_az_el is mocked to return the az and el unchanged, and the
|
|
152
|
+
map has the same spacing as the PSETs, then the map should have
|
|
153
|
+
the same values as the PSETs, summed.
|
|
154
|
+
"""
|
|
155
|
+
index_matching_method = ena_maps.IndexMatchMethod.PUSH
|
|
156
|
+
|
|
157
|
+
pset_spacing_deg = self.ultra_psets[0].spacing_deg
|
|
158
|
+
|
|
159
|
+
# Mock frame_transform to return the az and el unchanged
|
|
160
|
+
mock_frame_transform_az_el.side_effect = (
|
|
161
|
+
lambda et, az_el, from_frame, to_frame, degrees: az_el
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
rectangular_map = ena_maps.RectangularSkyMap(
|
|
165
|
+
spacing_deg=pset_spacing_deg,
|
|
166
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Project each PSET's values to the map
|
|
170
|
+
for ultra_pset in self.ultra_psets:
|
|
171
|
+
rectangular_map.project_pset_values_to_map(
|
|
172
|
+
ultra_pset,
|
|
173
|
+
value_keys=["counts", "exposure_time"],
|
|
174
|
+
index_match_method=index_matching_method,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Check that the map has been updated
|
|
178
|
+
assert rectangular_map.data_dict != {}
|
|
179
|
+
|
|
180
|
+
# Check that the map has the same values as the PSETs, summed
|
|
181
|
+
simple_summed_pset_counts = np.sum(
|
|
182
|
+
[pset["counts"].values for pset in self.l1c_pset_products], axis=0
|
|
183
|
+
).reshape(rectangular_map.data_dict["counts"].shape)
|
|
184
|
+
|
|
185
|
+
np.testing.assert_allclose(
|
|
186
|
+
rectangular_map.data_dict["counts"],
|
|
187
|
+
simple_summed_pset_counts,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@pytest.mark.usefixtures("_setup_ultra_l1c_pset_products")
|
|
191
|
+
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
192
|
+
def test_project_pset_values_to_map_pull_method(self, mock_frame_transform_az_el):
|
|
193
|
+
"""Test projection to Rect. Map fails w "pull" index matching method."""
|
|
194
|
+
|
|
195
|
+
index_matching_method = ena_maps.IndexMatchMethod.PULL
|
|
196
|
+
rectangular_map = ena_maps.RectangularSkyMap(
|
|
197
|
+
spacing_deg=10,
|
|
198
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
with pytest.raises(NotImplementedError):
|
|
202
|
+
rectangular_map.project_pset_values_to_map(
|
|
203
|
+
self.ultra_psets[0],
|
|
204
|
+
value_keys=["counts", "exposure_time"],
|
|
205
|
+
index_match_method=index_matching_method,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class TestIndexMatching:
|
|
210
|
+
@pytest.fixture(autouse=True)
|
|
211
|
+
def _setup_ultra_l1c_pset_products(self, l1c_pset_products):
|
|
212
|
+
"""Setup fixture data as class attributes"""
|
|
213
|
+
self.l1c_spatial_bin_spacing_deg = l1c_pset_products["spacing"]
|
|
214
|
+
self.l1c_pset_products = l1c_pset_products["products"]
|
|
215
|
+
|
|
216
|
+
@pytest.mark.parametrize(
|
|
217
|
+
"map_spacing_deg",
|
|
218
|
+
[0.5, 1, 10],
|
|
219
|
+
)
|
|
220
|
+
@mock.patch("imap_processing.spice.geometry.frame_transform_az_el")
|
|
221
|
+
def test_match_coords_to_indices_pset_to_rect_map(
|
|
222
|
+
self, mock_frame_transform_az_el, map_spacing_deg
|
|
223
|
+
):
|
|
224
|
+
# Mock frame_transform to return the az and el unchanged
|
|
225
|
+
mock_frame_transform_az_el.side_effect = (
|
|
226
|
+
lambda et, az_el, from_frame, to_frame, degrees: az_el
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Mock a PSET, overriding the az/el points
|
|
230
|
+
mock_pset_input_frame = ena_maps.UltraPointingSet(
|
|
231
|
+
l1c_dataset=self.l1c_pset_products[0],
|
|
232
|
+
spice_reference_frame=geometry.SpiceFrame.IMAP_DPS,
|
|
233
|
+
)
|
|
234
|
+
manual_az_el_coords = np.array(
|
|
235
|
+
[
|
|
236
|
+
[0, -90], # always -> RectangularSkyMap pixel 0
|
|
237
|
+
[0.4999999, -90],
|
|
238
|
+
[180.5, -89.5],
|
|
239
|
+
[359.5, -89.5],
|
|
240
|
+
[0.5, 0],
|
|
241
|
+
[180.5, 0],
|
|
242
|
+
[359.5, 0],
|
|
243
|
+
[0.5, 89.5],
|
|
244
|
+
[180.5, 89.5],
|
|
245
|
+
[359.5, 89.5],
|
|
246
|
+
[359.999999, 89.99999],
|
|
247
|
+
]
|
|
248
|
+
)
|
|
249
|
+
mock_pset_input_frame.az_el_points = np.deg2rad(manual_az_el_coords)
|
|
250
|
+
|
|
251
|
+
# Manually calculate the resulting 1D pixel indices for each az/el pair
|
|
252
|
+
# (num of pixels in an az row spanning 180 deg of elevation) * (current az row)
|
|
253
|
+
# + (pixel along in current az row)
|
|
254
|
+
expected_output_pixel = np.array(
|
|
255
|
+
[
|
|
256
|
+
(az // map_spacing_deg) * (180 // map_spacing_deg)
|
|
257
|
+
+ ((90 + el) // map_spacing_deg)
|
|
258
|
+
for [az, el] in manual_az_el_coords
|
|
259
|
+
]
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Mock the rectangular map and check the output values
|
|
263
|
+
mock_rect_map = ena_maps.RectangularSkyMap(
|
|
264
|
+
spacing_deg=map_spacing_deg,
|
|
265
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
266
|
+
)
|
|
267
|
+
flat_indices_input_grid_output_frame = ena_maps.match_coords_to_indices(
|
|
268
|
+
mock_pset_input_frame, mock_rect_map
|
|
269
|
+
)
|
|
270
|
+
assert mock_rect_map.num_points == 360 * 180 / map_spacing_deg**2
|
|
271
|
+
assert len(flat_indices_input_grid_output_frame) == len(manual_az_el_coords)
|
|
272
|
+
np.testing.assert_equal(
|
|
273
|
+
flat_indices_input_grid_output_frame, expected_output_pixel
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
# Check that the map's az/el points at the matched indices
|
|
277
|
+
# are the same as the input az/el points to within the spacing of the map
|
|
278
|
+
matched_map_az_el = mock_rect_map.az_el_points[
|
|
279
|
+
flat_indices_input_grid_output_frame
|
|
280
|
+
]
|
|
281
|
+
np.testing.assert_allclose(
|
|
282
|
+
matched_map_az_el[:, 0],
|
|
283
|
+
mock_pset_input_frame.az_el_points[:, 0],
|
|
284
|
+
atol=np.deg2rad(map_spacing_deg),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def test_match_coords_to_indices_pset_to_healpix_map_other_map(
|
|
288
|
+
self,
|
|
289
|
+
):
|
|
290
|
+
mock_pset_input_frame = ena_maps.UltraPointingSet(
|
|
291
|
+
l1c_dataset=self.l1c_pset_products[0],
|
|
292
|
+
spice_reference_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Until implemented, just change the tiling on a RectangularSkyMap
|
|
296
|
+
mock_hp_map = ena_maps.RectangularSkyMap(
|
|
297
|
+
spacing_deg=2,
|
|
298
|
+
spice_frame=geometry.SpiceFrame.ECLIPJ2000,
|
|
299
|
+
)
|
|
300
|
+
mock_hp_map.tiling_type = ena_maps.SkyTilingType.HEALPIX
|
|
301
|
+
|
|
302
|
+
# Should raise NotImplementedError
|
|
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"
|
|
308
|
+
with pytest.raises(ValueError, match="Tiling type of the output frame"):
|
|
309
|
+
ena_maps.match_coords_to_indices(mock_pset_input_frame, mock_other_map)
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from imap_processing.ena_maps.utils import map_utils
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestENAMapMappingUtils:
|
|
8
|
+
def test_bin_single_array_at_indices(
|
|
9
|
+
self,
|
|
10
|
+
):
|
|
11
|
+
"""Test coverage for bin_single_array_at_indices function w/ simple 1D input"""
|
|
12
|
+
value_array = np.array([1, 2, 3, 4, 5, 6])
|
|
13
|
+
input_indices = np.array([0, 1, 2, 2, 1, 0])
|
|
14
|
+
projection_indices = np.array([1, 2, 3, 1, 2, 3])
|
|
15
|
+
projection_grid_shape = (5,)
|
|
16
|
+
expected_projection_values = np.array([0, 4, 4, 4, 0])
|
|
17
|
+
projection_values = map_utils.bin_single_array_at_indices(
|
|
18
|
+
value_array,
|
|
19
|
+
input_indices=input_indices,
|
|
20
|
+
projection_indices=projection_indices,
|
|
21
|
+
projection_grid_shape=projection_grid_shape,
|
|
22
|
+
)
|
|
23
|
+
np.testing.assert_equal(projection_values, expected_projection_values)
|
|
24
|
+
|
|
25
|
+
def test_bin_single_array_at_indices_extra_axis(
|
|
26
|
+
self,
|
|
27
|
+
):
|
|
28
|
+
"""Test coverage for bin_single_array_at_indices function w/ simple 2D input,
|
|
29
|
+
Corresponding to an extra axis that is not spatially binned.
|
|
30
|
+
"""
|
|
31
|
+
# Binning will occur along axis 0 (combining 1, 2, 3 and 4, 5, 6 separately)
|
|
32
|
+
value_array = np.array(
|
|
33
|
+
[
|
|
34
|
+
[1, 4],
|
|
35
|
+
[2, 5],
|
|
36
|
+
[3, 6],
|
|
37
|
+
]
|
|
38
|
+
)
|
|
39
|
+
input_indices = np.array([0, 1, 2, 2])
|
|
40
|
+
projection_indices = np.array([1, 0, 1, 6])
|
|
41
|
+
projection_grid_shape = (7, 1)
|
|
42
|
+
expected_projection_values = np.array(
|
|
43
|
+
[
|
|
44
|
+
[2, 5],
|
|
45
|
+
[4, 10],
|
|
46
|
+
[0, 0],
|
|
47
|
+
[0, 0],
|
|
48
|
+
[0, 0],
|
|
49
|
+
[0, 0],
|
|
50
|
+
[3, 6],
|
|
51
|
+
]
|
|
52
|
+
)
|
|
53
|
+
projection_values = map_utils.bin_single_array_at_indices(
|
|
54
|
+
value_array,
|
|
55
|
+
input_indices=input_indices,
|
|
56
|
+
projection_indices=projection_indices,
|
|
57
|
+
projection_grid_shape=projection_grid_shape,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
np.testing.assert_equal(projection_values, expected_projection_values)
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parametrize(
|
|
63
|
+
"projection_grid_shape", [(1, 1), (10, 10), (180, 360), (360, 720), (360, 180)]
|
|
64
|
+
)
|
|
65
|
+
@pytest.mark.parametrize(
|
|
66
|
+
"input_grid_shape", [(1, 1), (10, 10), (180, 360), (360, 720), (360, 180)]
|
|
67
|
+
)
|
|
68
|
+
def test_bin_single_array_at_indices_complex_2d(
|
|
69
|
+
self, projection_grid_shape, input_grid_shape
|
|
70
|
+
):
|
|
71
|
+
"""Test coverage for bin_single_array_at_indices function w/ complex 2D input,
|
|
72
|
+
Corresponding to an extra axis that is not spatially binned.
|
|
73
|
+
Parameterized across different input and projection grid shapes.
|
|
74
|
+
"""
|
|
75
|
+
np.random.seed(0)
|
|
76
|
+
extra_axis_size = 11 # Another axis which is not spatially binned, e.g. energy
|
|
77
|
+
input_grid_size = np.prod(input_grid_shape)
|
|
78
|
+
projection_grid_size = np.prod(projection_grid_shape)
|
|
79
|
+
value_array = np.random.rand(input_grid_size, extra_axis_size)
|
|
80
|
+
input_indices = np.random.randint(0, input_grid_size, size=1000)
|
|
81
|
+
projection_indices = np.random.randint(0, projection_grid_size, size=1000)
|
|
82
|
+
projection_values = map_utils.bin_single_array_at_indices(
|
|
83
|
+
value_array,
|
|
84
|
+
input_indices=input_indices,
|
|
85
|
+
projection_indices=projection_indices,
|
|
86
|
+
projection_grid_shape=projection_grid_shape,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Explicitly check that the shape of the output is the same as projection grid
|
|
90
|
+
np.testing.assert_equal(
|
|
91
|
+
projection_values.shape,
|
|
92
|
+
(
|
|
93
|
+
projection_grid_size,
|
|
94
|
+
extra_axis_size,
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Create the expected projection values by summing the input values in a loop
|
|
99
|
+
# This is different from the binning function, which uses np.bincount
|
|
100
|
+
expected_projection_values = np.zeros((projection_grid_size, extra_axis_size))
|
|
101
|
+
for ii, ip in zip(input_indices, projection_indices):
|
|
102
|
+
expected_projection_values[ip, :] += value_array[ii, :]
|
|
103
|
+
|
|
104
|
+
np.testing.assert_allclose(projection_values, expected_projection_values)
|
|
105
|
+
|
|
106
|
+
@pytest.mark.parametrize("projection_grid_shape", [(1, 1), (10, 10), (180, 360)])
|
|
107
|
+
@pytest.mark.parametrize("input_grid_shape", [(1, 1), (10, 10), (180, 360)])
|
|
108
|
+
@pytest.mark.parametrize("num_extra_dims", [1, 2, 3, 5])
|
|
109
|
+
def test_bin_single_array_at_indices_complex_3d(
|
|
110
|
+
self, projection_grid_shape, input_grid_shape, num_extra_dims
|
|
111
|
+
):
|
|
112
|
+
"""Test coverage for bin_single_array_at_indices function w/ complex N-Dim input
|
|
113
|
+
Corresponding to 2 extra axes that are not spatially binned.
|
|
114
|
+
Parameterized across different input and projection grid shapes.
|
|
115
|
+
"""
|
|
116
|
+
np.random.seed(0)
|
|
117
|
+
extra_axes_sizes = np.full(num_extra_dims, 3, dtype=int).tolist()
|
|
118
|
+
input_grid_size = np.prod(input_grid_shape)
|
|
119
|
+
projection_grid_size = np.prod(projection_grid_shape)
|
|
120
|
+
value_array = np.random.rand(input_grid_size, *extra_axes_sizes)
|
|
121
|
+
input_indices = np.random.randint(0, input_grid_size, size=1000)
|
|
122
|
+
projection_indices = np.random.randint(0, projection_grid_size, size=1000)
|
|
123
|
+
projection_values = map_utils.bin_single_array_at_indices(
|
|
124
|
+
value_array,
|
|
125
|
+
input_indices=input_indices,
|
|
126
|
+
projection_indices=projection_indices,
|
|
127
|
+
projection_grid_shape=projection_grid_shape,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Explicitly check that the shape of the output is the same as projection grid
|
|
131
|
+
np.testing.assert_equal(
|
|
132
|
+
projection_values.shape,
|
|
133
|
+
(projection_grid_size, *extra_axes_sizes),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Create the expected projection values by summing the input values in a loop
|
|
137
|
+
# This is different from the binning function, which uses np.bincount
|
|
138
|
+
expected_projection_values = np.zeros((projection_grid_size, *extra_axes_sizes))
|
|
139
|
+
for ii, ip in zip(input_indices, projection_indices):
|
|
140
|
+
expected_projection_values[ip, ...] += value_array[ii, ...]
|
|
141
|
+
|
|
142
|
+
np.testing.assert_allclose(projection_values, expected_projection_values)
|
|
143
|
+
|
|
144
|
+
# Parameterize by the size of the projection grid,
|
|
145
|
+
# which is not necessarily same size as input grid
|
|
146
|
+
@pytest.mark.parametrize("projection_grid_shape", [(1, 1), (10, 10), (360, 720)])
|
|
147
|
+
def test_bin_values_at_indices_collapse_to_idx_zero(self, projection_grid_shape):
|
|
148
|
+
"""Test coverage for bin_values_at_indices function w/ dict of multiple
|
|
149
|
+
1D input value arrays and a single 2D input value array.
|
|
150
|
+
All input values are binned to the first index of the projection grid.
|
|
151
|
+
Parameterized across different projection grid shapes.
|
|
152
|
+
"""
|
|
153
|
+
# 1D input values (2nd will be scalar multiple of 1st)
|
|
154
|
+
input_values_1d_1 = np.array([0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
|
|
155
|
+
scale_factor_1d = 1.5
|
|
156
|
+
input_values_1d_2 = input_values_1d_1 * scale_factor_1d
|
|
157
|
+
|
|
158
|
+
# 2D input values. The second axis (different cols) will be summed independently
|
|
159
|
+
input_values_2d = np.array(
|
|
160
|
+
[
|
|
161
|
+
[-0.5, 0, 0.5],
|
|
162
|
+
[1, 2, 3],
|
|
163
|
+
[4, 5, 6],
|
|
164
|
+
[7, 8, 9],
|
|
165
|
+
[0, 0, 0],
|
|
166
|
+
[0, 0, 0],
|
|
167
|
+
[0, 0, 0],
|
|
168
|
+
[0, 0, 0],
|
|
169
|
+
[0, 0, 0],
|
|
170
|
+
[0, 0, 0],
|
|
171
|
+
[0, 0, 0],
|
|
172
|
+
[0, 0, 0],
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
extra_axis_size_2d = input_values_2d.shape[1]
|
|
176
|
+
|
|
177
|
+
# 3D input values
|
|
178
|
+
input_values_3d = np.zeros((input_values_2d.shape[0], 3, 3))
|
|
179
|
+
input_values_3d[:2] = np.array(
|
|
180
|
+
[
|
|
181
|
+
[
|
|
182
|
+
[1, 2, 3],
|
|
183
|
+
[4, 5, 6],
|
|
184
|
+
[7, 8, 9],
|
|
185
|
+
],
|
|
186
|
+
[
|
|
187
|
+
[10, 11, 12],
|
|
188
|
+
[13, 14, 15],
|
|
189
|
+
[16, 17, 18],
|
|
190
|
+
],
|
|
191
|
+
]
|
|
192
|
+
)
|
|
193
|
+
extra_axes_size_3d = input_values_3d.shape[1:]
|
|
194
|
+
|
|
195
|
+
# Set up the expected projection values
|
|
196
|
+
expected_projection_values_1d_1 = np.zeros(projection_grid_shape).ravel()
|
|
197
|
+
expected_projection_values_1d_1[0] = np.sum(input_values_1d_1)
|
|
198
|
+
expected_projection_values_1d_2 = (
|
|
199
|
+
expected_projection_values_1d_1 * scale_factor_1d
|
|
200
|
+
)
|
|
201
|
+
expected_projection_values_2d = np.zeros(
|
|
202
|
+
(np.prod(projection_grid_shape), extra_axis_size_2d)
|
|
203
|
+
)
|
|
204
|
+
expected_projection_values_2d[0, :] = np.array([11.5, 15, 18.5])
|
|
205
|
+
expected_projection_values_3d = np.zeros(
|
|
206
|
+
(np.prod(projection_grid_shape), *extra_axes_size_3d)
|
|
207
|
+
)
|
|
208
|
+
expected_projection_values_3d[0, :, :] = np.array(
|
|
209
|
+
[
|
|
210
|
+
[11, 13, 15],
|
|
211
|
+
[17, 19, 21],
|
|
212
|
+
[23, 25, 27],
|
|
213
|
+
]
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
input_values_to_bin = {
|
|
217
|
+
"sum_variable_1d_1": input_values_1d_1,
|
|
218
|
+
"sum_variable_1d_2": input_values_1d_2,
|
|
219
|
+
"sum_variable_2d": np.array(input_values_2d),
|
|
220
|
+
"sum_variable_3d": np.array(input_values_3d),
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# Set up indices
|
|
224
|
+
input_indices = np.arange(len(input_values_1d_1))
|
|
225
|
+
projection_indices = np.zeros_like(input_indices)
|
|
226
|
+
|
|
227
|
+
output_dict = map_utils.bin_values_at_indices(
|
|
228
|
+
projection_indices=projection_indices,
|
|
229
|
+
projection_grid_shape=projection_grid_shape,
|
|
230
|
+
input_values_to_bin=input_values_to_bin,
|
|
231
|
+
input_indices=input_indices,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
np.testing.assert_equal(
|
|
235
|
+
output_dict["sum_variable_1d_1"], expected_projection_values_1d_1
|
|
236
|
+
)
|
|
237
|
+
np.testing.assert_equal(
|
|
238
|
+
output_dict["sum_variable_1d_2"], expected_projection_values_1d_2
|
|
239
|
+
)
|
|
240
|
+
np.testing.assert_equal(
|
|
241
|
+
output_dict["sum_variable_2d"], expected_projection_values_2d
|
|
242
|
+
)
|
|
243
|
+
np.testing.assert_equal(
|
|
244
|
+
output_dict["sum_variable_3d"], expected_projection_values_3d
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def test_bin_values_at_indices_2d_indices_raises(self):
|
|
248
|
+
"""2D indices are not supported for binning.
|
|
249
|
+
Test that ValueError is raised."""
|
|
250
|
+
input_values = np.array([1, 2, 3])
|
|
251
|
+
input_indices = np.array([[0, 1], [1, 2]])
|
|
252
|
+
projection_indices = np.array([0, 1, 2])
|
|
253
|
+
projection_grid_shape = (3,)
|
|
254
|
+
|
|
255
|
+
with pytest.raises(
|
|
256
|
+
ValueError,
|
|
257
|
+
match=(
|
|
258
|
+
"Indices must be 1D arrays. If using a rectangular grid, "
|
|
259
|
+
"the indices must be unwrapped."
|
|
260
|
+
),
|
|
261
|
+
):
|
|
262
|
+
map_utils.bin_single_array_at_indices(
|
|
263
|
+
input_values,
|
|
264
|
+
input_indices=input_indices,
|
|
265
|
+
projection_indices=projection_indices,
|
|
266
|
+
projection_grid_shape=projection_grid_shape,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def test_bin_values_at_indices_mismatched_sizes_raises(self):
|
|
270
|
+
"""Mismatched input and projection indices should raise an error.
|
|
271
|
+
Test that ValueError is raised."""
|
|
272
|
+
input_values = np.array([1, 2, 3])
|
|
273
|
+
input_indices = np.array([0, 1, 0, 1])
|
|
274
|
+
projection_indices = np.array([0, 1, 2])
|
|
275
|
+
projection_grid_shape = (3,)
|
|
276
|
+
|
|
277
|
+
with pytest.raises(
|
|
278
|
+
ValueError,
|
|
279
|
+
match=("The number of input and projection indices must be the same"),
|
|
280
|
+
):
|
|
281
|
+
map_utils.bin_single_array_at_indices(
|
|
282
|
+
input_values,
|
|
283
|
+
input_indices=input_indices,
|
|
284
|
+
projection_indices=projection_indices,
|
|
285
|
+
projection_grid_shape=projection_grid_shape,
|
|
286
|
+
)
|