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,519 @@
|
|
|
1
|
+
"""Define classes for handling pointing sets and maps for ENA data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import pathlib
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import xarray as xr
|
|
12
|
+
from numpy.typing import NDArray
|
|
13
|
+
|
|
14
|
+
from imap_processing.cdf.utils import load_cdf
|
|
15
|
+
from imap_processing.ena_maps.utils import map_utils, spatial_utils
|
|
16
|
+
from imap_processing.spice import geometry
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SkyTilingType(Enum):
|
|
22
|
+
"""Enumeration of the types of tiling used in the ENA maps."""
|
|
23
|
+
|
|
24
|
+
RECTANGULAR = "Rectangular"
|
|
25
|
+
HEALPIX = "Healpix"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class IndexMatchMethod(Enum):
|
|
29
|
+
"""
|
|
30
|
+
Enumeration of the types of index matching methods used in the ENA sky maps.
|
|
31
|
+
|
|
32
|
+
Notes
|
|
33
|
+
-----
|
|
34
|
+
Index matching is the process of determining which pixels in a map grid correspond
|
|
35
|
+
to which pixels in a pointing set grid. The Ultra instrument team has determined
|
|
36
|
+
that they must support two methods of index matching for rectangular grid maps:
|
|
37
|
+
|
|
38
|
+
**Push Method**
|
|
39
|
+
|
|
40
|
+
The "push" method takes each pixel in a pointing set and transforms its coordinates
|
|
41
|
+
to the frame of the map, then determines into which pixel in the map grid the
|
|
42
|
+
transformed pointing set pixel falls.
|
|
43
|
+
This method ensures that all pointing set pixels (and thus all counts) are
|
|
44
|
+
captured in the map, but does not ensure that all pixels in the map receive data.
|
|
45
|
+
|
|
46
|
+
**Pull Method**
|
|
47
|
+
|
|
48
|
+
The "pull" method takes each pixel in the map grid and transforms its coordinates
|
|
49
|
+
to the frame of the pointing set, then determines into which pixel in the
|
|
50
|
+
pointing set grid the transformed map pixel falls.
|
|
51
|
+
This method ensures that all pixels in the map receive data, but can result in
|
|
52
|
+
some pointing set pixels not being captured in the map, and others being captured
|
|
53
|
+
multiple times.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
PUSH = "Push"
|
|
57
|
+
PULL = "Pull"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def match_coords_to_indices(
|
|
61
|
+
input_object: PointingSet | AbstractSkyMap,
|
|
62
|
+
output_object: PointingSet | AbstractSkyMap,
|
|
63
|
+
event_time: float | None = None,
|
|
64
|
+
) -> NDArray:
|
|
65
|
+
"""
|
|
66
|
+
Find the output indices corresponding to each input coord between 2 spatial objects.
|
|
67
|
+
|
|
68
|
+
First, the pixel center coordinates of the input spatial object are
|
|
69
|
+
transformed from the Spice coordinate frame of the input object to their
|
|
70
|
+
corresponding coordinates in the Spice frame of the output object.
|
|
71
|
+
Then, the transformed pixel centers are matched to the 1D indices of the spatial
|
|
72
|
+
pixels in the output frame, either in an unwrapped rectangular grid or a Healpix
|
|
73
|
+
tessellation of the sky.
|
|
74
|
+
|
|
75
|
+
This function always "pushes" the pixels of the input object to corresponding pixels
|
|
76
|
+
in the output object's unwrapped rectangular grid or healpix tessellation;
|
|
77
|
+
however, by swapping the input and output objects, one can apply the "pull" method
|
|
78
|
+
of index matching.
|
|
79
|
+
|
|
80
|
+
At present, the allowable inputs are either:
|
|
81
|
+
- A PointingSet object and a SkyMap object, in either order of input/output.
|
|
82
|
+
The event time will be taken from the PointingSet object.
|
|
83
|
+
- Two SkyMap objects, in which case the event time must be specified.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
input_object : PointingSet | AbstractSkyMap
|
|
88
|
+
An object containing 1D spatial pixel centers in azimuth and elevation,
|
|
89
|
+
which will be matched to 1D indices of spatial pixels in the output frame.
|
|
90
|
+
Must contain the Spice frame in which the pixel centers are defined.
|
|
91
|
+
output_object : PointingSet | AbstractSkyMap
|
|
92
|
+
The object containing a grid or tessellation of spatial pixels
|
|
93
|
+
into which the input spatial pixel centers will 'land', and be matched to
|
|
94
|
+
corresponding pixel 1D indices in the output frame.
|
|
95
|
+
event_time : float, optional
|
|
96
|
+
Event time at which to transform the input spatial object to the output frame.
|
|
97
|
+
This can be manually specified, e.g., for converting between Maps which do not
|
|
98
|
+
contain an epoch value.
|
|
99
|
+
The default value is None, in which case the event time of the PointingSet
|
|
100
|
+
object is used.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
flat_indices_input_grid_output_frame : NDArray
|
|
105
|
+
1D array of pixel indices of the output object corresponding to each pixel in
|
|
106
|
+
the input object. The length of the array is equal to the number of pixels in
|
|
107
|
+
the input object, and may contain 0, 1, or multiple occurrences of the same
|
|
108
|
+
output index.
|
|
109
|
+
|
|
110
|
+
Raises
|
|
111
|
+
------
|
|
112
|
+
ValueError
|
|
113
|
+
If both input and output objects are PointingSet objects.
|
|
114
|
+
ValueError
|
|
115
|
+
If the event time is not specified and both objects are SkyMaps.
|
|
116
|
+
NotImplementedError
|
|
117
|
+
If the output tiling type is HEALPIX. Will be implemented in the future.
|
|
118
|
+
ValueError
|
|
119
|
+
If the tiling type of the output frame is not RECTANGULAR or HEALPIX.
|
|
120
|
+
"""
|
|
121
|
+
if isinstance(input_object, PointingSet) and isinstance(output_object, PointingSet):
|
|
122
|
+
raise ValueError("Cannot match indices between two PointingSet objects.")
|
|
123
|
+
|
|
124
|
+
# If event_time is not specified, use event_time of the PointingSet, if present.
|
|
125
|
+
if event_time is None:
|
|
126
|
+
if isinstance(input_object, PointingSet):
|
|
127
|
+
event_time = input_object.data["epoch"].values
|
|
128
|
+
elif isinstance(output_object, PointingSet):
|
|
129
|
+
event_time = output_object.data["epoch"].values
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
"Event time must be specified if both objects are SkyMaps."
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Az/El pixel center coords of the input object in its own frame
|
|
136
|
+
input_obj_az_el_input_frame = input_object.az_el_points
|
|
137
|
+
|
|
138
|
+
# Transform the input pixel centers to the output frame
|
|
139
|
+
input_obj_az_el_output_frame = geometry.frame_transform_az_el(
|
|
140
|
+
et=event_time,
|
|
141
|
+
az_el=input_obj_az_el_input_frame,
|
|
142
|
+
from_frame=input_object.spice_reference_frame,
|
|
143
|
+
to_frame=output_object.spice_reference_frame,
|
|
144
|
+
degrees=False,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# The way indices are matched depends on the tiling type of the 2nd object
|
|
148
|
+
if output_object.tiling_type is SkyTilingType.RECTANGULAR:
|
|
149
|
+
# To match to a rectangular grid, we need to digitize the transformed az, el
|
|
150
|
+
# pixel centers onto the bin edges of the output frame's grid, then
|
|
151
|
+
# use ravel_multi_index to get the 1D indices of the pixels in the output frame.
|
|
152
|
+
az_indices = (
|
|
153
|
+
np.digitize(
|
|
154
|
+
input_obj_az_el_output_frame[:, 0],
|
|
155
|
+
output_object.sky_grid.az_bin_edges,
|
|
156
|
+
)
|
|
157
|
+
- 1
|
|
158
|
+
)
|
|
159
|
+
el_indices = (
|
|
160
|
+
np.digitize(
|
|
161
|
+
input_obj_az_el_output_frame[:, 1],
|
|
162
|
+
output_object.sky_grid.el_bin_edges,
|
|
163
|
+
)
|
|
164
|
+
- 1
|
|
165
|
+
)
|
|
166
|
+
flat_indices_input_grid_output_frame = np.ravel_multi_index(
|
|
167
|
+
multi_index=(az_indices, el_indices),
|
|
168
|
+
dims=(
|
|
169
|
+
len(output_object.sky_grid.az_bin_midpoints),
|
|
170
|
+
len(output_object.sky_grid.el_bin_midpoints),
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
elif output_object.tiling_type is SkyTilingType.HEALPIX:
|
|
175
|
+
# To match to a Healpix tessellation, we need to use the healpy function ang2pix
|
|
176
|
+
# which directly returns the index on the output frame's Healpix tessellation.
|
|
177
|
+
"""
|
|
178
|
+
Leaving this as a placeholder for now, so we don't yet
|
|
179
|
+
need to add a healpy dependency. It will look something like the
|
|
180
|
+
following code, much simpler than the rectangular case:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
import healpy as hp
|
|
184
|
+
flat_indices_input_grid_output_frame = hp.ang2pix(
|
|
185
|
+
nside=spatial_object_output_frame.nside,
|
|
186
|
+
theta=np.rad2deg(obj1_az_el_points_frame2[:, 0]), # Lon
|
|
187
|
+
phi=np.rad2deg(obj1_az_el_points_frame2[:, 1]), # Lat
|
|
188
|
+
nest=False,
|
|
189
|
+
lonlat=True,
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
"""
|
|
193
|
+
raise NotImplementedError(
|
|
194
|
+
"Index matching for output tiling type Healpix is not yet implemented."
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError(
|
|
199
|
+
"Tiling type of the output frame must be either RECTANGULAR or HEALPIX."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return flat_indices_input_grid_output_frame
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
# Define the pointing set classes
|
|
206
|
+
class PointingSet(ABC):
|
|
207
|
+
"""
|
|
208
|
+
Abstract class to contain pointing set (PSET) data in the context of ENA sky maps.
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
dataset : xr.Dataset
|
|
213
|
+
Dataset containing the pointing set data.
|
|
214
|
+
spice_reference_frame : geometry.SpiceFrame
|
|
215
|
+
The reference Spice frame of the pointing set.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
@abstractmethod
|
|
219
|
+
def __init__(self, dataset: xr.Dataset, spice_reference_frame: geometry.SpiceFrame):
|
|
220
|
+
"""Abstract method to initialize the pointing set object."""
|
|
221
|
+
self.spice_reference_frame = spice_reference_frame
|
|
222
|
+
self.num_points = 0
|
|
223
|
+
self.az_el_points = np.zeros((self.num_points, 2))
|
|
224
|
+
self.data = xr.Dataset()
|
|
225
|
+
|
|
226
|
+
def __repr__(self) -> str:
|
|
227
|
+
"""
|
|
228
|
+
Return a string representation of the pointing set.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
str
|
|
233
|
+
String representation of the pointing set.
|
|
234
|
+
"""
|
|
235
|
+
return (
|
|
236
|
+
f"{self.__class__} PointingSet"
|
|
237
|
+
f"(spice_reference_frame={self.spice_reference_frame})"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class UltraPointingSet(PointingSet):
|
|
242
|
+
"""
|
|
243
|
+
PSET object specifically for ULTRA data, nominally at Level 1C.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
l1c_dataset : xr.Dataset | pathlib.Path | str
|
|
248
|
+
L1c xarray dataset containing the pointing set data or the path to the dataset.
|
|
249
|
+
Currently, the dataset is expected to be in a rectangular grid,
|
|
250
|
+
with data_vars indexed along the coordinates:
|
|
251
|
+
- 'epoch' : time value (1 value per PSET)
|
|
252
|
+
- 'azimuth_bin_center' : azimuth bin center values
|
|
253
|
+
- 'elevation_bin_center' : elevation bin center values
|
|
254
|
+
Some data_vars may additionally be indexed by energy bin;
|
|
255
|
+
however, only the spatial axes are used in this class.
|
|
256
|
+
spice_reference_frame : geometry.SpiceFrame
|
|
257
|
+
The reference Spice frame of the pointing set. Default is IMAP_DPS.
|
|
258
|
+
|
|
259
|
+
Raises
|
|
260
|
+
------
|
|
261
|
+
ValueError
|
|
262
|
+
If the azimuth or elevation bin centers do not match the constructed grid.
|
|
263
|
+
Or if the azimuth or elevation bin spacing is not uniform.
|
|
264
|
+
ValueError
|
|
265
|
+
If multiple epochs are found in the dataset.
|
|
266
|
+
"""
|
|
267
|
+
|
|
268
|
+
def __init__(
|
|
269
|
+
self,
|
|
270
|
+
l1c_dataset: xr.Dataset | pathlib.Path | str,
|
|
271
|
+
spice_reference_frame: geometry.SpiceFrame = geometry.SpiceFrame.IMAP_DPS,
|
|
272
|
+
):
|
|
273
|
+
# Store the reference frame of the pointing set
|
|
274
|
+
self.spice_reference_frame = spice_reference_frame
|
|
275
|
+
|
|
276
|
+
# Read in the data and store the xarray dataset as data attr
|
|
277
|
+
if isinstance(l1c_dataset, (str, pathlib.Path)):
|
|
278
|
+
self.data = load_cdf(pathlib.Path(l1c_dataset))
|
|
279
|
+
elif isinstance(l1c_dataset, xr.Dataset):
|
|
280
|
+
self.data = l1c_dataset
|
|
281
|
+
|
|
282
|
+
# A PSET must have a single epoch
|
|
283
|
+
self.epoch = self.data["epoch"].values
|
|
284
|
+
if len(np.unique(self.epoch)) > 1:
|
|
285
|
+
raise ValueError("Multiple epochs found in the dataset.")
|
|
286
|
+
|
|
287
|
+
# The rest of the constructor handles the rectangular grid
|
|
288
|
+
# aspects of the Ultra PSET.
|
|
289
|
+
# NOTE: This may be changed to Healpix tessellation in the future
|
|
290
|
+
self.tiling_type = SkyTilingType.RECTANGULAR
|
|
291
|
+
|
|
292
|
+
# Ensure 1D axes grids are uniformly spaced,
|
|
293
|
+
# then set spacing based on data's azimuth bin spacing.
|
|
294
|
+
az_bin_delta = np.diff(self.data["azimuth_bin_center"])
|
|
295
|
+
el_bin_delta = np.diff(self.data["elevation_bin_center"])
|
|
296
|
+
if not np.allclose(az_bin_delta, az_bin_delta[0], atol=1e-10, rtol=0):
|
|
297
|
+
raise ValueError("Azimuth bin spacing is not uniform.")
|
|
298
|
+
if not np.allclose(el_bin_delta, el_bin_delta[0], atol=1e-10, rtol=0):
|
|
299
|
+
raise ValueError("Elevation bin spacing is not uniform.")
|
|
300
|
+
if not np.isclose(az_bin_delta[0], el_bin_delta[0], atol=1e-10, rtol=0):
|
|
301
|
+
raise ValueError(
|
|
302
|
+
"Azimuth and elevation bin spacing do not match: "
|
|
303
|
+
f"az {az_bin_delta[0]} != el {el_bin_delta[0]}."
|
|
304
|
+
)
|
|
305
|
+
self.spacing_deg = az_bin_delta[0]
|
|
306
|
+
|
|
307
|
+
# Build the azimuth and elevation grids with an AzElSkyGrid object
|
|
308
|
+
# and check that the 1D axes match the dataset's az and el.
|
|
309
|
+
self.sky_grid = spatial_utils.AzElSkyGrid(
|
|
310
|
+
spacing_deg=self.spacing_deg,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
for dim, constructed_bins in zip(
|
|
314
|
+
["azimuth", "elevation"],
|
|
315
|
+
[self.sky_grid.az_bin_midpoints, self.sky_grid.el_bin_midpoints],
|
|
316
|
+
):
|
|
317
|
+
if not np.allclose(
|
|
318
|
+
sorted(np.rad2deg(constructed_bins)),
|
|
319
|
+
self.data[f"{dim}_bin_center"],
|
|
320
|
+
atol=1e-10,
|
|
321
|
+
rtol=0,
|
|
322
|
+
):
|
|
323
|
+
raise ValueError(
|
|
324
|
+
f"{dim} bin centers do not match."
|
|
325
|
+
f"Constructed: {np.rad2deg(constructed_bins)}"
|
|
326
|
+
f"Dataset: {self.data[f'{dim}_bin_center']}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Unwrap the az, el grids to series of points tiling the sky and combine them
|
|
330
|
+
# into shape (number of points in tiling of the sky, 2) where
|
|
331
|
+
# column 0 (az_el_points[:, 0]) is the azimuth of that point and
|
|
332
|
+
# column 1 (az_el_points[:, 1]) is the elevation of that point.
|
|
333
|
+
self.az_el_points = np.column_stack(
|
|
334
|
+
(
|
|
335
|
+
self.sky_grid.az_grid.ravel(),
|
|
336
|
+
self.sky_grid.el_grid.ravel(),
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
self.num_points = self.az_el_points.shape[0]
|
|
340
|
+
|
|
341
|
+
# Also store the bin edges for the pointing set to allow for "pull" method
|
|
342
|
+
# of index matching (not yet implemented).
|
|
343
|
+
# These are 1D arrays of different lengths and cannot be stacked.
|
|
344
|
+
self.az_bin_edges = self.sky_grid.az_bin_edges
|
|
345
|
+
self.el_bin_edges = self.sky_grid.el_bin_edges
|
|
346
|
+
|
|
347
|
+
def __repr__(self) -> str:
|
|
348
|
+
"""
|
|
349
|
+
Return a string representation of the UltraPointingSet.
|
|
350
|
+
|
|
351
|
+
Returns
|
|
352
|
+
-------
|
|
353
|
+
str
|
|
354
|
+
String representation of the UltraPointingSet.
|
|
355
|
+
"""
|
|
356
|
+
return (
|
|
357
|
+
f"UltraPointingSet\n\t(spice_reference_frame="
|
|
358
|
+
f"{self.spice_reference_frame}, epoch={self.epoch}, "
|
|
359
|
+
f"num_points={self.num_points})"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# Define the Map classes
|
|
364
|
+
class AbstractSkyMap(ABC):
|
|
365
|
+
"""Abstract base class to contain map data in the context of ENA sky maps."""
|
|
366
|
+
|
|
367
|
+
@abstractmethod
|
|
368
|
+
def __init__(self) -> None:
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
def __repr__(self) -> str:
|
|
372
|
+
"""
|
|
373
|
+
Return a string representation of the map.
|
|
374
|
+
|
|
375
|
+
Returns
|
|
376
|
+
-------
|
|
377
|
+
str
|
|
378
|
+
String representation of the map.
|
|
379
|
+
"""
|
|
380
|
+
return f"{self.__class__} Map)"
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class RectangularSkyMap(AbstractSkyMap):
|
|
384
|
+
"""
|
|
385
|
+
Map which tiles the sky with a 2D rectangular grid of azimuth/elevation pixels.
|
|
386
|
+
|
|
387
|
+
NOTE: Internally, the map is stored as a 1D array of pixels.
|
|
388
|
+
|
|
389
|
+
Parameters
|
|
390
|
+
----------
|
|
391
|
+
spacing_deg : float
|
|
392
|
+
The spacing of the rectangular grid in degrees.
|
|
393
|
+
spice_frame : geometry.SpiceFrame
|
|
394
|
+
The reference Spice frame of the map.
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
def __init__(
|
|
398
|
+
self,
|
|
399
|
+
spacing_deg: float,
|
|
400
|
+
spice_frame: geometry.SpiceFrame,
|
|
401
|
+
):
|
|
402
|
+
# Define the core properties of the map:
|
|
403
|
+
self.tiling_type = SkyTilingType.RECTANGULAR # Type of tiling of the sky
|
|
404
|
+
self.spacing_deg = spacing_deg
|
|
405
|
+
self.spice_reference_frame = spice_frame
|
|
406
|
+
self.sky_grid = spatial_utils.AzElSkyGrid(
|
|
407
|
+
spacing_deg=self.spacing_deg,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Solid angles of each pixel in the map grid in units of steradians
|
|
411
|
+
self.solid_angle_grid = spatial_utils.build_solid_angle_map(
|
|
412
|
+
spacing_deg=self.spacing_deg,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Unwrap the az, el, solid angle grids to series of points tiling the sky
|
|
416
|
+
az_points = self.sky_grid.az_grid.ravel()
|
|
417
|
+
el_points = self.sky_grid.el_grid.ravel()
|
|
418
|
+
self.az_el_points = np.column_stack((az_points, el_points))
|
|
419
|
+
self.solid_angle_points = self.solid_angle_grid.ravel()
|
|
420
|
+
self.num_points = self.az_el_points.shape[0]
|
|
421
|
+
|
|
422
|
+
# Initialize empty data dictionary to store map data
|
|
423
|
+
self.data_dict: dict[str, NDArray] = {}
|
|
424
|
+
|
|
425
|
+
def project_pset_values_to_map(
|
|
426
|
+
self,
|
|
427
|
+
pointing_set: PointingSet,
|
|
428
|
+
value_keys: list[str] | None = None,
|
|
429
|
+
index_match_method: IndexMatchMethod = IndexMatchMethod.PUSH,
|
|
430
|
+
) -> None:
|
|
431
|
+
"""
|
|
432
|
+
Project a pointing set's values to the map grid.
|
|
433
|
+
|
|
434
|
+
Here, the term "project" refers to the process of determining which pixels in
|
|
435
|
+
the map grid correspond to which pixels in the pointing set grid, and then
|
|
436
|
+
binning the values at those indices from the pointing set to the map.
|
|
437
|
+
|
|
438
|
+
Parameters
|
|
439
|
+
----------
|
|
440
|
+
pointing_set : PointingSet
|
|
441
|
+
The pointing set containing the values to project to the map.
|
|
442
|
+
value_keys : list[tuple[str, IndexMatchMethod]] | None
|
|
443
|
+
The keys of the values to project to the map.
|
|
444
|
+
Ex.: ["counts", "flux"]
|
|
445
|
+
data_vars named each key must be present, and of the same dimensionality in
|
|
446
|
+
each pointing set which is to be projected to the map.
|
|
447
|
+
Default is None, in which case all data_vars in the pointing set are used.
|
|
448
|
+
index_match_method : IndexMatchMethod, optional
|
|
449
|
+
The method of index matching to use for all values.
|
|
450
|
+
Default is IndexMatchMethod.PUSH.
|
|
451
|
+
|
|
452
|
+
Raises
|
|
453
|
+
------
|
|
454
|
+
ValueError
|
|
455
|
+
If a value key is not found in the pointing set.
|
|
456
|
+
"""
|
|
457
|
+
if value_keys is None:
|
|
458
|
+
value_keys = list(pointing_set.data.data_vars.keys())
|
|
459
|
+
|
|
460
|
+
for value_key in value_keys:
|
|
461
|
+
if value_key not in pointing_set.data.data_vars:
|
|
462
|
+
raise ValueError(f"Value key {value_key} not found in pointing set.")
|
|
463
|
+
|
|
464
|
+
# Determine the indices of the sky map grid that correspond to
|
|
465
|
+
# each pixel in the pointing set.
|
|
466
|
+
if index_match_method is IndexMatchMethod.PUSH:
|
|
467
|
+
matched_indices_push = match_coords_to_indices(
|
|
468
|
+
input_object=pointing_set,
|
|
469
|
+
output_object=self,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
for value_key in value_keys:
|
|
473
|
+
# If multiple spatial axes present
|
|
474
|
+
# (i.e (az, el) for rectangular coordinate PSET),
|
|
475
|
+
# flatten them in the values array to match the raveled indices
|
|
476
|
+
raveled_pset_data = pointing_set.data[value_key].data.reshape(
|
|
477
|
+
pointing_set.num_points, -1
|
|
478
|
+
)
|
|
479
|
+
if value_key not in self.data_dict:
|
|
480
|
+
# Initialize the map data array if it doesn't exist (values start at 0)
|
|
481
|
+
output_shape = (self.num_points, *raveled_pset_data.shape[1:])
|
|
482
|
+
self.data_dict[value_key] = np.zeros(output_shape)
|
|
483
|
+
|
|
484
|
+
if index_match_method is IndexMatchMethod.PUSH:
|
|
485
|
+
pointing_projected_values = map_utils.bin_single_array_at_indices(
|
|
486
|
+
value_array=raveled_pset_data,
|
|
487
|
+
projection_grid_shape=(
|
|
488
|
+
len(self.sky_grid.az_bin_midpoints),
|
|
489
|
+
len(self.sky_grid.el_bin_midpoints),
|
|
490
|
+
),
|
|
491
|
+
projection_indices=matched_indices_push,
|
|
492
|
+
)
|
|
493
|
+
else:
|
|
494
|
+
raise NotImplementedError(
|
|
495
|
+
"The 'pull' method of index matching is not yet implemented."
|
|
496
|
+
)
|
|
497
|
+
self.data_dict[value_key] += pointing_projected_values
|
|
498
|
+
|
|
499
|
+
def __repr__(self) -> str:
|
|
500
|
+
"""
|
|
501
|
+
Return a string representation of the RectangularSkyMap.
|
|
502
|
+
|
|
503
|
+
Returns
|
|
504
|
+
-------
|
|
505
|
+
str
|
|
506
|
+
String representation of the RectangularSkyMap.
|
|
507
|
+
"""
|
|
508
|
+
return (
|
|
509
|
+
"RectangularSkyMap\n\t(reference_frame="
|
|
510
|
+
f"{self.spice_reference_frame.name} ({self.spice_reference_frame.value}), "
|
|
511
|
+
f"spacing_deg={self.spacing_deg}, num_points={self.num_points})"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
# TODO:
|
|
516
|
+
# Add pulling index matching in match_pset_coords_to_indices
|
|
517
|
+
|
|
518
|
+
# TODO:
|
|
519
|
+
# Check units of time which will be read in. Do we need to add j2000ns_to_j2000s?
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Utilities for generating ENA maps."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from numpy.typing import NDArray
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def bin_single_array_at_indices(
|
|
14
|
+
value_array: NDArray,
|
|
15
|
+
projection_grid_shape: tuple[int, int],
|
|
16
|
+
projection_indices: NDArray,
|
|
17
|
+
input_indices: NDArray | None = None,
|
|
18
|
+
) -> NDArray:
|
|
19
|
+
"""
|
|
20
|
+
Bin an array of values at the given indices.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
value_array : NDArray
|
|
25
|
+
Array of values to bin. The 0th axis must be the one and only spatial axis.
|
|
26
|
+
If other axes are present, they will be binned independently
|
|
27
|
+
along the 0th (spatial) axis.
|
|
28
|
+
projection_grid_shape : tuple[int]
|
|
29
|
+
The shape of the grid onto which values are projected
|
|
30
|
+
(rows, columns) if the grid is rectangular,
|
|
31
|
+
or just (number of bins,) if the grid is 1D.
|
|
32
|
+
projection_indices : NDArray
|
|
33
|
+
Ordered indices for projection grid, corresponding to indices in input grid.
|
|
34
|
+
1 dimensional. May be non-unique, depending on the projection method.
|
|
35
|
+
input_indices : NDArray
|
|
36
|
+
Ordered indices for input grid, corresponding to indices in projection grid.
|
|
37
|
+
1 dimensional. May be non-unique, depending on the projection method.
|
|
38
|
+
If None (default), an arange of the same length as the
|
|
39
|
+
0th axis of value_array is used.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
NDArray
|
|
44
|
+
Binned values on the projection grid.
|
|
45
|
+
|
|
46
|
+
Raises
|
|
47
|
+
------
|
|
48
|
+
ValueError
|
|
49
|
+
If the input and projection indices are not 1D arrays
|
|
50
|
+
with the same number of elements.
|
|
51
|
+
NotImplementedError
|
|
52
|
+
If the input value_array has dimensionality less than 1.
|
|
53
|
+
"""
|
|
54
|
+
if input_indices is None:
|
|
55
|
+
input_indices = np.arange(value_array.shape[0])
|
|
56
|
+
|
|
57
|
+
# Both sets of indices must be 1D with the same number of elements
|
|
58
|
+
if input_indices.ndim != 1 or projection_indices.ndim != 1:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
"Indices must be 1D arrays. "
|
|
61
|
+
"If using a rectangular grid, the indices must be unwrapped."
|
|
62
|
+
)
|
|
63
|
+
if input_indices.size != projection_indices.size:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
"The number of input and projection indices must be the same. \n"
|
|
66
|
+
f"Received {input_indices.size} input indices and {projection_indices.size}"
|
|
67
|
+
" projection indices."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
num_projection_indices = np.prod(projection_grid_shape)
|
|
71
|
+
|
|
72
|
+
if value_array.ndim == 1:
|
|
73
|
+
binned_values = np.bincount(
|
|
74
|
+
projection_indices,
|
|
75
|
+
weights=value_array[input_indices],
|
|
76
|
+
minlength=num_projection_indices,
|
|
77
|
+
)
|
|
78
|
+
elif value_array.ndim >= 2:
|
|
79
|
+
# Apply bincount to each row independently
|
|
80
|
+
binned_values = np.apply_along_axis(
|
|
81
|
+
lambda x: np.bincount(
|
|
82
|
+
projection_indices,
|
|
83
|
+
weights=x[input_indices, ...],
|
|
84
|
+
minlength=num_projection_indices,
|
|
85
|
+
),
|
|
86
|
+
axis=0,
|
|
87
|
+
arr=value_array,
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
raise NotImplementedError(
|
|
91
|
+
"Only 1+ Dimensional arrays are supported for binning. "
|
|
92
|
+
f"Received array with shape {value_array.shape}."
|
|
93
|
+
)
|
|
94
|
+
return binned_values
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def bin_values_at_indices(
|
|
98
|
+
input_values_to_bin: dict[str, NDArray],
|
|
99
|
+
projection_grid_shape: tuple[int, int],
|
|
100
|
+
projection_indices: NDArray,
|
|
101
|
+
input_indices: NDArray | None = None,
|
|
102
|
+
) -> dict[str, NDArray]:
|
|
103
|
+
"""
|
|
104
|
+
Project values from input grid to projection grid based on matched indices.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
input_values_to_bin : dict[str, NDArray]
|
|
109
|
+
Dict matching variable names to arrays of values to bin.
|
|
110
|
+
The 0th axis of each array must be the one and only spatial axis,
|
|
111
|
+
which the indices correspond to and on which the values will be binned.
|
|
112
|
+
The other axes will be binned independently along this 0th axis.
|
|
113
|
+
projection_grid_shape : tuple[int, int]
|
|
114
|
+
The shape of the grid onto which values are projected (rows, columns).
|
|
115
|
+
This size of the resulting grid (rows * columns) will be the size of the
|
|
116
|
+
projected values contained in the output dictionary.
|
|
117
|
+
projection_indices : NDArray
|
|
118
|
+
Ordered indices for projection grid, corresponding to indices in input grid.
|
|
119
|
+
1 dimensional. May be non-unique, depending on the projection method.
|
|
120
|
+
input_indices : NDArray
|
|
121
|
+
Ordered indices for input grid, corresponding to indices in projection grid.
|
|
122
|
+
1 dimensional. May be non-unique, depending on the projection method.
|
|
123
|
+
If None (default), behavior is determined by bin_single_array_at_indices.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
dict[str, NDArray]
|
|
128
|
+
Dict matching the input variable names to the binned values
|
|
129
|
+
on the projection grid.
|
|
130
|
+
|
|
131
|
+
ValueError
|
|
132
|
+
If the input and projection indices are not 1D arrays
|
|
133
|
+
with the same number of elements.
|
|
134
|
+
"""
|
|
135
|
+
binned_values_dict = {}
|
|
136
|
+
for value_name, value_array in input_values_to_bin.items():
|
|
137
|
+
logger.info(f"Binning {value_name}")
|
|
138
|
+
binned_values_dict[value_name] = bin_single_array_at_indices(
|
|
139
|
+
value_array=value_array,
|
|
140
|
+
projection_grid_shape=projection_grid_shape,
|
|
141
|
+
projection_indices=projection_indices,
|
|
142
|
+
input_indices=input_indices,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return binned_values_dict
|